mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: add BOLT-12 offers (#1242)
* feat: add BOLT-12 offers * chore: display incoming bolt 12 payments in tx list * chore: show bolt 12 button for users without alby account * fix: check if bolt12payment fields are nil before assigning * chore: do not pass in expiry * fix: use checkLDKerr for error handling * chore: add wails support * chore: ui improvements * chore: show bolt-12 for LDK backend type only * chore: add offer info to tx metadata * chore: use bolt12Offer * fix: make offer description optional * fix: remove incorrect log line * chore: minor updates to node payBOLT12Offer command * chore: remove unnecessary BOLT-12 offer copy * chore: remove unused quantity * chore: cleanup bolt12Offer usage in TransactionItem * chore: prioritize lightning address, make bolt-12 copy consistent * chore: rename generate offer to make offer * fix: error handling * chore: move payment hash into pay offer response --------- Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
This commit is contained in:
parent
4ab91202b7
commit
c653ae9090
20 changed files with 757 additions and 680 deletions
12
api/api.go
12
api/api.go
|
|
@ -685,6 +685,18 @@ func (api *api) UpdateChannel(ctx context.Context, updateChannelRequest *UpdateC
|
|||
return api.svc.GetLNClient().UpdateChannel(ctx, updateChannelRequest)
|
||||
}
|
||||
|
||||
func (api *api) MakeOffer(ctx context.Context, description string) (string, error) {
|
||||
if api.svc.GetLNClient() == nil {
|
||||
return "", errors.New("LNClient not started")
|
||||
}
|
||||
offer, err := api.svc.GetLNClient().MakeOffer(ctx, description)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return offer, nil
|
||||
}
|
||||
|
||||
func (api *api) GetNewOnchainAddress(ctx context.Context) (string, error) {
|
||||
if api.svc.GetLNClient() == nil {
|
||||
return "", errors.New("LNClient not started")
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ type API interface {
|
|||
OpenChannel(ctx context.Context, openChannelRequest *OpenChannelRequest) (*OpenChannelResponse, error)
|
||||
CloseChannel(ctx context.Context, peerId, channelId string, force bool) (*CloseChannelResponse, error)
|
||||
UpdateChannel(ctx context.Context, updateChannelRequest *UpdateChannelRequest) error
|
||||
MakeOffer(ctx context.Context, description string) (string, error)
|
||||
GetNewOnchainAddress(ctx context.Context) (string, error)
|
||||
GetUnusedOnchainAddress(ctx context.Context) (string, error)
|
||||
SignMessage(ctx context.Context, message string) (*SignMessageResponse, error)
|
||||
|
|
@ -346,6 +347,10 @@ type PayInvoiceRequest struct {
|
|||
Metadata Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type MakeOfferRequest struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type MakeInvoiceRequest struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
|
|
|
|||
|
|
@ -88,7 +88,10 @@ function TransactionItem({ tx }: Props) {
|
|||
|
||||
const eventId = tx.metadata?.nostr?.tags?.find((t) => t[0] === "e")?.[1];
|
||||
|
||||
const description = tx.description || tx.metadata?.comment;
|
||||
const bolt12Offer = tx.metadata?.offer;
|
||||
|
||||
const description =
|
||||
tx.description || tx.metadata?.comment || bolt12Offer?.payer_note;
|
||||
|
||||
const copy = (text: string) => {
|
||||
copyToClipboard(text, toast);
|
||||
|
|
@ -266,7 +269,15 @@ function TransactionItem({ tx }: Props) {
|
|||
<div className="mt-6">
|
||||
<p>Comment</p>
|
||||
<p className="text-muted-foreground break-all">
|
||||
{tx.metadata?.comment}
|
||||
{tx.metadata.comment}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{bolt12Offer?.payer_note && (
|
||||
<div className="mt-6">
|
||||
<p>Payer Note</p>
|
||||
<p className="text-muted-foreground break-all">
|
||||
{bolt12Offer.payer_note}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -304,6 +315,22 @@ function TransactionItem({ tx }: Props) {
|
|||
{showDetails && (
|
||||
<>
|
||||
{tx.boostagram && <PodcastingInfo boost={tx.boostagram} />}
|
||||
{bolt12Offer && (
|
||||
<div className="mt-6">
|
||||
<p>BOLT-12 Offer Id</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<p className="text-muted-foreground break-all">
|
||||
{bolt12Offer.id}
|
||||
</p>
|
||||
<CopyIcon
|
||||
className="cursor-pointer text-muted-foreground w-4 h-4 flex-shrink-0"
|
||||
onClick={() => {
|
||||
copy(bolt12Offer.id as string);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{tx.preimage && (
|
||||
<div className="mt-6">
|
||||
<p>Preimage</p>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default function ReceiveLayout() {
|
|||
title="Receive"
|
||||
contentRight={
|
||||
hasChannelManagement && (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="md:flex md:items-center md:gap-4">
|
||||
<span className="text-muted-foreground">Receive Limit:</span>
|
||||
<div className="balance sensitive slashed-zero">
|
||||
{new Intl.NumberFormat().format(
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ import Send from "src/screens/wallet/Send";
|
|||
import SignMessage from "src/screens/wallet/SignMessage";
|
||||
import WithdrawOnchainFunds from "src/screens/wallet/WithdrawOnchainFunds";
|
||||
import ReceiveInvoice from "src/screens/wallet/receive/ReceiveInvoice";
|
||||
import ReceiveOffer from "src/screens/wallet/receive/ReceiveOffer";
|
||||
import ConfirmPayment from "src/screens/wallet/send/ConfirmPayment";
|
||||
import LnurlPay from "src/screens/wallet/send/LnurlPay";
|
||||
import PaymentSuccess from "src/screens/wallet/send/PaymentSuccess";
|
||||
|
|
@ -127,6 +128,11 @@ const routes = [
|
|||
path: "invoice",
|
||||
element: <ReceiveInvoice />,
|
||||
},
|
||||
{
|
||||
handle: { crumb: () => "BOLT-12 Offer" },
|
||||
path: "offer",
|
||||
element: <ReceiveOffer />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { CopyIcon, PencilIcon } from "lucide-react";
|
||||
import { CopyIcon, PencilIcon, ReceiptTextIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Loading from "src/components/Loading";
|
||||
import QRCode from "src/components/QRCode";
|
||||
import { Button, LinkButton } from "src/components/ui/button";
|
||||
import { Card, CardContent } from "src/components/ui/card";
|
||||
import { useToast } from "src/components/ui/use-toast";
|
||||
import UserAvatar from "src/components/UserAvatar";
|
||||
import { useAlbyMe } from "src/hooks/useAlbyMe";
|
||||
|
|
@ -35,36 +36,54 @@ export default function Receive() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-lg">
|
||||
<div className="w-full md:max-w-lg">
|
||||
<div className="grid gap-5">
|
||||
{info?.albyAccountConnected && me?.lightning_address && (
|
||||
<div className="flex flex-col items-center justify-center gap-6 border rounded-xl w-full md:max-w-xs p-4 md:p-6">
|
||||
<div className="relative flex flex-col items-center justify-center">
|
||||
<QRCode value={me.lightning_address} className="w-full h-auto" />
|
||||
<UserAvatar className="w-14 h-14 absolute border-4 border-white bg-white" />
|
||||
</div>
|
||||
<p className="text-center font-semibold break-all">
|
||||
{me.lightning_address}
|
||||
</p>
|
||||
<div className="flex gap-4 w-full">
|
||||
<LinkButton
|
||||
to="invoice"
|
||||
variant="outline"
|
||||
className="flex-1 flex gap-2 items-center justify-center"
|
||||
>
|
||||
<PencilIcon className="w-4 h-4" /> Amount
|
||||
</LinkButton>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
copyToClipboard(me.lightning_address, toast);
|
||||
}}
|
||||
className="flex-1 flex gap-2 items-center justify-center"
|
||||
>
|
||||
<CopyIcon className="w-4 h-4" /> Copy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Card className="w-full md:max-w-xs">
|
||||
<CardContent className="flex flex-col items-center gap-6 pt-6">
|
||||
<div className="relative flex items-center justify-center">
|
||||
<QRCode
|
||||
value={me.lightning_address}
|
||||
className="w-full h-auto"
|
||||
/>
|
||||
<UserAvatar className="w-14 h-14 absolute border-4 border-white bg-white" />
|
||||
</div>
|
||||
<p className="text-center font-semibold break-all">
|
||||
{me.lightning_address}
|
||||
</p>
|
||||
<div className="flex gap-4 w-full">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
copyToClipboard(me.lightning_address, toast);
|
||||
}}
|
||||
className="flex-1 flex gap-2 items-center justify-center"
|
||||
>
|
||||
<CopyIcon className="w-4 h-4" /> Copy Lightning Address
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 w-full border-t pt-6">
|
||||
<LinkButton
|
||||
to="invoice"
|
||||
variant="outline"
|
||||
className="flex-1 flex gap-2 items-center justify-center"
|
||||
>
|
||||
<PencilIcon className="w-4 h-4" /> Amount
|
||||
</LinkButton>
|
||||
{info.backendType === "LDK" && (
|
||||
<LinkButton
|
||||
to="/wallet/receive/offer"
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
<ReceiptTextIcon className="h-4 w-4 shrink-0 mr-2" />
|
||||
BOLT-12
|
||||
</LinkButton>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { AlertTriangleIcon, CircleCheckIcon, CopyIcon } from "lucide-react";
|
||||
import {
|
||||
AlertTriangleIcon,
|
||||
CircleCheckIcon,
|
||||
CopyIcon,
|
||||
ReceiptTextIcon,
|
||||
} from "lucide-react";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
|
|
@ -99,7 +104,7 @@ export default function ReceiveInvoice() {
|
|||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row gap-6">
|
||||
<div className="w-full md:max-w-xl">
|
||||
<div className="w-full md:max-w-lg">
|
||||
<div className="grid gap-5">
|
||||
{hasChannelManagement &&
|
||||
parseInt(amount || "0") * 1000 >=
|
||||
|
|
@ -117,57 +122,63 @@ export default function ReceiveInvoice() {
|
|||
)}
|
||||
<div>
|
||||
{transaction ? (
|
||||
<div className="flex flex-col items-center justify-center gap-6 border rounded-xl w-full md:max-w-xs p-4 md:p-6">
|
||||
<Card className="w-full md:max-w-xs">
|
||||
{!paymentDone ? (
|
||||
<>
|
||||
<div className="flex flex-row items-center gap-2 font-medium">
|
||||
<Loading className="w-4 h-4" />
|
||||
<p>Waiting for payment</p>
|
||||
</div>
|
||||
<div className="relative flex flex-col items-center justify-center">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex justify-center">
|
||||
<Loading className="w-4 h-4 mr-2" />
|
||||
<p>Waiting for payment</p>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col items-center gap-4">
|
||||
<QRCode value={transaction.invoice} className="w-full" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<p className="text-xl font-semibold slashed-zero">
|
||||
{new Intl.NumberFormat().format(parseInt(amount))} sats
|
||||
</p>
|
||||
<FormattedFiatAmount amount={parseInt(amount)} />
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={copy} variant="outline">
|
||||
<CopyIcon className="w-4 h-4 mr-2" />
|
||||
Copy Invoice
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<p className="text-xl font-semibold slashed-zero">
|
||||
{new Intl.NumberFormat().format(parseInt(amount))}{" "}
|
||||
sats
|
||||
</p>
|
||||
<FormattedFiatAmount amount={parseInt(amount)} />
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={copy} variant="outline">
|
||||
<CopyIcon className="w-4 h-4 mr-2" />
|
||||
Copy Invoice
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-center font-medium">
|
||||
Payment Received!
|
||||
</div>
|
||||
<div className="relative flex flex-col items-center justify-center">
|
||||
<CircleCheckIcon className="w-64 h-64 mb-1" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<p className="text-xl font-semibold slashed-zero">
|
||||
{new Intl.NumberFormat().format(parseInt(amount))} sats
|
||||
</p>
|
||||
<FormattedFiatAmount amount={parseInt(amount)} />
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setPaymentDone(false);
|
||||
setTransaction(null);
|
||||
}}
|
||||
>
|
||||
Receive Another Payment
|
||||
</Button>
|
||||
</div>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center">
|
||||
Payment Received!
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col items-center gap-4">
|
||||
<CircleCheckIcon className="w-72 h-72 p-2" />
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<p className="text-xl font-semibold slashed-zero">
|
||||
{new Intl.NumberFormat().format(parseInt(amount))}{" "}
|
||||
sats
|
||||
</p>
|
||||
<FormattedFiatAmount amount={parseInt(amount)} />
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setPaymentDone(false);
|
||||
setTransaction(null);
|
||||
}}
|
||||
>
|
||||
Receive Another Payment
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="grid gap-5">
|
||||
<div>
|
||||
|
|
@ -197,7 +208,7 @@ export default function ReceiveInvoice() {
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<LoadingButton
|
||||
className="w-full md:w-auto"
|
||||
loading={isLoading}
|
||||
|
|
@ -206,6 +217,15 @@ export default function ReceiveInvoice() {
|
|||
>
|
||||
Create Invoice
|
||||
</LoadingButton>
|
||||
{!info?.albyAccountConnected &&
|
||||
info.backendType === "LDK" && (
|
||||
<Link to="/wallet/receive/offer">
|
||||
<Button variant="outline" className="w-full">
|
||||
<ReceiptTextIcon className="h-4 w-4 shrink-0 mr-2" />
|
||||
BOLT-12 Offer
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
|
|
|||
137
frontend/src/screens/wallet/receive/ReceiveOffer.tsx
Normal file
137
frontend/src/screens/wallet/receive/ReceiveOffer.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { CircleAlertIcon, CopyIcon, RefreshCwIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import QRCode from "src/components/QRCode";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "src/components/ui/alert.tsx";
|
||||
import { Button } from "src/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
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 { useToast } from "src/components/ui/use-toast";
|
||||
|
||||
import { copyToClipboard } from "src/lib/clipboard";
|
||||
import { CreateOfferRequest } from "src/types";
|
||||
import { request } from "src/utils/request";
|
||||
|
||||
export default function ReceiveOffer() {
|
||||
const { toast } = useToast();
|
||||
const [isLoading, setLoading] = React.useState(false);
|
||||
const [description, setDescription] = React.useState<string>("");
|
||||
const [offer, setOffer] = React.useState<string | null>(null);
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const offer = await request<string>("/api/offers", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
description,
|
||||
} as CreateOfferRequest),
|
||||
});
|
||||
|
||||
if (offer) {
|
||||
setOffer(offer);
|
||||
|
||||
toast({
|
||||
title: "Successfully created BOLT-12 offer",
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to create offer: " + e,
|
||||
});
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copy = () => {
|
||||
copyToClipboard(offer as string, toast);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid gap-5 md:max-w-lg">
|
||||
{!offer && (
|
||||
<Alert>
|
||||
<CircleAlertIcon className="h-4 w-4" />
|
||||
<AlertTitle>BOLT-12 Offers are in beta</AlertTitle>
|
||||
<AlertDescription>
|
||||
BOLT-12 is not supported by all wallets and nodes in the lightning
|
||||
network. This feature will work only if you have a channel with a
|
||||
node that supports onion message forwarding, and are paid by a
|
||||
lightning wallet that supports paying BOLT-12 offers.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{offer ? (
|
||||
<>
|
||||
<Card className="w-full md:max-w-xs">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center">Lightning Offer</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col items-center gap-4">
|
||||
<QRCode value={offer} className="w-full" />
|
||||
<div className="flex flex-col md:flex-row gap-4 w-full">
|
||||
<Button
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setDescription("");
|
||||
setOffer(null);
|
||||
}}
|
||||
>
|
||||
<RefreshCwIcon className="h-4 w-4 shrink-0 mr-2" />
|
||||
New Offer
|
||||
</Button>
|
||||
<Button className="flex-1" onClick={copy} variant="secondary">
|
||||
<CopyIcon className="w-4 h-4 mr-2" />
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="grid gap-5">
|
||||
<div>
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
type="text"
|
||||
value={description}
|
||||
placeholder="For e.g. what is this payment for?"
|
||||
onChange={(e) => {
|
||||
setDescription(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<LoadingButton
|
||||
className="w-full md:w-auto"
|
||||
loading={isLoading}
|
||||
type="submit"
|
||||
>
|
||||
Create Offer
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -299,6 +299,10 @@ export type PayInvoiceResponse = {
|
|||
fee: number;
|
||||
};
|
||||
|
||||
export type CreateOfferRequest = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type CreateInvoiceRequest = {
|
||||
amount: number;
|
||||
description: string;
|
||||
|
|
@ -492,6 +496,10 @@ export type TransactionMetadata = {
|
|||
pubkey: string;
|
||||
tags: string[][];
|
||||
}; // NIP-57
|
||||
offer?: {
|
||||
id: string;
|
||||
payer_note: string;
|
||||
}; // BOLT-12
|
||||
} & Record<string, unknown>;
|
||||
|
||||
export type Boostagram = {
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
|
|||
restrictedApiGroup.GET("/wallet/capabilities", httpSvc.capabilitiesHandler)
|
||||
restrictedApiGroup.POST("/payments/:invoice", httpSvc.sendPaymentHandler)
|
||||
restrictedApiGroup.POST("/invoices", httpSvc.makeInvoiceHandler)
|
||||
restrictedApiGroup.POST("/offers", httpSvc.makeOfferHandler)
|
||||
restrictedApiGroup.GET("/transactions", httpSvc.listTransactionsHandler)
|
||||
restrictedApiGroup.GET("/transactions/:paymentHash", httpSvc.lookupTransactionHandler)
|
||||
restrictedApiGroup.GET("/balances", httpSvc.balancesHandler)
|
||||
|
|
@ -543,6 +544,27 @@ func (httpSvc *HttpService) sendPaymentHandler(c echo.Context) error {
|
|||
return c.JSON(http.StatusOK, paymentResponse)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) makeOfferHandler(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
var makeOfferRequest api.MakeOfferRequest
|
||||
if err := c.Bind(&makeOfferRequest); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, ErrorResponse{
|
||||
Message: fmt.Sprintf("Bad request: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
offer, err := httpSvc.api.MakeOffer(ctx, makeOfferRequest.Description)
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: fmt.Sprintf("Failed to generate BOLT-12 offer: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, offer)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) makeInvoiceHandler(c echo.Context) error {
|
||||
var makeInvoiceRequest api.MakeInvoiceRequest
|
||||
if err := c.Bind(&makeInvoiceRequest); err != nil {
|
||||
|
|
|
|||
|
|
@ -564,6 +564,10 @@ func (cs *CashuService) executeCommandResetWallet() (*lnclient.CustomNodeCommand
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (svc *CashuService) MakeOffer(ctx context.Context, description string) (string, error) {
|
||||
return "", errors.New("not supported")
|
||||
}
|
||||
|
||||
func (cs *CashuService) ListOnchainTransactions(ctx context.Context) ([]lnclient.OnchainTransaction, error) {
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,6 +437,17 @@ func getMaxTotalRoutingFeeLimit(amountMsat uint64) ldk_node.MaxTotalRoutingFeeLi
|
|||
}
|
||||
}
|
||||
|
||||
func (ls *LDKService) MakeOffer(ctx context.Context, description string) (string, error) {
|
||||
offer, err := ls.node.Bolt12Payment().ReceiveVariableAmount(description, nil)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to generate BOLT12 offer")
|
||||
return "", err
|
||||
}
|
||||
|
||||
logger.Logger.WithField("offer", offer).Info("Generated BOLT12 offer")
|
||||
return offer, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -1291,6 +1302,34 @@ func (ls *LDKService) ldkPaymentToTransaction(payment *ldk_node.PaymentDetails)
|
|||
paymentHash = bolt11PaymentKind.Hash
|
||||
}
|
||||
|
||||
bolt12PaymentKind, isBolt12PaymentKind := payment.Kind.(ldk_node.PaymentKindBolt12Offer)
|
||||
|
||||
if isBolt12PaymentKind {
|
||||
createdAt = int64(payment.CreatedAt)
|
||||
|
||||
if bolt12PaymentKind.Hash == nil {
|
||||
return nil, errors.New("BOLT-12 payment has no payment hash")
|
||||
}
|
||||
paymentHash = *bolt12PaymentKind.Hash
|
||||
|
||||
offer := map[string]interface{}{}
|
||||
offer["id"] = bolt12PaymentKind.OfferId
|
||||
|
||||
if bolt12PaymentKind.PayerNote != nil {
|
||||
offer["payer_note"] = *bolt12PaymentKind.PayerNote
|
||||
}
|
||||
|
||||
metadata["offer"] = offer
|
||||
|
||||
if payment.Status == ldk_node.PaymentStatusSucceeded {
|
||||
if bolt12PaymentKind.Preimage != nil {
|
||||
preimage = *bolt12PaymentKind.Preimage
|
||||
}
|
||||
lastUpdate := int64(payment.LatestUpdateTimestamp)
|
||||
settledAt = &lastUpdate
|
||||
}
|
||||
}
|
||||
|
||||
spontaneousPaymentKind, isSpontaneousPaymentKind := payment.Kind.(ldk_node.PaymentKindSpontaneous)
|
||||
if isSpontaneousPaymentKind {
|
||||
// keysend payment
|
||||
|
|
@ -1964,12 +2003,169 @@ func (ls *LDKService) GetPubkey() string {
|
|||
return ls.pubkey
|
||||
}
|
||||
|
||||
func (ls *LDKService) PayOfferSync(ctx context.Context, offer string, amount uint64, payerNote string) (*lnclient.PayOfferResponse, error) {
|
||||
// TODO: this is only for testing MakeOffer and needs improvements
|
||||
// (+ BOLT-12 payments need to go through transactions service)
|
||||
// TODO: send liquidity event if amount too large
|
||||
|
||||
paymentStart := time.Now()
|
||||
ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
|
||||
defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
|
||||
|
||||
// TODO: use normal send if no amount is provided
|
||||
paymentId, err := checkLDKErr(ls.node.Bolt12Payment().SendUsingAmount(offer, amount, nil, &payerNote))
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to initiate BOLT-12 variable amount payment")
|
||||
return nil, errors.New("failed to initiate BOLT-12 variable amount payment")
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_id": paymentId,
|
||||
}).Info("Initiated BOLT-12 variable amount payment")
|
||||
|
||||
fee := uint64(0)
|
||||
preimage := ""
|
||||
|
||||
payment := ls.node.Payment(paymentId)
|
||||
if payment == nil {
|
||||
return nil, errors.New("payment not found by payment ID")
|
||||
}
|
||||
|
||||
paymentHash := ""
|
||||
|
||||
for start := time.Now(); time.Since(start) < time.Second*60; {
|
||||
event := <-ldkEventSubscription
|
||||
|
||||
eventPaymentSuccessful, isEventPaymentSuccessfulEvent := (*event).(ldk_node.EventPaymentSuccessful)
|
||||
eventPaymentFailed, isEventPaymentFailedEvent := (*event).(ldk_node.EventPaymentFailed)
|
||||
|
||||
if isEventPaymentSuccessfulEvent && eventPaymentSuccessful.PaymentId != nil && *eventPaymentSuccessful.PaymentId == paymentId {
|
||||
logger.Logger.Info("Got payment success event")
|
||||
payment := ls.node.Payment(paymentId)
|
||||
if payment == nil {
|
||||
logger.Logger.Errorf("Couldn't find payment by payment ID: %v", paymentId)
|
||||
return nil, errors.New("payment not found")
|
||||
}
|
||||
|
||||
bolt12PaymentKind, ok := payment.Kind.(ldk_node.PaymentKindBolt12Offer)
|
||||
|
||||
if !ok {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment": payment,
|
||||
}).Error("Payment is not a BOLT-12 offer kind")
|
||||
return nil, errors.New("payment is not a BOLT-12 offer")
|
||||
}
|
||||
|
||||
if bolt12PaymentKind.Preimage == nil {
|
||||
logger.Logger.Errorf("No payment preimage for payment ID: %v", paymentId)
|
||||
return nil, errors.New("payment preimage not found")
|
||||
}
|
||||
preimage = *bolt12PaymentKind.Preimage
|
||||
|
||||
if bolt12PaymentKind.Hash == nil {
|
||||
logger.Logger.Errorf("No payment hash for payment ID: %v", paymentId)
|
||||
return nil, errors.New("payment hash not found")
|
||||
}
|
||||
paymentHash = *bolt12PaymentKind.Hash
|
||||
|
||||
if eventPaymentSuccessful.FeePaidMsat != nil {
|
||||
fee = *eventPaymentSuccessful.FeePaidMsat
|
||||
}
|
||||
break
|
||||
}
|
||||
if isEventPaymentFailedEvent && eventPaymentFailed.PaymentId != nil && *eventPaymentFailed.PaymentId == paymentId {
|
||||
reason := ls.getPaymentFailReason(&eventPaymentFailed)
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_id": paymentId,
|
||||
"reason": reason,
|
||||
}).Error("Received payment failed event")
|
||||
|
||||
return nil, fmt.Errorf("received payment failed event: %s", reason)
|
||||
}
|
||||
}
|
||||
|
||||
if preimage == "" {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_id": paymentId,
|
||||
}).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.PayOfferResponse{
|
||||
PaymentHash: paymentHash,
|
||||
Preimage: preimage,
|
||||
Fee: fee,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const nodeCommandPayBOLT12Offer = "payBOLT12Offer"
|
||||
|
||||
func (ls *LDKService) GetCustomNodeCommandDefinitions() []lnclient.CustomNodeCommandDef {
|
||||
return nil
|
||||
return []lnclient.CustomNodeCommandDef{
|
||||
{
|
||||
Name: nodeCommandPayBOLT12Offer,
|
||||
Description: "Send payments to a BOLT-12 offer. NOTE: this is for testing only. Payment will not show in transaction list.",
|
||||
Args: []lnclient.CustomNodeCommandArgDef{
|
||||
{
|
||||
Name: "offer",
|
||||
Description: "BOLT-12 offer of receiver",
|
||||
},
|
||||
{
|
||||
Name: "amount",
|
||||
Description: "amount to send in millisats",
|
||||
},
|
||||
{
|
||||
Name: "payerNote",
|
||||
Description: "note to the recepient",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *LDKService) ExecuteCustomNodeCommand(ctx context.Context, command *lnclient.CustomNodeCommandRequest) (*lnclient.CustomNodeCommandResponse, error) {
|
||||
return nil, nil
|
||||
switch command.Name {
|
||||
case nodeCommandPayBOLT12Offer:
|
||||
var offer string
|
||||
var amount uint64
|
||||
var payerNote string
|
||||
var err error
|
||||
for i := range command.Args {
|
||||
switch command.Args[i].Name {
|
||||
case "offer":
|
||||
offer = command.Args[i].Value
|
||||
case "amount":
|
||||
amount, err = strconv.ParseUint(string(command.Args[i].Value), 10, 64)
|
||||
case "payerNote":
|
||||
payerNote = command.Args[i].Value
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payOfferResponse, err := ls.PayOfferSync(ctx, offer, amount, payerNote)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &lnclient.CustomNodeCommandResponse{
|
||||
Response: map[string]interface{}{
|
||||
"paymentHash": payOfferResponse.PaymentHash,
|
||||
"preimage": payOfferResponse.Preimage,
|
||||
"fee": payOfferResponse.Fee,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, lnclient.ErrUnknownCustomNodeCommand
|
||||
}
|
||||
|
||||
func (ls *LDKService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (*lnclient.Transaction, error) {
|
||||
|
|
|
|||
|
|
@ -1573,6 +1573,10 @@ func (svc *LNDService) ExecuteCustomNodeCommand(ctx context.Context, command *ln
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (svc *LNDService) MakeOffer(ctx context.Context, description string) (string, error) {
|
||||
return "", errors.New("not supported")
|
||||
}
|
||||
|
||||
func (svc *LNDService) ListOnchainTransactions(ctx context.Context) ([]lnclient.OnchainTransaction, error) {
|
||||
resp, err := svc.client.GetTransactions(ctx, &lnrpc.GetTransactionsRequest{})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ type LNClient interface {
|
|||
CloseChannel(ctx context.Context, closeChannelRequest *CloseChannelRequest) (*CloseChannelResponse, error)
|
||||
UpdateChannel(ctx context.Context, updateChannelRequest *UpdateChannelRequest) error
|
||||
DisconnectPeer(ctx context.Context, peerId string) error
|
||||
MakeOffer(ctx context.Context, description string) (string, error)
|
||||
GetNewOnchainAddress(ctx context.Context) (string, error)
|
||||
ResetRouter(key string) error
|
||||
GetOnchainBalance(ctx context.Context) (*OnchainBalanceResponse, error)
|
||||
|
|
@ -191,6 +192,12 @@ type PayInvoiceResponse struct {
|
|||
Fee uint64 `json:"fee"`
|
||||
}
|
||||
|
||||
type PayOfferResponse = struct {
|
||||
Preimage string `json:"preimage"`
|
||||
Fee uint64 `json:"fee"`
|
||||
PaymentHash string `json:"payment_hash"`
|
||||
}
|
||||
|
||||
type PayKeysendResponse struct {
|
||||
Fee uint64 `json:"fee"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -553,6 +553,10 @@ func (svc *PhoenixService) ExecuteCustomNodeCommand(ctx context.Context, command
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (svc *PhoenixService) MakeOffer(ctx context.Context, description string) (string, error) {
|
||||
return "", errors.New("not supported")
|
||||
}
|
||||
|
||||
func (svc *PhoenixService) ListOnchainTransactions(ctx context.Context) ([]lnclient.OnchainTransaction, error) {
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,6 +235,10 @@ func (mln *MockLn) ExecuteCustomNodeCommand(ctx context.Context, command *lnclie
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (mln *MockLn) MakeOffer(ctx context.Context, description string) (string, error) {
|
||||
return "", errors.New("not supported")
|
||||
}
|
||||
|
||||
func (mln *MockLn) ListOnchainTransactions(ctx context.Context) ([]lnclient.OnchainTransaction, error) {
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,26 +59,15 @@ type MockConfig_ChangeUnlockPassword_Call struct {
|
|||
}
|
||||
|
||||
// ChangeUnlockPassword is a helper method to define mock.On call
|
||||
// - currentUnlockPassword string
|
||||
// - newUnlockPassword string
|
||||
// - currentUnlockPassword
|
||||
// - newUnlockPassword
|
||||
func (_e *MockConfig_Expecter) ChangeUnlockPassword(currentUnlockPassword interface{}, newUnlockPassword interface{}) *MockConfig_ChangeUnlockPassword_Call {
|
||||
return &MockConfig_ChangeUnlockPassword_Call{Call: _e.mock.On("ChangeUnlockPassword", currentUnlockPassword, newUnlockPassword)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_ChangeUnlockPassword_Call) Run(run func(currentUnlockPassword string, newUnlockPassword string)) *MockConfig_ChangeUnlockPassword_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -116,20 +105,14 @@ type MockConfig_CheckUnlockPassword_Call struct {
|
|||
}
|
||||
|
||||
// CheckUnlockPassword is a helper method to define mock.On call
|
||||
// - password string
|
||||
// - password
|
||||
func (_e *MockConfig_Expecter) CheckUnlockPassword(password interface{}) *MockConfig_CheckUnlockPassword_Call {
|
||||
return &MockConfig_CheckUnlockPassword_Call{Call: _e.mock.On("CheckUnlockPassword", password)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_CheckUnlockPassword_Call) Run(run func(password string)) *MockConfig_CheckUnlockPassword_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -176,26 +159,15 @@ type MockConfig_Get_Call struct {
|
|||
}
|
||||
|
||||
// Get is a helper method to define mock.On call
|
||||
// - key string
|
||||
// - encryptionKey string
|
||||
// - key
|
||||
// - encryptionKey
|
||||
func (_e *MockConfig_Expecter) Get(key interface{}, encryptionKey interface{}) *MockConfig_Get_Call {
|
||||
return &MockConfig_Get_Call{Call: _e.mock.On("Get", key, encryptionKey)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_Get_Call) Run(run func(key string, encryptionKey string)) *MockConfig_Get_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -455,20 +427,14 @@ type MockConfig_SaveUnlockPasswordCheck_Call struct {
|
|||
}
|
||||
|
||||
// SaveUnlockPasswordCheck is a helper method to define mock.On call
|
||||
// - encryptionKey string
|
||||
// - encryptionKey
|
||||
func (_e *MockConfig_Expecter) SaveUnlockPasswordCheck(encryptionKey interface{}) *MockConfig_SaveUnlockPasswordCheck_Call {
|
||||
return &MockConfig_SaveUnlockPasswordCheck_Call{Call: _e.mock.On("SaveUnlockPasswordCheck", encryptionKey)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SaveUnlockPasswordCheck_Call) Run(run func(encryptionKey string)) *MockConfig_SaveUnlockPasswordCheck_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -506,20 +472,14 @@ type MockConfig_SetAutoUnlockPassword_Call struct {
|
|||
}
|
||||
|
||||
// SetAutoUnlockPassword is a helper method to define mock.On call
|
||||
// - unlockPassword string
|
||||
// - unlockPassword
|
||||
func (_e *MockConfig_Expecter) SetAutoUnlockPassword(unlockPassword interface{}) *MockConfig_SetAutoUnlockPassword_Call {
|
||||
return &MockConfig_SetAutoUnlockPassword_Call{Call: _e.mock.On("SetAutoUnlockPassword", unlockPassword)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetAutoUnlockPassword_Call) Run(run func(unlockPassword string)) *MockConfig_SetAutoUnlockPassword_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -557,20 +517,14 @@ type MockConfig_SetCurrency_Call struct {
|
|||
}
|
||||
|
||||
// SetCurrency is a helper method to define mock.On call
|
||||
// - value string
|
||||
// - value
|
||||
func (_e *MockConfig_Expecter) SetCurrency(value interface{}) *MockConfig_SetCurrency_Call {
|
||||
return &MockConfig_SetCurrency_Call{Call: _e.mock.On("SetCurrency", value)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetCurrency_Call) Run(run func(value string)) *MockConfig_SetCurrency_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -608,32 +562,16 @@ type MockConfig_SetIgnore_Call struct {
|
|||
}
|
||||
|
||||
// SetIgnore is a helper method to define mock.On call
|
||||
// - key string
|
||||
// - value string
|
||||
// - encryptionKey string
|
||||
// - key
|
||||
// - value
|
||||
// - encryptionKey
|
||||
func (_e *MockConfig_Expecter) SetIgnore(key interface{}, value interface{}, encryptionKey interface{}) *MockConfig_SetIgnore_Call {
|
||||
return &MockConfig_SetIgnore_Call{Call: _e.mock.On("SetIgnore", key, value, encryptionKey)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetIgnore_Call) Run(run func(key string, value string, encryptionKey string)) *MockConfig_SetIgnore_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
run(args[0].(string), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
@ -671,32 +609,16 @@ type MockConfig_SetUpdate_Call struct {
|
|||
}
|
||||
|
||||
// SetUpdate is a helper method to define mock.On call
|
||||
// - key string
|
||||
// - value string
|
||||
// - encryptionKey string
|
||||
// - key
|
||||
// - value
|
||||
// - encryptionKey
|
||||
func (_e *MockConfig_Expecter) SetUpdate(key interface{}, value interface{}, encryptionKey interface{}) *MockConfig_SetUpdate_Call {
|
||||
return &MockConfig_SetUpdate_Call{Call: _e.mock.On("SetUpdate", key, value, encryptionKey)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetUpdate_Call) Run(run func(key string, value string, encryptionKey string)) *MockConfig_SetUpdate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
run(args[0].(string), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -555,20 +555,14 @@ type MockService_StartApp_Call struct {
|
|||
}
|
||||
|
||||
// StartApp is a helper method to define mock.On call
|
||||
// - encryptionKey string
|
||||
// - encryptionKey
|
||||
func (_e *MockService_Expecter) StartApp(encryptionKey interface{}) *MockService_StartApp_Call {
|
||||
return &MockService_StartApp_Call{Call: _e.mock.On("StartApp", encryptionKey)}
|
||||
}
|
||||
|
||||
func (_c *MockService_StartApp_Call) Run(run func(encryptionKey string)) *MockService_StartApp_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
|
|
|||
|
|
@ -944,6 +944,27 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: *nodeHealth, Error: ""}
|
||||
case "/api/offers":
|
||||
makeOfferRequest := &api.MakeOfferRequest{}
|
||||
err := json.Unmarshal([]byte(body), makeOfferRequest)
|
||||
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()}
|
||||
}
|
||||
offer, err := app.api.MakeOffer(ctx, makeOfferRequest.Description)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"route": route,
|
||||
"method": method,
|
||||
"body": body,
|
||||
}).WithError(err).Error("Failed to generate BOLT-12 offer")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: offer, Error: ""}
|
||||
case "/api/commands":
|
||||
nodeCommandsResponse, err := app.api.GetCustomNodeCommands()
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue