mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: auto-swaps (#1266)
Also fixes #1303 ## TODOs - [x] Do final testing with LDK/LND on mainnet - [x] Improve fee display on the frontend, currently looks scary - [x] ~Shift Swaps from settings~ (Agreeed to do in follow-up) - [x] ~Add option to do one-time swaps~ (Agreeed to do in follow-up) ### Minor - [x] `LDK_NETWORK` config var should now be `NETWORK` - [x] ~Use LNClient method to publish transactions~ (Agreeed to do in follow-up) ### Testing - [ ] Phoenix - [ ] Cashu - [x] Wails
This commit is contained in:
commit
f41f96335e
38 changed files with 1260 additions and 140 deletions
|
|
@ -35,4 +35,8 @@ FRONTEND_URL=http://localhost:5173
|
|||
#LND_ADDRESS=127.0.0.1:10001
|
||||
#LND_MACAROON_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/data/chain/bitcoin/regtest/admin.macaroon
|
||||
|
||||
#LDK_VSS_URL="http://localhost:8090/vss"
|
||||
#LDK_VSS_URL="http://localhost:8090/vss"
|
||||
|
||||
# Boltz API
|
||||
#BOLTZ_API=https://api.testnet.boltz.exchange
|
||||
#NETWORK=testnet
|
||||
18
README.md
18
README.md
|
|
@ -159,12 +159,14 @@ For more information refer to:
|
|||
The following configuration options can be set as environment variables or in a .env file
|
||||
|
||||
- `RELAY`: default: "wss://relay.getalby.com/v1"
|
||||
- `JWT_SECRET`: a randomly generated secret string. (only needed in http mode)
|
||||
- `DATABASE_URI`: a sqlite filename or postgres URL. Default is SQLite DB `nwc.db` without a path, which will be put in the user home directory: $XDG_DATA_HOME/albyhub/nwc.db
|
||||
- `PORT`: the port on which the app should listen on (default: 8080)
|
||||
- `WORK_DIR`: directory to store NWC data files. Default: $XDG_DATA_HOME/albyhub
|
||||
- `LOG_LEVEL`: log level for the application. Higher is more verbose. Default: 4 (info)
|
||||
- `AUTO_UNLOCK_PASSWORD`: provide unlock password to auto-unlock Alby Hub on startup (e.g. after a machine restart). Unlock password still be required to access the interface.
|
||||
- `JWT_SECRET`: A randomly generated secret string. (only needed in http mode)
|
||||
- `DATABASE_URI`: A sqlite filename or postgres URL. Default is SQLite DB `nwc.db` without a path, which will be put in the user home directory: $XDG_DATA_HOME/albyhub/nwc.db
|
||||
- `PORT`: The port on which the app should listen on (default: 8080)
|
||||
- `WORK_DIR`: Directory to store NWC data files. Default: $XDG_DATA_HOME/albyhub
|
||||
- `LOG_LEVEL`: Log level for the application. Higher is more verbose. Default: 4 (info)
|
||||
- `AUTO_UNLOCK_PASSWORD`: Provide unlock password to auto-unlock Alby Hub on startup (e.g. after a machine restart). Unlock password still be required to access the interface.
|
||||
- `BOLTZ_API`: The api which provides auto swaps functionality. Default: "https://api.boltz.exchange"
|
||||
- `NETWORK`: On-chain network used for auto swaps. Should match the backend network. Default: "bitcoin"
|
||||
|
||||
### Migrating the database (Sqlite <-> Postgres)
|
||||
|
||||
|
|
@ -204,14 +206,14 @@ _To configure via env, the following parameters must be provided:_
|
|||
##### Mutinynet
|
||||
|
||||
- `MEMPOOL_API=https://mutinynet.com/api`
|
||||
- `LDK_NETWORK=signet`
|
||||
- `NETWORK=signet`
|
||||
- `LDK_ESPLORA_SERVER=https://mutinynet.com/api`
|
||||
- `LDK_GOSSIP_SOURCE=https://rgs.mutinynet.com/snapshot`
|
||||
|
||||
##### Testnet (Not recommended - try Mutinynet)
|
||||
|
||||
- `MEMPOOL_API=https://mempool.space/testnet/api`
|
||||
- `LDK_NETWORK=testnet`
|
||||
- `NETWORK=testnet`
|
||||
- `LDK_ESPLORA_SERVER=https://mempool.space/testnet/api`
|
||||
- `LDK_GOSSIP_SOURCE=https://rapidsync.lightningdevkit.org/testnet/snapshot`
|
||||
|
||||
|
|
|
|||
78
api/api.go
78
api/api.go
|
|
@ -10,6 +10,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -543,6 +544,83 @@ func (api *api) GetNodeConnectionInfo(ctx context.Context) (*lnclient.NodeConnec
|
|||
return api.svc.GetLNClient().GetNodeConnectionInfo(ctx)
|
||||
}
|
||||
|
||||
func (api *api) GetAutoSwapsConfig() (*GetAutoSwapsConfigResponse, error) {
|
||||
swapBalanceThresholdStr, _ := api.cfg.Get(config.AutoSwapBalanceThresholdKey, "")
|
||||
swapAmountStr, _ := api.cfg.Get(config.AutoSwapAmountKey, "")
|
||||
swapDestination, _ := api.cfg.Get(config.AutoSwapDestinationKey, "")
|
||||
|
||||
enabled := swapBalanceThresholdStr != "" &&
|
||||
swapAmountStr != "" &&
|
||||
swapDestination != ""
|
||||
var swapBalanceThreshold, swapAmount uint64
|
||||
if enabled {
|
||||
var err error
|
||||
if swapBalanceThreshold, err = strconv.ParseUint(swapBalanceThresholdStr, 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("invalid autoswap balance threshold: %w", err)
|
||||
}
|
||||
if swapAmount, err = strconv.ParseUint(swapAmountStr, 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("invalid autoswap amount: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
swapFees, err := api.svc.GetSwapsService().CalculateFee()
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to calculate fee info")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GetAutoSwapsConfigResponse{
|
||||
Enabled: enabled,
|
||||
BalanceThreshold: swapBalanceThreshold,
|
||||
SwapAmount: swapAmount,
|
||||
Destination: swapDestination,
|
||||
AlbyServiceFee: swapFees.AlbyServiceFee,
|
||||
BoltzServiceFee: swapFees.BoltzServiceFee,
|
||||
BoltzNetworkFee: swapFees.BoltzNetworkFee,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *api) EnableAutoSwaps(ctx context.Context, enableAutoSwapsRequest *EnableAutoSwapsRequest) error {
|
||||
err := api.cfg.SetUpdate(config.AutoSwapBalanceThresholdKey, strconv.FormatUint(enableAutoSwapsRequest.BalanceThreshold, 10), "")
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to save autoswap balance threshold to config")
|
||||
return err
|
||||
}
|
||||
|
||||
err = api.cfg.SetUpdate(config.AutoSwapAmountKey, strconv.FormatUint(enableAutoSwapsRequest.SwapAmount, 10), "")
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to save autoswap amount to config")
|
||||
return err
|
||||
}
|
||||
|
||||
err = api.cfg.SetUpdate(config.AutoSwapDestinationKey, enableAutoSwapsRequest.Destination, "")
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to save autoswap destination to config")
|
||||
return err
|
||||
}
|
||||
|
||||
return api.svc.StartAutoSwaps()
|
||||
}
|
||||
|
||||
func (api *api) DisableAutoSwaps() error {
|
||||
if err := api.cfg.SetUpdate(config.AutoSwapBalanceThresholdKey, "", ""); err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to remove autoswap balance threshold")
|
||||
return err
|
||||
}
|
||||
if err := api.cfg.SetUpdate(config.AutoSwapAmountKey, "", ""); err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to remove autoswap amount")
|
||||
return err
|
||||
}
|
||||
if err := api.cfg.SetUpdate(config.AutoSwapDestinationKey, "", ""); err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to remove autoswap destination")
|
||||
return err
|
||||
}
|
||||
|
||||
api.svc.GetSwapsService().StopAutoSwaps()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *api) GetNodeStatus(ctx context.Context) (*lnclient.NodeStatus, error) {
|
||||
if api.svc.GetLNClient() == nil {
|
||||
return nil, errors.New("LNClient not started")
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ type API interface {
|
|||
GetWalletCapabilities(ctx context.Context) (*WalletCapabilitiesResponse, error)
|
||||
Health(ctx context.Context) (*HealthResponse, error)
|
||||
SetCurrency(currency string) error
|
||||
GetAutoSwapsConfig() (*GetAutoSwapsConfigResponse, error)
|
||||
DisableAutoSwaps() error
|
||||
EnableAutoSwaps(ctx context.Context, autoSwapsRequest *EnableAutoSwapsRequest) error
|
||||
GetCustomNodeCommands() (*CustomNodeCommandsResponse, error)
|
||||
ExecuteCustomNodeCommand(ctx context.Context, command string) (interface{}, error)
|
||||
}
|
||||
|
|
@ -114,6 +117,22 @@ type CreateAppRequest struct {
|
|||
UnlockPassword string `json:"unlockPassword"`
|
||||
}
|
||||
|
||||
type EnableAutoSwapsRequest struct {
|
||||
BalanceThreshold uint64 `json:"balanceThreshold"`
|
||||
SwapAmount uint64 `json:"swapAmount"`
|
||||
Destination string `json:"destination"`
|
||||
}
|
||||
|
||||
type GetAutoSwapsConfigResponse struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
BalanceThreshold uint64 `json:"balanceThreshold"`
|
||||
SwapAmount uint64 `json:"swapAmount"`
|
||||
Destination string `json:"destination"`
|
||||
AlbyServiceFee float64 `json:"albyServiceFee"`
|
||||
BoltzServiceFee float64 `json:"boltzServiceFee"`
|
||||
BoltzNetworkFee uint64 `json:"boltzNetworkFee"`
|
||||
}
|
||||
|
||||
type StartRequest struct {
|
||||
UnlockPassword string `json:"unlockPassword"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func (api *api) SendPayment(ctx context.Context, invoice string, amountMsat *uin
|
|||
if api.svc.GetLNClient() == nil {
|
||||
return nil, errors.New("LNClient not started")
|
||||
}
|
||||
transaction, err := api.svc.GetTransactionsService().SendPaymentSync(ctx, invoice, amountMsat, nil, api.svc.GetLNClient(), nil, nil)
|
||||
transaction, err := api.svc.GetTransactionsService().SendPaymentSync(ctx, invoice, amountMsat, nil, api.svc.GetLNClient(), nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ func (api *api) TopupIsolatedApp(ctx context.Context, userApp *db.App, amountMsa
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = api.svc.GetTransactionsService().SendPaymentSync(ctx, transaction.PaymentRequest, nil, nil, api.svc.GetLNClient(), nil, nil)
|
||||
_, err = api.svc.GetTransactionsService().SendPaymentSync(ctx, transaction.PaymentRequest, nil, nil, api.svc.GetLNClient(), nil, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,20 @@ func (cfg *config) GetRelayUrl() string {
|
|||
return relayUrl
|
||||
}
|
||||
|
||||
func (cfg *config) GetNetwork() string {
|
||||
env := cfg.GetEnv()
|
||||
|
||||
if env.Network != "" {
|
||||
return env.Network
|
||||
}
|
||||
|
||||
if env.LDKNetwork != "" {
|
||||
return env.LDKNetwork
|
||||
}
|
||||
|
||||
return "bitcoin"
|
||||
}
|
||||
|
||||
func (cfg *config) Get(key string, encryptionKey string) (string, error) {
|
||||
return cfg.get(key, encryptionKey, cfg.db)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
OnchainAddressKey = "OnchainAddress"
|
||||
OnchainAddressKey = "OnchainAddress"
|
||||
AutoSwapBalanceThresholdKey = "AutoSwapBalanceThreshold"
|
||||
AutoSwapAmountKey = "AutoSwapAmount"
|
||||
AutoSwapDestinationKey = "AutoSwapDestination"
|
||||
)
|
||||
|
||||
type AppConfig struct {
|
||||
|
|
@ -23,7 +26,8 @@ type AppConfig struct {
|
|||
JWTSecret string `envconfig:"JWT_SECRET"`
|
||||
LogLevel string `envconfig:"LOG_LEVEL" default:"4"`
|
||||
LogToFile bool `envconfig:"LOG_TO_FILE" default:"true"`
|
||||
LDKNetwork string `envconfig:"LDK_NETWORK" default:"bitcoin"`
|
||||
Network string `envconfig:"NETWORK"`
|
||||
LDKNetwork string `envconfig:"LDK_NETWORK"`
|
||||
LDKEsploraServer string `envconfig:"LDK_ESPLORA_SERVER" default:"https://electrs.getalbypro.com"` // TODO: remove LDK prefix
|
||||
LDKGossipSource string `envconfig:"LDK_GOSSIP_SOURCE"`
|
||||
LDKLogLevel string `envconfig:"LDK_LOG_LEVEL" default:"3"`
|
||||
|
|
@ -44,6 +48,7 @@ type AppConfig struct {
|
|||
EnableAdvancedSetup bool `envconfig:"ENABLE_ADVANCED_SETUP" default:"true"`
|
||||
AutoUnlockPassword string `envconfig:"AUTO_UNLOCK_PASSWORD"`
|
||||
LogDBQueries bool `envconfig:"LOG_DB_QUERIES" default:"false"`
|
||||
BoltzApi string `envconfig:"BOLTZ_API" default:"https://api.boltz.exchange"`
|
||||
}
|
||||
|
||||
func (c *AppConfig) IsDefaultClientId() bool {
|
||||
|
|
@ -56,6 +61,7 @@ type Config interface {
|
|||
SetUpdate(key string, value string, encryptionKey string) error
|
||||
GetJWTSecret() string
|
||||
GetRelayUrl() string
|
||||
GetNetwork() string
|
||||
GetEnv() *AppConfig
|
||||
CheckUnlockPassword(password string) bool
|
||||
ChangeUnlockPassword(currentUnlockPassword string, newUnlockPassword string) error
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ const (
|
|||
// accounting for encryption and other metadata in the response, this is set to 4096 characters
|
||||
const INVOICE_METADATA_MAX_LENGTH = 4096
|
||||
|
||||
const SEND_PAYMENT_TIMEOUT = 50
|
||||
|
||||
// errors used by NIP-47 and the transaction service
|
||||
const (
|
||||
ERROR_INTERNAL = "INTERNAL"
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ export default function SettingsLayout() {
|
|||
<aside className="flex flex-col justify-between lg:w-1/5">
|
||||
<nav className="flex flex-wrap lg:flex-col lg:space-y-1">
|
||||
<MenuItem to="/settings">General</MenuItem>
|
||||
<MenuItem to="/settings/swaps">Swaps</MenuItem>
|
||||
{info?.autoUnlockPasswordSupported && (
|
||||
<MenuItem to="/settings/auto-unlock">Auto Unlock</MenuItem>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const RadioGroupItem = React.forwardRef<
|
|||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||
<CheckIcon className="h-3.5 w-3.5 fill-primary" />
|
||||
<CheckIcon className="h-3.5 w-3.5" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
|
|
|
|||
16
frontend/src/hooks/useSwaps.tsx
Normal file
16
frontend/src/hooks/useSwaps.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import useSWR, { SWRConfiguration } from "swr";
|
||||
|
||||
import { SwapsSettingsResponse } from "src/types";
|
||||
import { swrFetcher } from "src/utils/swr";
|
||||
|
||||
const pollConfiguration: SWRConfiguration = {
|
||||
refreshInterval: 5 * 60 * 1000, // 5 minutes
|
||||
};
|
||||
|
||||
export function useSwaps(poll = true) {
|
||||
return useSWR<SwapsSettingsResponse>(
|
||||
"/api/settings/swaps",
|
||||
swrFetcher,
|
||||
poll ? pollConfiguration : undefined
|
||||
);
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ import { ChangeUnlockPassword } from "src/screens/settings/ChangeUnlockPassword"
|
|||
import DebugTools from "src/screens/settings/DebugTools";
|
||||
import DeveloperSettings from "src/screens/settings/DeveloperSettings";
|
||||
import Settings from "src/screens/settings/Settings";
|
||||
import Swaps from "src/screens/settings/Swaps";
|
||||
|
||||
import { ImportMnemonic } from "src/screens/setup/ImportMnemonic";
|
||||
import { RestoreNode } from "src/screens/setup/RestoreNode";
|
||||
|
|
@ -189,6 +190,10 @@ const routes = [
|
|||
element: <AutoUnlock />,
|
||||
handle: { crumb: () => "Auto Unlock" },
|
||||
},
|
||||
{
|
||||
path: "swaps",
|
||||
element: <Swaps />,
|
||||
},
|
||||
{
|
||||
path: "change-unlock-password",
|
||||
element: <ChangeUnlockPassword />,
|
||||
|
|
|
|||
261
frontend/src/screens/settings/Swaps.tsx
Normal file
261
frontend/src/screens/settings/Swaps.tsx
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
import { ClipboardPasteIcon, XCircleIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import Loading from "src/components/Loading";
|
||||
import SettingsHeader from "src/components/SettingsHeader";
|
||||
import { Button } from "src/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "src/components/ui/card";
|
||||
import { Input } from "src/components/ui/input";
|
||||
import { Label } from "src/components/ui/label";
|
||||
import { LoadingButton } from "src/components/ui/loading-button";
|
||||
import { RadioGroup, RadioGroupItem } from "src/components/ui/radio-group";
|
||||
import { useToast } from "src/components/ui/use-toast";
|
||||
import { useOnchainAddress } from "src/hooks/useOnchainAddress";
|
||||
import { useSwaps } from "src/hooks/useSwaps";
|
||||
import { request } from "src/utils/request";
|
||||
|
||||
function Swaps() {
|
||||
const { toast } = useToast();
|
||||
// TODO: Optimize by setting this from the backend
|
||||
const { data: onchainAddress } = useOnchainAddress();
|
||||
const { data: swapsSettings, mutate } = useSwaps();
|
||||
|
||||
const [swapTo, setSwapTo] = useState("hub");
|
||||
const [balanceThreshold, setBalanceThreshold] = useState("");
|
||||
const [swapAmount, setSwapAmount] = useState("");
|
||||
const [destination, setDestination] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
await request("/api/settings/swaps", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
swapAmount: parseInt(swapAmount),
|
||||
balanceThreshold: parseInt(balanceThreshold),
|
||||
destination: swapTo === "hub" ? onchainAddress : destination,
|
||||
}),
|
||||
});
|
||||
toast({ title: "Saved successfully." });
|
||||
await mutate();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Saving swap settings failed",
|
||||
description: (error as Error).message,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onDeactivate = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
await request("/api/settings/swaps", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
toast({ title: "Deactivated successfully." });
|
||||
await mutate();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Deactivating auto swaps failed",
|
||||
description: (error as Error).message,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const paste = async () => {
|
||||
const text = await navigator.clipboard.readText();
|
||||
setDestination(text.trim());
|
||||
};
|
||||
|
||||
if (!onchainAddress || !swapsSettings) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsHeader
|
||||
title="Swaps"
|
||||
description="Automatically swap lightning to on-chain funds, a fee of 1.5% applies to all swaps."
|
||||
/>
|
||||
{!swapsSettings.enabled ? (
|
||||
<form onSubmit={onSubmit} className="flex flex-col gap-4">
|
||||
<div className="grid gap-1.5">
|
||||
<Label>Spending balance threshold</Label>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Swap out as soon as this amount is reached"
|
||||
value={balanceThreshold}
|
||||
onChange={(e) => setBalanceThreshold(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<Label>Swap amount</Label>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="How much do you want to swap out?"
|
||||
value={swapAmount}
|
||||
min={50000}
|
||||
onChange={(e) => setSwapAmount(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Should be ≥ 50000 sats
|
||||
</p>
|
||||
</div>
|
||||
<Label>Destination</Label>
|
||||
<RadioGroup
|
||||
defaultValue="normal"
|
||||
value={swapTo}
|
||||
onValueChange={(val) => {
|
||||
setSwapTo(val);
|
||||
if (val == "hub") {
|
||||
setDestination(onchainAddress);
|
||||
} else {
|
||||
setDestination("");
|
||||
}
|
||||
}}
|
||||
className="flex gap-4 flex-row"
|
||||
>
|
||||
<div className="flex items-start space-x-2 mb-2">
|
||||
<RadioGroupItem value="hub" id="hub" className="shrink-0" />
|
||||
<Label
|
||||
htmlFor="hub"
|
||||
className="text-primary font-medium cursor-pointer"
|
||||
>
|
||||
Alby Hub on-chain balance
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
<RadioGroupItem
|
||||
value="external"
|
||||
id="external"
|
||||
className="shrink-0"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="external"
|
||||
className="text-primary font-medium cursor-pointer"
|
||||
>
|
||||
External on-chain wallet
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{swapTo == "external" && (
|
||||
<div className="grid gap-1.5">
|
||||
<Label>Receiving on-chain address</Label>
|
||||
<div className="flex gap-2 mb-4">
|
||||
<Input
|
||||
placeholder="bc1..."
|
||||
value={destination}
|
||||
onChange={(e) => setDestination(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="px-2"
|
||||
onClick={paste}
|
||||
>
|
||||
<ClipboardPasteIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between border-t py-4">
|
||||
<Label>Fee</Label>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{swapsSettings.albyServiceFee + swapsSettings.boltzServiceFee}% +
|
||||
on-chain fees
|
||||
</p>
|
||||
</div>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
disabled={
|
||||
!balanceThreshold || (swapTo == "external" && !destination)
|
||||
}
|
||||
>
|
||||
Enable Auto Swaps
|
||||
</LoadingButton>
|
||||
</form>
|
||||
) : (
|
||||
<Card className="w-full hidden md:block self-start">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="font-medium">Active Recurring Swap</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Alby Hub will try to perform a swap every time the balance reaches
|
||||
the threshold.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 space-y-4 text-sm">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">Type</span>
|
||||
<span className="text-muted-foreground text-right">
|
||||
Lightning to On-chain
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">Destination</span>
|
||||
<span className="text-muted-foreground text-right">
|
||||
{swapsSettings.destination}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">Spending Balance Threshold</span>
|
||||
<span className="text-muted-foreground text-right">
|
||||
{new Intl.NumberFormat().format(
|
||||
swapsSettings.balanceThreshold
|
||||
)}{" "}
|
||||
sats
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">Swap amount</span>
|
||||
<span className="text-muted-foreground text-right">
|
||||
{new Intl.NumberFormat().format(swapsSettings.swapAmount)}{" "}
|
||||
sats
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">Fee</span>
|
||||
<span className="text-muted-foreground text-right">
|
||||
{swapsSettings.albyServiceFee + swapsSettings.boltzServiceFee}
|
||||
% + on-chain fees
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end">
|
||||
<Button onClick={onDeactivate} disabled={loading} variant="outline">
|
||||
<XCircleIcon className="h-4 w-4 mr-2" />
|
||||
Deactivate
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Swaps;
|
||||
|
|
@ -190,6 +190,16 @@ export type AppMetadata = { app_store_app_id?: string } & Record<
|
|||
unknown
|
||||
>;
|
||||
|
||||
export type SwapsSettingsResponse = {
|
||||
enabled: boolean;
|
||||
balanceThreshold: number;
|
||||
swapAmount: number;
|
||||
destination: string;
|
||||
albyServiceFee: number;
|
||||
boltzServiceFee: number;
|
||||
boltzNetworkFee: number;
|
||||
};
|
||||
|
||||
export interface MnemonicResponse {
|
||||
mnemonic: string;
|
||||
}
|
||||
|
|
|
|||
20
go.mod
20
go.mod
|
|
@ -1,8 +1,6 @@
|
|||
module github.com/getAlby/hub
|
||||
|
||||
go 1.24.1
|
||||
|
||||
toolchain go1.24.2
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.5.3
|
||||
|
|
@ -73,6 +71,7 @@ require (
|
|||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/fergusstrange/embedded-postgres v1.29.0 // indirect
|
||||
github.com/frankban/quicktest v1.14.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
|
|
@ -85,7 +84,7 @@ require (
|
|||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.18.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
|
|
@ -148,6 +147,7 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
|
@ -173,7 +173,7 @@ require (
|
|||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
|
|
@ -184,6 +184,9 @@ require (
|
|||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
||||
github.com/vulpemventures/go-elements v0.5.5 // indirect
|
||||
github.com/vulpemventures/go-secp256k1-zkp v1.1.6 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
|
|
@ -216,13 +219,13 @@ require (
|
|||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/tools v0.32.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
|
|
@ -246,6 +249,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/BoltzExchange/boltz-client/v2 v2.6.0
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||
|
|
@ -258,7 +262,7 @@ require (
|
|||
github.com/lightningnetwork/lnd v0.19.0-beta.rc3
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/tyler-smith/go-bip32 v1.0.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
gorm.io/datatypes v1.2.5
|
||||
)
|
||||
|
||||
|
|
|
|||
34
go.sum
34
go.sum
|
|
@ -6,6 +6,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BoltzExchange/boltz-client/v2 v2.6.0 h1:TB7w3/p8fvymAcuaFpv9yozE3Ga6igtqVvtg7AH1Ohs=
|
||||
github.com/BoltzExchange/boltz-client/v2 v2.6.0/go.mod h1:bCuK5Lus9QY8L6z+W95wmkeO6U9OuaysRhRbI/d/fi0=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/appsec-internal-go v1.9.0 h1:cGOneFsg0JTRzWl5U2+og5dbtyW3N8XaYwc5nXe39Vw=
|
||||
github.com/DataDog/appsec-internal-go v1.9.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g=
|
||||
|
|
@ -200,6 +202,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
|||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fergusstrange/embedded-postgres v1.29.0 h1:Uv8hdhoiaNMuH0w8UuGXDHr60VoAQPFdgx7Qf3bzXJM=
|
||||
|
|
@ -213,8 +217,8 @@ github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09
|
|||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.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/ldk-node-go v0.0.0-20250503035148-4f935f853d83 h1:eOkG4g/8IFSK7zyNDr9X10yxBsqCgVfU2WDd70RLfBw=
|
||||
|
|
@ -250,8 +254,8 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1
|
|||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
||||
|
|
@ -646,8 +650,8 @@ github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
|||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
|
|
@ -701,6 +705,12 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
|||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 h1:CTcw80hz/Sw8hqlKX5ZYvBUF5gAHSHwdjXxRf/cjDcI=
|
||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:GXBJykxW2kUcktGdsgyay7uwwWvkljASfljNcT0mbh8=
|
||||
github.com/vulpemventures/go-elements v0.5.5 h1:oN76qcussRvVn8jkwaCI9+thmP9UeXwrzghAoWFzLAg=
|
||||
github.com/vulpemventures/go-elements v0.5.5/go.mod h1:Tvhb+rZWv3lxoI5CdK03J3V+e2QVr/7UAnCYILxFSq4=
|
||||
github.com/vulpemventures/go-secp256k1-zkp v1.1.6 h1:BmsrmXRLUibwa75Qkk8yELjpzCzlAjYFGLiLiOdq7Xo=
|
||||
github.com/vulpemventures/go-secp256k1-zkp v1.1.6/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
|
||||
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
|
||||
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
|
|
@ -819,8 +829,8 @@ golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZP
|
|||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
|
|
@ -862,8 +872,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
|
|
@ -962,8 +972,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
|||
|
|
@ -158,6 +158,9 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
|
|||
restrictedApiGroup.GET("/health", httpSvc.healthHandler)
|
||||
restrictedApiGroup.GET("/commands", httpSvc.getCustomNodeCommandsHandler)
|
||||
restrictedApiGroup.POST("/command", httpSvc.execCustomNodeCommandHandler)
|
||||
restrictedApiGroup.GET("/settings/swaps", httpSvc.getAutoSwapsConfigHandler)
|
||||
restrictedApiGroup.POST("/settings/swaps", httpSvc.enableAutoSwapsHandler)
|
||||
restrictedApiGroup.DELETE("/settings/swaps", httpSvc.disableAutoSwapsHandler)
|
||||
|
||||
httpSvc.albyHttpSvc.RegisterSharedRoutes(restrictedApiGroup, e)
|
||||
}
|
||||
|
|
@ -1155,3 +1158,44 @@ func (httpSvc *HttpService) healthHandler(c echo.Context) error {
|
|||
|
||||
return c.JSON(http.StatusOK, healthResponse)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) getAutoSwapsConfigHandler(c echo.Context) error {
|
||||
getAutoSwapsConfigResponse, err := httpSvc.api.GetAutoSwapsConfig()
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: fmt.Sprintf("Failed to get swap settings: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, getAutoSwapsConfigResponse)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) enableAutoSwapsHandler(c echo.Context) error {
|
||||
var enableAutoSwapsRequest api.EnableAutoSwapsRequest
|
||||
if err := c.Bind(&enableAutoSwapsRequest); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, ErrorResponse{
|
||||
Message: fmt.Sprintf("Bad request: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
err := httpSvc.api.EnableAutoSwaps(c.Request().Context(), &enableAutoSwapsRequest)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: fmt.Sprintf("Failed to save swap settings: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) disableAutoSwapsHandler(c echo.Context) error {
|
||||
err := httpSvc.api.DisableAutoSwaps()
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func (cs *CashuService) Shutdown() error {
|
|||
return cs.wallet.Shutdown()
|
||||
}
|
||||
|
||||
func (cs *CashuService) SendPaymentSync(ctx context.Context, invoice string, amount *uint64) (response *lnclient.PayInvoiceResponse, err error) {
|
||||
func (cs *CashuService) SendPaymentSync(ctx context.Context, invoice string, amount *uint64, timeoutSeconds *int64) (response *lnclient.PayInvoiceResponse, err error) {
|
||||
// TODO: support 0-amount invoices
|
||||
if amount != nil {
|
||||
return nil, errors.New("0-amount invoices not supported")
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/logger"
|
||||
|
|
@ -435,7 +436,12 @@ func getMaxTotalRoutingFeeLimit(amountMsat uint64) ldk_node.MaxTotalRoutingFeeLi
|
|||
}
|
||||
}
|
||||
|
||||
func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
|
||||
func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string, amount *uint64, timeoutSeconds *int64) (*lnclient.PayInvoiceResponse, error) {
|
||||
sendPaymentTimeout := int64(constants.SEND_PAYMENT_TIMEOUT)
|
||||
if timeoutSeconds != nil {
|
||||
sendPaymentTimeout = *timeoutSeconds
|
||||
}
|
||||
|
||||
paymentRequest, err := decodepay.Decodepay(invoice)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
|
|
@ -485,59 +491,67 @@ func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string, amoun
|
|||
fee := uint64(0)
|
||||
preimage := ""
|
||||
|
||||
for start := time.Now(); time.Since(start) < time.Second*50; {
|
||||
event := <-ldkEventSubscription
|
||||
timeout := time.Second * time.Duration(sendPaymentTimeout)
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
eventPaymentSuccessful, isEventPaymentSuccessfulEvent := (*event).(ldk_node.EventPaymentSuccessful)
|
||||
eventPaymentFailed, isEventPaymentFailedEvent := (*event).(ldk_node.EventPaymentFailed)
|
||||
|
||||
if isEventPaymentSuccessfulEvent && eventPaymentSuccessful.PaymentHash == paymentHash {
|
||||
logger.Logger.Info("Got payment success event")
|
||||
payment := ls.node.Payment(paymentHash)
|
||||
if payment == nil {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("Couldn't find payment by payment hash")
|
||||
return nil, errors.New("payment not found")
|
||||
}
|
||||
|
||||
if eventPaymentSuccessful.PaymentPreimage == nil {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("No payment preimage in payment success event")
|
||||
return nil, errors.New("payment preimage not found")
|
||||
}
|
||||
|
||||
preimage = *eventPaymentSuccessful.PaymentPreimage
|
||||
|
||||
if eventPaymentSuccessful.FeePaidMsat != nil {
|
||||
fee = *eventPaymentSuccessful.FeePaidMsat
|
||||
}
|
||||
break
|
||||
}
|
||||
if isEventPaymentFailedEvent && eventPaymentFailed.PaymentHash != nil && *eventPaymentFailed.PaymentHash == paymentHash {
|
||||
failureReasonMessage := ls.getPaymentFailReason(&eventPaymentFailed)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
|
||||
case <-timer.C:
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
"reason": failureReasonMessage,
|
||||
}).Error("Received payment failed event")
|
||||
"paymentHash": paymentHash,
|
||||
}).Warn("Timed out waiting for payment to be sent")
|
||||
return nil, lnclient.NewTimeoutError()
|
||||
|
||||
return nil, fmt.Errorf("received payment failed event: %s", failureReasonMessage)
|
||||
case ev := <-ldkEventSubscription:
|
||||
switch event := (*ev).(type) {
|
||||
case ldk_node.EventPaymentSuccessful:
|
||||
if event.PaymentHash != paymentHash {
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Logger.Info("Got payment success event")
|
||||
payment := ls.node.Payment(paymentHash)
|
||||
if payment == nil {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("Couldn't find payment by payment hash")
|
||||
return nil, errors.New("payment not found")
|
||||
}
|
||||
|
||||
if event.PaymentPreimage == nil {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("No payment preimage in payment success event")
|
||||
return nil, errors.New("payment preimage not found")
|
||||
}
|
||||
|
||||
preimage = *event.PaymentPreimage
|
||||
|
||||
if event.FeePaidMsat != nil {
|
||||
fee = *event.FeePaidMsat
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"duration": time.Since(paymentStart).Milliseconds(),
|
||||
"fee": fee,
|
||||
}).Info("Successful payment")
|
||||
|
||||
return &lnclient.PayInvoiceResponse{
|
||||
Preimage: preimage,
|
||||
Fee: fee,
|
||||
}, nil
|
||||
case ldk_node.EventPaymentFailed:
|
||||
if event.PaymentHash != nil && *event.PaymentHash == paymentHash {
|
||||
failureReasonMessage := ls.getPaymentFailReason(&event)
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
"reason": failureReasonMessage,
|
||||
}).Error("Received payment failed event")
|
||||
return nil, fmt.Errorf("received payment failed event: %s", failureReasonMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if preimage == "" {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
}).Warn("Timed out waiting for payment to be sent")
|
||||
return nil, lnclient.NewTimeoutError()
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"duration": time.Since(paymentStart).Milliseconds(),
|
||||
"fee": fee,
|
||||
}).Info("Successful payment")
|
||||
|
||||
return &lnclient.PayInvoiceResponse{
|
||||
Preimage: preimage,
|
||||
Fee: fee,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ls *LDKService) SendKeysend(ctx context.Context, amount uint64, destination string, custom_records []lnclient.TLVRecord, preimage string) (*lnclient.PayKeysendResponse, error) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/lnclient/lnd/wrapper"
|
||||
|
|
@ -284,9 +285,14 @@ func (svc *LNDService) Shutdown() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
|
||||
func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64, timeoutSeconds *int64) (*lnclient.PayInvoiceResponse, error) {
|
||||
const MAX_PARTIAL_PAYMENTS = 16
|
||||
const SEND_PAYMENT_TIMEOUT = 50
|
||||
|
||||
sendPaymentTimeout := int64(constants.SEND_PAYMENT_TIMEOUT)
|
||||
if timeoutSeconds != nil {
|
||||
sendPaymentTimeout = *timeoutSeconds
|
||||
}
|
||||
|
||||
paymentRequest, err := decodepay.Decodepay(payReq)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
|
|
@ -302,7 +308,7 @@ func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string, amoun
|
|||
sendRequest := &routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: payReq,
|
||||
MaxParts: MAX_PARTIAL_PAYMENTS,
|
||||
TimeoutSeconds: SEND_PAYMENT_TIMEOUT,
|
||||
TimeoutSeconds: int32(sendPaymentTimeout),
|
||||
FeeLimitMsat: int64(transactions.CalculateFeeReserveMsat(paymentAmountMsat)),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ type NodeConnectionInfo struct {
|
|||
}
|
||||
|
||||
type LNClient interface {
|
||||
SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*PayInvoiceResponse, error)
|
||||
SendPaymentSync(ctx context.Context, payReq string, amount *uint64, timeoutSeconds *int64) (*PayInvoiceResponse, error)
|
||||
SendKeysend(ctx context.Context, amount uint64, destination string, customRecords []TLVRecord, preimage string) (*PayKeysendResponse, error)
|
||||
GetPubkey() string
|
||||
GetInfo(ctx context.Context) (info *NodeInfo, err error)
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string
|
|||
return transaction, nil
|
||||
}
|
||||
|
||||
func (svc *PhoenixService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
|
||||
func (svc *PhoenixService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64, timeoutSeconds *int64) (*lnclient.PayInvoiceResponse, error) {
|
||||
// TODO: support 0-amount invoices
|
||||
if amount != nil {
|
||||
return nil, errors.New("0-amount invoices not supported")
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func (controller *nip47Controller) pay(ctx context.Context, bolt11 string, amoun
|
|||
"bolt11": bolt11,
|
||||
}).Info("Sending payment")
|
||||
|
||||
transaction, err := controller.transactionsService.SendPaymentSync(ctx, bolt11, amount, metadata, controller.lnClient, &app.ID, &requestEventId)
|
||||
transaction, err := controller.transactionsService.SendPaymentSync(ctx, bolt11, amount, metadata, controller.lnClient, &app.ID, &requestEventId, nil)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"request_event_id": requestEventId,
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ import (
|
|||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/service/keys"
|
||||
"github.com/getAlby/hub/swaps"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
StartApp(encryptionKey string) error
|
||||
StopApp()
|
||||
StartAutoSwaps() error
|
||||
Shutdown()
|
||||
|
||||
// TODO: remove getters (currently used by http / wails services)
|
||||
|
|
@ -21,6 +23,7 @@ type Service interface {
|
|||
GetEventPublisher() events.EventPublisher
|
||||
GetLNClient() lnclient.LNClient
|
||||
GetTransactionsService() transactions.TransactionsService
|
||||
GetSwapsService() swaps.SwapsService
|
||||
GetDB() *gorm.DB
|
||||
GetConfig() config.Config
|
||||
GetKeys() keys.Keys
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/service/keys"
|
||||
"github.com/getAlby/hub/swaps"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
"github.com/getAlby/hub/version"
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ type service struct {
|
|||
db *gorm.DB
|
||||
lnClient lnclient.LNClient
|
||||
transactionsService transactions.TransactionsService
|
||||
swapsService swaps.SwapsService
|
||||
albyOAuthSvc alby.AlbyOAuthService
|
||||
eventPublisher events.EventPublisher
|
||||
ctx context.Context
|
||||
|
|
@ -115,6 +117,8 @@ func NewService(ctx context.Context) (*service, error) {
|
|||
|
||||
albyOAuthSvc := alby.NewAlbyOAuthService(gormDB, cfg, keys, eventPublisher)
|
||||
|
||||
transactionsSvc := transactions.NewTransactionsService(gormDB, eventPublisher)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
svc := &service{
|
||||
cfg: cfg,
|
||||
|
|
@ -123,7 +127,8 @@ func NewService(ctx context.Context) (*service, error) {
|
|||
eventPublisher: eventPublisher,
|
||||
albyOAuthSvc: albyOAuthSvc,
|
||||
nip47Service: nip47.NewNip47Service(gormDB, cfg, keys, eventPublisher, albyOAuthSvc),
|
||||
transactionsService: transactions.NewTransactionsService(gormDB, eventPublisher),
|
||||
transactionsService: transactionsSvc,
|
||||
swapsService: swaps.NewSwapsService(cfg, eventPublisher, transactionsSvc),
|
||||
db: gormDB,
|
||||
keys: keys,
|
||||
}
|
||||
|
|
@ -250,6 +255,10 @@ func (svc *service) GetTransactionsService() transactions.TransactionsService {
|
|||
return svc.transactionsService
|
||||
}
|
||||
|
||||
func (svc *service) GetSwapsService() swaps.SwapsService {
|
||||
return svc.swapsService
|
||||
}
|
||||
|
||||
func (svc *service) GetKeys() keys.Keys {
|
||||
return svc.keys
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,6 +286,11 @@ func (svc *service) StartApp(encryptionKey string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = svc.StartAutoSwaps()
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Couldn't enable auto swaps")
|
||||
}
|
||||
|
||||
svc.publishAllAppInfoEvents()
|
||||
|
||||
svc.startupState = "Connecting To Relay"
|
||||
|
|
@ -300,6 +305,10 @@ func (svc *service) StartApp(encryptionKey string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (svc *service) StartAutoSwaps() error {
|
||||
return svc.GetSwapsService().EnableAutoSwaps(svc.ctx, svc.lnClient)
|
||||
}
|
||||
|
||||
func (svc *service) launchLNBackend(ctx context.Context, encryptionKey string) error {
|
||||
if svc.lnClient != nil {
|
||||
logger.Logger.Error("LNClient already started")
|
||||
|
|
@ -343,7 +352,7 @@ func (svc *service) launchLNBackend(ctx context.Context, encryptionKey string) e
|
|||
setStartupState := func(startupState string) {
|
||||
svc.startupState = startupState
|
||||
}
|
||||
lnClient, err = ldk.NewLDKService(ctx, svc.cfg, svc.eventPublisher, mnemonic, ldkWorkdir, svc.cfg.GetEnv().LDKNetwork, vssToken, setStartupState)
|
||||
lnClient, err = ldk.NewLDKService(ctx, svc.cfg, svc.eventPublisher, mnemonic, ldkWorkdir, svc.cfg.GetNetwork(), vssToken, setStartupState)
|
||||
case config.PhoenixBackendType:
|
||||
PhoenixdAddress, _ := svc.cfg.Get("PhoenixdAddress", encryptionKey)
|
||||
PhoenixdAuthorization, _ := svc.cfg.Get("PhoenixdAuthorization", encryptionKey)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ func (svc *service) StopApp() {
|
|||
if svc.appCancelFn != nil {
|
||||
logger.Logger.Info("Stopping app...")
|
||||
svc.appCancelFn()
|
||||
svc.swapsService.StopAutoSwaps()
|
||||
svc.wg.Wait()
|
||||
logger.Logger.Info("app stopped")
|
||||
}
|
||||
|
|
|
|||
415
swaps/swaps_service.go
Normal file
415
swaps/swaps_service.go
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
package swaps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/BoltzExchange/boltz-client/v2/pkg/boltz"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type swapsService struct {
|
||||
cancelFn context.CancelFunc
|
||||
cfg config.Config
|
||||
eventPublisher events.EventPublisher
|
||||
transactionsService transactions.TransactionsService
|
||||
boltzApi *boltz.Api
|
||||
}
|
||||
|
||||
type SwapsService interface {
|
||||
EnableAutoSwaps(ctx context.Context, lnClient lnclient.LNClient) error
|
||||
StopAutoSwaps()
|
||||
CalculateFee() (*SwapFees, error)
|
||||
ReverseSwap(ctx context.Context, amount uint64, destination string, lnClient lnclient.LNClient) error
|
||||
}
|
||||
|
||||
const (
|
||||
AlbySwapServiceFee = 1.0
|
||||
)
|
||||
|
||||
type FeeRates struct {
|
||||
FastestFee uint64 `json:"fastestFee"`
|
||||
HalfHourFee uint64 `json:"halfHourFee"`
|
||||
HourFee uint64 `json:"hourFee"`
|
||||
EconomyFee uint64 `json:"economyFee"`
|
||||
MinimumFee uint64 `json:"minimumFee"`
|
||||
}
|
||||
|
||||
type SwapFees struct {
|
||||
AlbyServiceFee float64 `json:"albyServiceFee"`
|
||||
BoltzServiceFee float64 `json:"boltzServiceFee"`
|
||||
BoltzNetworkFee uint64 `json:"boltzNetworkFee"`
|
||||
}
|
||||
|
||||
func NewSwapsService(cfg config.Config, eventPublisher events.EventPublisher, transactionsService transactions.TransactionsService) *swapsService {
|
||||
return &swapsService{
|
||||
cfg: cfg,
|
||||
eventPublisher: eventPublisher,
|
||||
transactionsService: transactionsService,
|
||||
boltzApi: &boltz.Api{URL: cfg.GetEnv().BoltzApi},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *swapsService) EnableAutoSwaps(ctx context.Context, lnClient lnclient.LNClient) error {
|
||||
// stop any existing swap process
|
||||
svc.StopAutoSwaps()
|
||||
|
||||
ctx, cancelFn := context.WithCancel(ctx)
|
||||
swapDestination, _ := svc.cfg.Get(config.AutoSwapDestinationKey, "")
|
||||
balanceThresholdStr, _ := svc.cfg.Get(config.AutoSwapBalanceThresholdKey, "")
|
||||
amountStr, _ := svc.cfg.Get(config.AutoSwapAmountKey, "")
|
||||
|
||||
if swapDestination == "" || balanceThresholdStr == "" || amountStr == "" {
|
||||
cancelFn()
|
||||
return errors.New("auto swap not configured")
|
||||
}
|
||||
|
||||
parsedBalanceThreshold, err := strconv.ParseUint(balanceThresholdStr, 10, 64)
|
||||
if err != nil {
|
||||
cancelFn()
|
||||
return errors.New("invalid auto swap configuration")
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseUint(amountStr, 10, 64)
|
||||
if err != nil {
|
||||
cancelFn()
|
||||
return errors.New("invalid auto swap configuration")
|
||||
}
|
||||
|
||||
logger.Logger.Info("Starting auto swap workflow")
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
logger.Logger.Debug("Checking to see if we can swap")
|
||||
balance, err := lnClient.GetBalances(ctx, false)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to get balance")
|
||||
return
|
||||
}
|
||||
lightningBalance := uint64(balance.Lightning.TotalSpendable)
|
||||
balanceThresholdMilliSats := parsedBalanceThreshold * 1000
|
||||
if lightningBalance >= balanceThresholdMilliSats {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"amount": amount,
|
||||
"destination": swapDestination,
|
||||
}).Info("Initiating swap")
|
||||
err := svc.ReverseSwap(ctx, amount, swapDestination, lnClient)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to swap")
|
||||
}
|
||||
} else {
|
||||
logger.Logger.Info("Threshold requirements not met for swap, ignoring")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
logger.Logger.Info("Stopping auto swap workflow")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
svc.cancelFn = cancelFn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *swapsService) StopAutoSwaps() {
|
||||
if svc.cancelFn != nil {
|
||||
logger.Logger.Info("Stopping swap service...")
|
||||
svc.cancelFn()
|
||||
logger.Logger.Info("swap service stopped")
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *swapsService) ReverseSwap(ctx context.Context, amount uint64, destination string, lnClient lnclient.LNClient) error {
|
||||
var network, err = boltz.ParseChain(svc.cfg.GetNetwork())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ourKeys, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preimage := make([]byte, 32)
|
||||
_, err = rand.Read(preimage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
preimageHash := sha256.Sum256(preimage)
|
||||
|
||||
reversePairs, err := svc.boltzApi.GetReversePairs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get reverse pairs: %s", err)
|
||||
}
|
||||
|
||||
pair := boltz.Pair{From: boltz.CurrencyBtc, To: boltz.CurrencyBtc}
|
||||
pairInfo, err := boltz.FindPair(pair, reversePairs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find reverse pair: %s", err)
|
||||
}
|
||||
|
||||
fees := pairInfo.Fees
|
||||
serviceFeePercentage := boltz.Percentage(fees.Percentage)
|
||||
|
||||
serviceFee := boltz.CalculatePercentage(serviceFeePercentage, amount)
|
||||
networkFee := fees.MinerFees.Lockup + fees.MinerFees.Claim
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"serviceFee": serviceFee,
|
||||
"networkFee": networkFee,
|
||||
}).Info("Calculated fees for swap")
|
||||
|
||||
albyFee := &boltz.ExtraFees{
|
||||
Percentage: AlbySwapServiceFee,
|
||||
Id: "albyServiceFee",
|
||||
}
|
||||
|
||||
swap, err := svc.boltzApi.CreateReverseSwap(boltz.CreateReverseSwapRequest{
|
||||
From: boltz.CurrencyBtc,
|
||||
To: boltz.CurrencyBtc,
|
||||
ClaimPublicKey: ourKeys.PubKey().SerializeCompressed(),
|
||||
PreimageHash: preimageHash[:],
|
||||
InvoiceAmount: amount,
|
||||
Description: "Boltz swap invoice",
|
||||
PairHash: pairInfo.Hash,
|
||||
ReferralId: "alby",
|
||||
ExtraFees: albyFee,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create swap: %s", err)
|
||||
}
|
||||
|
||||
boltzPubKey, err := btcec.ParsePubKey(swap.RefundPublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree := swap.SwapTree.Deserialize()
|
||||
if err := tree.Init(boltz.CurrencyBtc, true, ourKeys, boltzPubKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tree.Check(boltz.ReverseSwap, swap.TimeoutBlockHeight, preimageHash[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Logger.WithField("swap", swap).Info("Swap created")
|
||||
|
||||
boltzWs := svc.boltzApi.NewWebsocket()
|
||||
if err := boltzWs.Connect(); err != nil {
|
||||
return fmt.Errorf("could not connect to Boltz websocket: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := boltzWs.Close(); err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to close boltz websocket")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := boltzWs.Subscribe([]string{swap.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paymentErrorCh := make(chan error, 1)
|
||||
updatesCh := boltzWs.Updates
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-paymentErrorCh:
|
||||
return err
|
||||
case update, ok := <-updatesCh:
|
||||
if !ok {
|
||||
return errors.New("boltz websocket closed unexpectedly")
|
||||
}
|
||||
parsedStatus := boltz.ParseEvent(update.Status)
|
||||
|
||||
switch parsedStatus {
|
||||
case boltz.SwapCreated:
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"swap": swap,
|
||||
"update": update,
|
||||
}).Info("Paying the swap invoice")
|
||||
err := lnClient.SendPaymentProbes(ctx, swap.Invoice)
|
||||
if err != nil {
|
||||
logger.Logger.WithField("swapId", swap.Id).Info("Couldn't probe invoice payment, terminating swap")
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
metadata := map[string]interface{}{
|
||||
"swapId": swap.Id,
|
||||
"onchainAmount": swap.OnchainAmount,
|
||||
"refundPubkey": swap.RefundPublicKey,
|
||||
}
|
||||
sendPaymentTimeout := int64(3600)
|
||||
_, err := svc.transactionsService.SendPaymentSync(ctx, swap.Invoice, nil, metadata, lnClient, nil, nil, &sendPaymentTimeout)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"swap": swap,
|
||||
"update": update,
|
||||
}).Error("Error paying the swap invoice")
|
||||
paymentErrorCh <- err
|
||||
return
|
||||
}
|
||||
logger.Logger.WithField("swapId", swap.Id).Info("Initiated swap invoice payment")
|
||||
}()
|
||||
case boltz.TransactionMempool:
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"swapId": swap.Id,
|
||||
"transaction": update.Transaction,
|
||||
}).Info("Lockup transaction found in mempool")
|
||||
case boltz.TransactionConfirmed:
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"swapId": swap.Id,
|
||||
"transaction": update.Transaction,
|
||||
}).Info("Lockup transaction confirmed in mempool")
|
||||
lockupTransaction, err := boltz.NewTxFromHex(boltz.CurrencyBtc, update.Transaction.Hex, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vout, _, err := lockupTransaction.FindVout(network, swap.LockupAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
feeRates, err := svc.getFeeRates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
claimTransaction, _, err := boltz.ConstructTransaction(
|
||||
network,
|
||||
boltz.CurrencyBtc,
|
||||
[]boltz.OutputDetails{
|
||||
{
|
||||
SwapId: swap.Id,
|
||||
SwapType: boltz.ReverseSwap,
|
||||
Address: destination,
|
||||
LockupTransaction: lockupTransaction,
|
||||
Vout: vout,
|
||||
Preimage: preimage,
|
||||
PrivateKey: ourKeys,
|
||||
SwapTree: tree,
|
||||
Cooperative: true,
|
||||
},
|
||||
},
|
||||
float64(feeRates.FastestFee),
|
||||
svc.boltzApi,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create claim transaction: %w", err)
|
||||
}
|
||||
|
||||
txHex, err := claimTransaction.Serialize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not serialize claim transaction: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Replace with LNClient method
|
||||
txId, err := svc.boltzApi.BroadcastTransaction(boltz.CurrencyBtc, txHex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not broadcast transaction: %w", err)
|
||||
}
|
||||
|
||||
logger.Logger.WithField("txId", txId).Info("Transaction broadcasted")
|
||||
case boltz.InvoiceSettled:
|
||||
logger.Logger.WithField("swapId", swap.Id).Info("Swap succeeded")
|
||||
svc.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_swap_succeeded",
|
||||
Properties: map[string]interface{}{
|
||||
"swapId": swap.Id,
|
||||
"invoice": swap.Invoice,
|
||||
"onchainAmount": swap.OnchainAmount,
|
||||
"refundPubkey": swap.RefundPublicKey,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *swapsService) CalculateFee() (*SwapFees, error) {
|
||||
reversePairs, err := svc.boltzApi.GetReversePairs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get reverse pairs: %s", err)
|
||||
}
|
||||
|
||||
pair := boltz.Pair{From: boltz.CurrencyBtc, To: boltz.CurrencyBtc}
|
||||
pairInfo, err := boltz.FindPair(pair, reversePairs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find reverse pair: %s", err)
|
||||
}
|
||||
|
||||
fees := pairInfo.Fees
|
||||
networkFee := fees.MinerFees.Lockup + fees.MinerFees.Claim
|
||||
|
||||
return &SwapFees{
|
||||
AlbyServiceFee: AlbySwapServiceFee,
|
||||
BoltzServiceFee: fees.Percentage,
|
||||
BoltzNetworkFee: networkFee,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *swapsService) getFeeRates() (*FeeRates, error) {
|
||||
url := svc.cfg.GetEnv().MempoolApi + "/v1/fees/recommended"
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 10,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"url": url,
|
||||
}).Error("Failed to create http request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"url": url,
|
||||
}).Error("Failed to send request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
body, readErr := io.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"url": url,
|
||||
}).Error("Failed to read response body")
|
||||
return nil, errors.New("failed to read response body")
|
||||
}
|
||||
|
||||
var rates FeeRates
|
||||
jsonErr := json.Unmarshal(body, &rates)
|
||||
if jsonErr != nil {
|
||||
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
||||
"url": url,
|
||||
}).Error("Failed to deserialize json")
|
||||
return nil, fmt.Errorf("failed to deserialize json %s %s", url, string(body))
|
||||
}
|
||||
return &rates, nil
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ func NewMockLn() (*MockLn, error) {
|
|||
return &MockLn{}, nil
|
||||
}
|
||||
|
||||
func (mln *MockLn) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
|
||||
func (mln *MockLn) SendPaymentSync(ctx context.Context, payReq string, amount *uint64, timeoutSeconds *int64) (*lnclient.PayInvoiceResponse, error) {
|
||||
if len(mln.PayInvoiceResponses) > 0 {
|
||||
response := mln.PayInvoiceResponses[0]
|
||||
err := mln.PayInvoiceErrors[0]
|
||||
|
|
|
|||
|
|
@ -307,6 +307,51 @@ func (_c *MockConfig_GetJWTSecret_Call) RunAndReturn(run func() string) *MockCon
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetNetwork provides a mock function with no fields
|
||||
func (_m *MockConfig) GetNetwork() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetNetwork")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockConfig_GetNetwork_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNetwork'
|
||||
type MockConfig_GetNetwork_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetNetwork is a helper method to define mock.On call
|
||||
func (_e *MockConfig_Expecter) GetNetwork() *MockConfig_GetNetwork_Call {
|
||||
return &MockConfig_GetNetwork_Call{Call: _e.mock.On("GetNetwork")}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetNetwork_Call) Run(run func()) *MockConfig_GetNetwork_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetNetwork_Call) Return(_a0 string) *MockConfig_GetNetwork_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetNetwork_Call) RunAndReturn(run func() string) *MockConfig_GetNetwork_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetRelayUrl provides a mock function with no fields
|
||||
func (_m *MockConfig) GetRelayUrl() string {
|
||||
ret := _m.Called()
|
||||
|
|
|
|||
|
|
@ -1572,9 +1572,9 @@ func (_c *MockLNClient_SendPaymentProbes_Call) RunAndReturn(run func(context.Con
|
|||
return _c
|
||||
}
|
||||
|
||||
// SendPaymentSync provides a mock function with given fields: ctx, payReq, amount
|
||||
func (_m *MockLNClient) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
|
||||
ret := _m.Called(ctx, payReq, amount)
|
||||
// SendPaymentSync provides a mock function with given fields: ctx, payReq, amount, timeoutSeconds
|
||||
func (_m *MockLNClient) SendPaymentSync(ctx context.Context, payReq string, amount *uint64, timeoutSeconds *int64) (*lnclient.PayInvoiceResponse, error) {
|
||||
ret := _m.Called(ctx, payReq, amount, timeoutSeconds)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendPaymentSync")
|
||||
|
|
@ -1582,19 +1582,19 @@ func (_m *MockLNClient) SendPaymentSync(ctx context.Context, payReq string, amou
|
|||
|
||||
var r0 *lnclient.PayInvoiceResponse
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *uint64) (*lnclient.PayInvoiceResponse, error)); ok {
|
||||
return rf(ctx, payReq, amount)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *uint64, *int64) (*lnclient.PayInvoiceResponse, error)); ok {
|
||||
return rf(ctx, payReq, amount, timeoutSeconds)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *uint64) *lnclient.PayInvoiceResponse); ok {
|
||||
r0 = rf(ctx, payReq, amount)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *uint64, *int64) *lnclient.PayInvoiceResponse); ok {
|
||||
r0 = rf(ctx, payReq, amount, timeoutSeconds)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*lnclient.PayInvoiceResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, *uint64) error); ok {
|
||||
r1 = rf(ctx, payReq, amount)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, *uint64, *int64) error); ok {
|
||||
r1 = rf(ctx, payReq, amount, timeoutSeconds)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
|
@ -1611,13 +1611,14 @@ type MockLNClient_SendPaymentSync_Call struct {
|
|||
// - ctx context.Context
|
||||
// - payReq string
|
||||
// - amount *uint64
|
||||
func (_e *MockLNClient_Expecter) SendPaymentSync(ctx interface{}, payReq interface{}, amount interface{}) *MockLNClient_SendPaymentSync_Call {
|
||||
return &MockLNClient_SendPaymentSync_Call{Call: _e.mock.On("SendPaymentSync", ctx, payReq, amount)}
|
||||
// - timeoutSeconds *int64
|
||||
func (_e *MockLNClient_Expecter) SendPaymentSync(ctx interface{}, payReq interface{}, amount interface{}, timeoutSeconds interface{}) *MockLNClient_SendPaymentSync_Call {
|
||||
return &MockLNClient_SendPaymentSync_Call{Call: _e.mock.On("SendPaymentSync", ctx, payReq, amount, timeoutSeconds)}
|
||||
}
|
||||
|
||||
func (_c *MockLNClient_SendPaymentSync_Call) Run(run func(ctx context.Context, payReq string, amount *uint64)) *MockLNClient_SendPaymentSync_Call {
|
||||
func (_c *MockLNClient_SendPaymentSync_Call) Run(run func(ctx context.Context, payReq string, amount *uint64, timeoutSeconds *int64)) *MockLNClient_SendPaymentSync_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(*uint64))
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(*uint64), args[3].(*int64))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -1627,7 +1628,7 @@ func (_c *MockLNClient_SendPaymentSync_Call) Return(_a0 *lnclient.PayInvoiceResp
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockLNClient_SendPaymentSync_Call) RunAndReturn(run func(context.Context, string, *uint64) (*lnclient.PayInvoiceResponse, error)) *MockLNClient_SendPaymentSync_Call {
|
||||
func (_c *MockLNClient_SendPaymentSync_Call) RunAndReturn(run func(context.Context, string, *uint64, *int64) (*lnclient.PayInvoiceResponse, error)) *MockLNClient_SendPaymentSync_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import (
|
|||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
swaps "github.com/getAlby/hub/swaps"
|
||||
|
||||
transactions "github.com/getAlby/hub/transactions"
|
||||
)
|
||||
|
||||
|
|
@ -359,6 +361,53 @@ func (_c *MockService_GetStartupState_Call) RunAndReturn(run func() string) *Moc
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetSwapsService provides a mock function with no fields
|
||||
func (_m *MockService) GetSwapsService() swaps.SwapsService {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSwapsService")
|
||||
}
|
||||
|
||||
var r0 swaps.SwapsService
|
||||
if rf, ok := ret.Get(0).(func() swaps.SwapsService); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(swaps.SwapsService)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockService_GetSwapsService_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSwapsService'
|
||||
type MockService_GetSwapsService_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetSwapsService is a helper method to define mock.On call
|
||||
func (_e *MockService_Expecter) GetSwapsService() *MockService_GetSwapsService_Call {
|
||||
return &MockService_GetSwapsService_Call{Call: _e.mock.On("GetSwapsService")}
|
||||
}
|
||||
|
||||
func (_c *MockService_GetSwapsService_Call) Run(run func()) *MockService_GetSwapsService_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetSwapsService_Call) Return(_a0 swaps.SwapsService) *MockService_GetSwapsService_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetSwapsService_Call) RunAndReturn(run func() swaps.SwapsService) *MockService_GetSwapsService_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetTransactionsService provides a mock function with no fields
|
||||
func (_m *MockService) GetTransactionsService() transactions.TransactionsService {
|
||||
ret := _m.Called()
|
||||
|
|
@ -529,6 +578,51 @@ func (_c *MockService_StartApp_Call) RunAndReturn(run func(string) error) *MockS
|
|||
return _c
|
||||
}
|
||||
|
||||
// StartAutoSwaps provides a mock function with no fields
|
||||
func (_m *MockService) StartAutoSwaps() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for StartAutoSwaps")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockService_StartAutoSwaps_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartAutoSwaps'
|
||||
type MockService_StartAutoSwaps_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// StartAutoSwaps is a helper method to define mock.On call
|
||||
func (_e *MockService_Expecter) StartAutoSwaps() *MockService_StartAutoSwaps_Call {
|
||||
return &MockService_StartAutoSwaps_Call{Call: _e.mock.On("StartAutoSwaps")}
|
||||
}
|
||||
|
||||
func (_c *MockService_StartAutoSwaps_Call) Run(run func()) *MockService_StartAutoSwaps_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_StartAutoSwaps_Call) Return(_a0 error) *MockService_StartAutoSwaps_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_StartAutoSwaps_Call) RunAndReturn(run func() error) *MockService_StartAutoSwaps_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// StopApp provides a mock function with no fields
|
||||
func (_m *MockService) StopApp() {
|
||||
_m.Called()
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func TestSendPaymentSync_App_NoPermission(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "app does not have pay_invoice scope", err.Error())
|
||||
|
|
@ -57,7 +57,7 @@ func TestSendPaymentSync_App_WithPermission(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -94,7 +94,7 @@ func TestSendPaymentSync_App_BudgetExceeded(t *testing.T) {
|
|||
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewQuotaExceededError())
|
||||
|
|
@ -141,7 +141,7 @@ func TestSendPaymentSync_App_BudgetExceeded_SettledPayment(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewQuotaExceededError())
|
||||
|
|
@ -180,7 +180,7 @@ func TestSendPaymentSync_App_BudgetExceeded_UnsettledPayment(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewQuotaExceededError())
|
||||
|
|
@ -220,7 +220,7 @@ func TestSendPaymentSync_App_BudgetNotExceeded_FailedPayment(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func TestSendPaymentSync_IsolatedApp_NoBalance(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewInsufficientBalanceError())
|
||||
|
|
@ -79,7 +79,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficient(t *testing.T) {
|
|||
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewInsufficientBalanceError())
|
||||
|
|
@ -125,7 +125,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceSufficient(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -174,7 +174,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_OutstandingPayment(t *t
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewInsufficientBalanceError())
|
||||
|
|
@ -220,7 +220,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_SettledPayment(t *testi
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewInsufficientBalanceError())
|
||||
|
|
@ -265,7 +265,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceSufficient_UnrelatedPayment(t *testi
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -313,7 +313,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceSufficient_FailedPayment(t *testing.
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -355,7 +355,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficientThenSufficient(t *testin
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, NewInsufficientBalanceError())
|
||||
|
|
@ -368,7 +368,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficientThenSufficient(t *testin
|
|||
AmountMsat: 10000, // add extra to cover fee reserves max of(10 sats or 1%)
|
||||
})
|
||||
|
||||
transaction, err = transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err = transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func TestSendPaymentSync_NoApp(t *testing.T) {
|
|||
}
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -62,7 +62,7 @@ func TestSendPaymentSync_0Amount(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
amount := uint64(1234)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.Mock0AmountInvoice, &amount, metadata, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.Mock0AmountInvoice, &amount, metadata, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, amount, transaction.AmountMsat)
|
||||
|
|
@ -82,7 +82,7 @@ func TestSendPaymentSync_MetadataTooLarge(t *testing.T) {
|
|||
metadata["randomkey"] = strings.Repeat("a", constants.INVOICE_METADATA_MAX_LENGTH-15) // json encoding adds 16 characters
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, fmt.Sprintf("encoded payment metadata provided is too large. Limit: %d Received: %d", constants.INVOICE_METADATA_MAX_LENGTH, constants.INVOICE_METADATA_MAX_LENGTH+1), err.Error())
|
||||
|
|
@ -104,7 +104,7 @@ func TestSendPaymentSync_Duplicate_AlreadyPaid(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "this invoice has already been paid", err.Error())
|
||||
|
|
@ -126,7 +126,7 @@ func TestSendPaymentSync_Duplicate_Pending(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "there is already a payment pending for this invoice", err.Error())
|
||||
|
|
@ -148,7 +148,7 @@ func TestSendPaymentSync_Duplicate_Failed(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
_, err = transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
|
||||
_, err = transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -308,7 +308,7 @@ func TestSendPaymentSync_FailedRemovesFeeReserve(t *testing.T) {
|
|||
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, transaction)
|
||||
|
|
@ -338,7 +338,7 @@ func TestSendPaymentSync_PendingHasFeeReserve(t *testing.T) {
|
|||
svc.LNClient.(*tests.MockLn).PayInvoiceResponses = append(svc.LNClient.(*tests.MockLn).PayInvoiceResponses, nil)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, transaction)
|
||||
|
|
@ -365,7 +365,7 @@ func TestConsumeEvent_DoesNotMarkFailedAsSuccessful(t *testing.T) {
|
|||
svc.LNClient.(*tests.MockLn).PayInvoiceErrors = append(svc.LNClient.(*tests.MockLn).PayInvoiceErrors, errors.New("some error"))
|
||||
svc.LNClient.(*tests.MockLn).PayInvoiceResponses = append(svc.LNClient.(*tests.MockLn).PayInvoiceResponses, nil)
|
||||
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, transaction)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func TestSendPaymentSync_SelfPayment_NoAppToNoApp(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -86,7 +86,7 @@ func TestSendPaymentSync_SelfPayment_NoAppToIsolatedApp(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -139,7 +139,7 @@ func TestSendPaymentSync_SelfPayment_NoAppToApp(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -208,7 +208,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToNoApp(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -283,7 +283,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToApp(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -365,7 +365,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToIsolatedApp(t *testing.T) {
|
|||
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
@ -457,7 +457,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToSelf(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type TransactionsService interface {
|
|||
MakeInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
LookupTransaction(ctx context.Context, paymentHash string, transactionType *string, lnClient lnclient.LNClient, appId *uint) (*Transaction, error)
|
||||
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool) (transactions []Transaction, totalCount uint64, err error)
|
||||
SendPaymentSync(ctx context.Context, payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
SendPaymentSync(ctx context.Context, payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint, timeoutSeconds *int64) (*Transaction, error)
|
||||
SendKeysend(ctx context.Context, amount uint64, destination string, customRecords []lnclient.TLVRecord, preimage string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ func (svc *transactionsService) MakeInvoice(ctx context.Context, amount uint64,
|
|||
return &dbTransaction, nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) {
|
||||
func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint, timeoutSeconds *int64) (*Transaction, error) {
|
||||
var metadataBytes []byte
|
||||
if metadata != nil {
|
||||
var err error
|
||||
|
|
@ -293,7 +293,7 @@ func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq stri
|
|||
if selfPayment {
|
||||
response, err = svc.interceptSelfPayment(paymentRequest.PaymentHash)
|
||||
} else {
|
||||
response, err = lnClient.SendPaymentSync(ctx, payReq, amountMsat)
|
||||
response, err = lnClient.SendPaymentSync(ctx, payReq, amountMsat, timeoutSeconds)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -976,6 +976,53 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: commandResponse, Error: ""}
|
||||
case "/api/settings/swaps":
|
||||
switch method {
|
||||
case "GET":
|
||||
autoSwapsConfig, err := app.api.GetAutoSwapsConfig()
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"route": route,
|
||||
"method": method,
|
||||
"body": body,
|
||||
}).WithError(err).Error("Failed to get auto swaps configuration")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: autoSwapsConfig, Error: ""}
|
||||
case "POST":
|
||||
enableAutoSwapsRequest := &api.EnableAutoSwapsRequest{}
|
||||
err := json.Unmarshal([]byte(body), enableAutoSwapsRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"route": route,
|
||||
"method": method,
|
||||
"body": body,
|
||||
}).WithError(err).Error("Failed to decode request to wails router")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
|
||||
err = app.api.EnableAutoSwaps(ctx, enableAutoSwapsRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"route": route,
|
||||
"method": method,
|
||||
"body": body,
|
||||
}).WithError(err).Error("Failed to enable swaps")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: nil, Error: ""}
|
||||
case "DELETE":
|
||||
err := app.api.DisableAutoSwaps()
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"route": route,
|
||||
"method": method,
|
||||
"body": body,
|
||||
}).WithError(err).Error("Failed to disable swaps")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: nil, Error: ""}
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(route, "/api/log/") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue