diff --git a/api/models.go b/api/models.go index ee640bb0..21104165 100644 --- a/api/models.go +++ b/api/models.go @@ -36,7 +36,7 @@ type API interface { RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (*RedeemOnchainFundsResponse, error) GetBalances(ctx context.Context) (*BalancesResponse, error) ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64) (*ListTransactionsResponse, error) - SendPayment(ctx context.Context, invoice string) (*SendPaymentResponse, error) + SendPayment(ctx context.Context, invoice string, amountMsat *uint64) (*SendPaymentResponse, error) CreateInvoice(ctx context.Context, amount uint64, description string) (*MakeInvoiceResponse, error) LookupInvoice(ctx context.Context, paymentHash string) (*LookupInvoiceResponse, error) RequestMempoolApi(endpoint string) (interface{}, error) @@ -290,6 +290,10 @@ type SignMessageResponse struct { Signature string `json:"signature"` } +type PayInvoiceRequest struct { + Amount *uint64 `json:"amount"` +} + type MakeInvoiceRequest struct { Amount uint64 `json:"amount"` Description string `json:"description"` diff --git a/api/transactions.go b/api/transactions.go index 94aea6dd..24cd166f 100644 --- a/api/transactions.go +++ b/api/transactions.go @@ -53,11 +53,11 @@ func (api *api) ListTransactions(ctx context.Context, appId *uint, limit uint64, return &apiTransactions, nil } -func (api *api) SendPayment(ctx context.Context, invoice string) (*SendPaymentResponse, error) { +func (api *api) SendPayment(ctx context.Context, invoice string, amountMsat *uint64) (*SendPaymentResponse, error) { if api.svc.GetLNClient() == nil { return nil, errors.New("LNClient not started") } - transaction, err := api.svc.GetTransactionsService().SendPaymentSync(ctx, invoice, nil, api.svc.GetLNClient(), nil, nil) + transaction, err := api.svc.GetTransactionsService().SendPaymentSync(ctx, invoice, amountMsat, nil, api.svc.GetLNClient(), nil, nil) if err != nil { return nil, err } @@ -131,7 +131,7 @@ func (api *api) TopupIsolatedApp(ctx context.Context, userApp *db.App, amountMsa return err } - _, err = api.svc.GetTransactionsService().SendPaymentSync(ctx, transaction.PaymentRequest, nil, api.svc.GetLNClient(), nil, nil) + _, err = api.svc.GetTransactionsService().SendPaymentSync(ctx, transaction.PaymentRequest, nil, nil, api.svc.GetLNClient(), nil, nil) return err } diff --git a/frontend/package.json b/frontend/package.json index 2d1fde8f..11071655 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,7 @@ "prepare": "cd .. && husky frontend/.husky" }, "dependencies": { - "@getalby/lightning-tools": "^5.1.1", + "@getalby/lightning-tools": "^5.1.2", "@getalby/sdk": "^3.8.2", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-alert-dialog": "^1.0.5", diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index 4247028e..3ec6aeb1 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -68,6 +68,7 @@ import ReceiveInvoice from "src/screens/wallet/receive/ReceiveInvoice"; import ConfirmPayment from "src/screens/wallet/send/ConfirmPayment"; import LnurlPay from "src/screens/wallet/send/LnurlPay"; import PaymentSuccess from "src/screens/wallet/send/PaymentSuccess"; +import ZeroAmount from "src/screens/wallet/send/ZeroAmount"; const routes = [ { @@ -128,6 +129,10 @@ const routes = [ path: "lnurl-pay", element: , }, + { + path: "0-amount", + element: , + }, { path: "confirm-payment", element: , diff --git a/frontend/src/screens/wallet/Send.tsx b/frontend/src/screens/wallet/Send.tsx index b351e7ca..3510d800 100644 --- a/frontend/src/screens/wallet/Send.tsx +++ b/frontend/src/screens/wallet/Send.tsx @@ -42,6 +42,15 @@ export default function Send() { } const invoice = new Invoice({ pr: recipient }); + if (invoice.satoshi === 0) { + navigate(`/wallet/send/0-amount`, { + state: { + args: { paymentRequest: invoice }, + }, + }); + return; + } + navigate(`/wallet/send/confirm-payment`, { state: { args: { paymentRequest: invoice }, diff --git a/frontend/src/screens/wallet/send/ConfirmPayment.tsx b/frontend/src/screens/wallet/send/ConfirmPayment.tsx index f924758b..46146ba0 100644 --- a/frontend/src/screens/wallet/send/ConfirmPayment.tsx +++ b/frontend/src/screens/wallet/send/ConfirmPayment.tsx @@ -16,6 +16,7 @@ export default function ConfirmPayment() { const { toast } = useToast(); const invoice = state?.args?.paymentRequest as Invoice; + const amount = state?.args?.amount as number | undefined; const [isLoading, setLoading] = React.useState(false); const onSubmit = async (event: React.FormEvent) => { @@ -26,6 +27,9 @@ export default function ConfirmPayment() { `/api/payments/${invoice.paymentRequest}`, { method: "POST", + body: JSON.stringify({ + amount: amount ? amount * 1000 : undefined, + }), headers: { "Content-Type": "application/json", }, @@ -71,7 +75,7 @@ export default function ConfirmPayment() {

- {new Intl.NumberFormat().format(invoice.satoshi)} sats + {new Intl.NumberFormat().format(amount || invoice.satoshi)} sats

{invoice.description && ( diff --git a/frontend/src/screens/wallet/send/ZeroAmount.tsx b/frontend/src/screens/wallet/send/ZeroAmount.tsx new file mode 100644 index 00000000..a10dc6eb --- /dev/null +++ b/frontend/src/screens/wallet/send/ZeroAmount.tsx @@ -0,0 +1,93 @@ +import React from "react"; +import { Button } from "src/components/ui/button"; +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 { Invoice } from "@getalby/lightning-tools"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import Loading from "src/components/Loading"; + +export default function ZeroAmount() { + const { state } = useLocation(); + const navigate = useNavigate(); + const { toast } = useToast(); + + const paymentRequest = state?.args?.paymentRequest as Invoice; + const [amount, setAmount] = React.useState(""); + const [isLoading, setLoading] = React.useState(false); + + const onSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + try { + if (!paymentRequest) { + throw new Error("no invoice set"); + } + setLoading(true); + + navigate(`/wallet/send/confirm-payment`, { + state: { + args: { paymentRequest, amount: parseInt(amount) }, + }, + }); + } catch (e) { + toast({ + variant: "destructive", + title: "Failed to send payment", + description: "" + e, + }); + console.error(e); + } finally { + setLoading(false); + } + }; + + React.useEffect(() => { + if (!paymentRequest) { + navigate("/wallet/send"); + } + }, [navigate, paymentRequest]); + + if (!paymentRequest) { + return ; + } + + return ( +
+
+ {paymentRequest.description && ( +
+ +

+ {paymentRequest.description} +

+
+ )} +
+ + { + setAmount(e.target.value.trim()); + }} + min={1} + required + autoFocus + /> +
+
+
+ + Continue + + + + +
+
+ ); +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e5bf32d0..3616db7a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1337,10 +1337,10 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== -"@getalby/lightning-tools@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@getalby/lightning-tools/-/lightning-tools-5.1.1.tgz#51125b2c58ef9372ae9efa93d0808d2205914b91" - integrity sha512-qiGWY7AMnQXywNlpEUTm/2u7Qx0C0qV0i3vlAV5ip8xV2quo4hkesHuAh6dBg/p3VC7t1fa9YUe9677hvQ3fVA== +"@getalby/lightning-tools@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@getalby/lightning-tools/-/lightning-tools-5.1.2.tgz#8a018e98d5c13097dd98d93192cf5e4e455f4c20" + integrity sha512-BwGm8eGbPh59BVa1gI5yJMantBl/Fdps6X4p1ZACnmxz9vDINX8/3aFoOnDlF7yyA2boXWCsReVQSr26Q2yjiQ== "@getalby/sdk@^3.8.2": version "3.8.2" diff --git a/http/http_service.go b/http/http_service.go index bd622dd9..12f4982b 100644 --- a/http/http_service.go +++ b/http/http_service.go @@ -459,7 +459,14 @@ func (httpSvc *HttpService) balancesHandler(c echo.Context) error { func (httpSvc *HttpService) sendPaymentHandler(c echo.Context) error { ctx := c.Request().Context() - paymentResponse, err := httpSvc.api.SendPayment(ctx, c.Param("invoice")) + var payInvoiceRequest api.PayInvoiceRequest + if err := c.Bind(&payInvoiceRequest); err != nil { + return c.JSON(http.StatusBadRequest, ErrorResponse{ + Message: fmt.Sprintf("Bad request: %s", err.Error()), + }) + } + + paymentResponse, err := httpSvc.api.SendPayment(ctx, c.Param("invoice"), payInvoiceRequest.Amount) if err != nil { return c.JSON(http.StatusInternalServerError, ErrorResponse{ diff --git a/lnclient/breez/breez.go b/lnclient/breez/breez.go index 489673a6..dfc37552 100644 --- a/lnclient/breez/breez.go +++ b/lnclient/breez/breez.go @@ -99,7 +99,10 @@ func (bs *BreezService) Shutdown() error { return bs.svc.Disconnect() } -func (bs *BreezService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) { +func (bs *BreezService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) { + if amount != nil { + return nil, errors.New("0-amount invoices not supported") + } sendPaymentRequest := breez_sdk.SendPaymentRequest{ Bolt11: payReq, } diff --git a/lnclient/cashu/cashu.go b/lnclient/cashu/cashu.go index f7e41c23..3b510746 100644 --- a/lnclient/cashu/cashu.go +++ b/lnclient/cashu/cashu.go @@ -76,7 +76,12 @@ func (cs *CashuService) Shutdown() error { return nil } -func (cs *CashuService) SendPaymentSync(ctx context.Context, invoice string) (response *lnclient.PayInvoiceResponse, err error) { +func (cs *CashuService) SendPaymentSync(ctx context.Context, invoice string, amount *uint64) (response *lnclient.PayInvoiceResponse, err error) { + // TODO: support 0-amount invoices + if amount != nil { + return nil, errors.New("0-amount invoices not supported") + } + meltResponse, err := cs.wallet.Melt(invoice, cs.wallet.CurrentMint()) if err != nil { logger.Logger.WithError(err).Error("Failed to melt invoice") diff --git a/lnclient/greenlight/greenlight.go b/lnclient/greenlight/greenlight.go index 476a5418..303553f9 100644 --- a/lnclient/greenlight/greenlight.go +++ b/lnclient/greenlight/greenlight.go @@ -117,7 +117,10 @@ func (gs *GreenlightService) Shutdown() error { return nil } -func (gs *GreenlightService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) { +func (gs *GreenlightService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) { + if amount != nil { + return nil, errors.New("0-amount invoices not supported") + } response, err := gs.client.Pay(glalby.PayRequest{ Bolt11: payReq, }) diff --git a/lnclient/ldk/ldk.go b/lnclient/ldk/ldk.go index e94e4686..7b23981a 100644 --- a/lnclient/ldk/ldk.go +++ b/lnclient/ldk/ldk.go @@ -472,7 +472,7 @@ func (ls *LDKService) resetRouterInternal() { } } -func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnclient.PayInvoiceResponse, error) { +func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string, amount *uint64) (*lnclient.PayInvoiceResponse, error) { paymentRequest, err := decodepay.Decodepay(invoice) if err != nil { logger.Logger.WithFields(logrus.Fields{ @@ -482,8 +482,13 @@ func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnc return nil, err } + paymentAmount := uint64(paymentRequest.MSatoshi) + if amount != nil { + paymentAmount = *amount + } + maxSpendable := ls.getMaxSpendable() - if paymentRequest.MSatoshi > maxSpendable { + if paymentAmount > maxSpendable { ls.eventPublisher.Publish(&events.Event{ Event: "nwc_outgoing_liquidity_required", Properties: map[string]interface{}{ @@ -499,7 +504,12 @@ func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnc ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe() defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription) - paymentHash, err := ls.node.Bolt11Payment().Send(invoice, nil) + var paymentHash string + if amount == nil { + paymentHash, err = ls.node.Bolt11Payment().Send(invoice, nil) + } else { + paymentHash, err = ls.node.Bolt11Payment().SendUsingAmount(invoice, *amount, nil) + } if err != nil { logger.Logger.WithError(err).Error("SendPayment failed") return nil, err @@ -649,15 +659,15 @@ func (ls *LDKService) getMaxReceivable() int64 { return int64(receivable) } -func (ls *LDKService) getMaxSpendable() int64 { - var spendable int64 = 0 +func (ls *LDKService) getMaxSpendable() uint64 { + var spendable uint64 = 0 channels := ls.node.ListChannels() for _, channel := range channels { if channel.IsUsable { - spendable += min(int64(channel.OutboundCapacityMsat), int64(*channel.CounterpartyOutboundHtlcMaximumMsat)) + spendable += min(channel.OutboundCapacityMsat, *channel.CounterpartyOutboundHtlcMaximumMsat) } } - return int64(spendable) + return spendable } func (ls *LDKService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *lnclient.Transaction, err error) { diff --git a/lnclient/lnd/lnd.go b/lnclient/lnd/lnd.go index 409803ea..e0f8d111 100644 --- a/lnclient/lnd/lnd.go +++ b/lnclient/lnd/lnd.go @@ -313,8 +313,14 @@ func (svc *LNDService) LookupInvoice(ctx context.Context, paymentHash string) (t return transaction, nil } -func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) { - resp, err := svc.client.SendPaymentSync(ctx, &lnrpc.SendRequest{PaymentRequest: payReq}) +func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) { + sendRequest := &lnrpc.SendRequest{PaymentRequest: payReq} + + if amount != nil { + sendRequest.AmtMsat = int64(*amount) + } + + resp, err := svc.client.SendPaymentSync(ctx, sendRequest) if err != nil { return nil, err } diff --git a/lnclient/models.go b/lnclient/models.go index d5b9d480..ec99fd56 100644 --- a/lnclient/models.go +++ b/lnclient/models.go @@ -46,7 +46,7 @@ type NodeConnectionInfo struct { } type LNClient interface { - SendPaymentSync(ctx context.Context, payReq string) (*PayInvoiceResponse, error) + SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*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) diff --git a/lnclient/phoenixd/phoenixd.go b/lnclient/phoenixd/phoenixd.go index e8c069bb..21d8f590 100644 --- a/lnclient/phoenixd/phoenixd.go +++ b/lnclient/phoenixd/phoenixd.go @@ -398,7 +398,11 @@ func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string return transaction, nil } -func (svc *PhoenixService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) { +func (svc *PhoenixService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) { + // TODO: support 0-amount invoices + if amount != nil { + return nil, errors.New("0-amount invoices not supported") + } form := url.Values{} form.Add("invoice", payReq) req, err := http.NewRequest(http.MethodPost, svc.Address+"/payinvoice", strings.NewReader(form.Encode())) diff --git a/nip47/controllers/multi_pay_invoice_controller.go b/nip47/controllers/multi_pay_invoice_controller.go index 3477e090..c9fa11cb 100644 --- a/nip47/controllers/multi_pay_invoice_controller.go +++ b/nip47/controllers/multi_pay_invoice_controller.go @@ -69,7 +69,7 @@ func (controller *nip47Controller) HandleMultiPayInvoiceEvent(ctx context.Contex dTag := []string{"d", invoiceDTagValue} controller. - pay(ctx, bolt11, metadata, &paymentRequest, nip47Request, requestEventId, app, publishResponse, nostr.Tags{dTag}) + pay(ctx, bolt11, invoiceInfo.Amount, metadata, &paymentRequest, nip47Request, requestEventId, app, publishResponse, nostr.Tags{dTag}) }(invoiceInfo) } diff --git a/nip47/controllers/pay_invoice_controller.go b/nip47/controllers/pay_invoice_controller.go index 7003b221..8204debb 100644 --- a/nip47/controllers/pay_invoice_controller.go +++ b/nip47/controllers/pay_invoice_controller.go @@ -16,6 +16,7 @@ import ( type payInvoiceParams struct { Invoice string `json:"invoice"` + Amount *uint64 `json:"amount"` Metadata map[string]interface{} `json:"metadata,omitempty"` } @@ -48,17 +49,17 @@ func (controller *nip47Controller) HandlePayInvoiceEvent(ctx context.Context, ni return } - controller.pay(ctx, bolt11, payParams.Metadata, &paymentRequest, nip47Request, requestEventId, app, publishResponse, tags) + controller.pay(ctx, bolt11, payParams.Amount, payParams.Metadata, &paymentRequest, nip47Request, requestEventId, app, publishResponse, tags) } -func (controller *nip47Controller) pay(ctx context.Context, bolt11 string, metadata map[string]interface{}, paymentRequest *decodepay.Bolt11, nip47Request *models.Request, requestEventId uint, app *db.App, publishResponse publishFunc, tags nostr.Tags) { +func (controller *nip47Controller) pay(ctx context.Context, bolt11 string, amount *uint64, metadata map[string]interface{}, paymentRequest *decodepay.Bolt11, nip47Request *models.Request, requestEventId uint, app *db.App, publishResponse publishFunc, tags nostr.Tags) { logger.Logger.WithFields(logrus.Fields{ "request_event_id": requestEventId, "app_id": app.ID, "bolt11": bolt11, }).Info("Sending payment") - transaction, err := controller.transactionsService.SendPaymentSync(ctx, bolt11, metadata, controller.lnClient, &app.ID, &requestEventId) + transaction, err := controller.transactionsService.SendPaymentSync(ctx, bolt11, amount, metadata, controller.lnClient, &app.ID, &requestEventId) if err != nil { logger.Logger.WithFields(logrus.Fields{ "request_event_id": requestEventId, diff --git a/nip47/controllers/pay_invoice_controller_test.go b/nip47/controllers/pay_invoice_controller_test.go index ba082819..54d6db6f 100644 --- a/nip47/controllers/pay_invoice_controller_test.go +++ b/nip47/controllers/pay_invoice_controller_test.go @@ -26,6 +26,15 @@ const nip47PayInvoiceJson = ` } } ` +const nip47PayInvoice0AmountJson = ` +{ + "method": "pay_invoice", + "params": { + "invoice": "lnbc1pn428a6pp5njn604kl6x7eruycwzpwapwe97uer8et084kru4r0hsql9ttpmusdp82pshjgr5dusyymrfde4jq4mpd3kx2apq24ek2uscqzpuxqr8pqsp50nvadnrqlvghx44ftl89gjvx9lvrfpy5sypt2zahmpehvex4lp7q9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq395pa893uux2agv8jlqxsppyyx5e4rfdjn9dhynlzxhuq3sgwnv48akdqxut7s4fqrre8adnt8kmfmjaexdcevajqlxvmtjpp823cmspud7udk", + "amount": 1234 + } +} +` const nip47PayJsonNoInvoice = ` { @@ -87,6 +96,51 @@ func TestHandlePayInvoiceEvent(t *testing.T) { assert.Equal(t, 123, decodedMetadata.A) } +func TestHandlePayInvoiceEvent_0Amount(t *testing.T) { + ctx := context.TODO() + defer tests.RemoveTestService() + svc, err := tests.CreateTestService() + require.NoError(t, err) + + app, _, err := tests.CreateApp(svc) + assert.NoError(t, err) + + appPermission := &db.AppPermission{ + AppId: app.ID, + App: *app, + Scope: constants.PAY_INVOICE_SCOPE, + } + err = svc.DB.Create(appPermission).Error + assert.NoError(t, err) + + nip47Request := &models.Request{} + err = json.Unmarshal([]byte(nip47PayInvoice0AmountJson), nip47Request) + assert.NoError(t, err) + + dbRequestEvent := &db.RequestEvent{} + err = svc.DB.Create(&dbRequestEvent).Error + assert.NoError(t, err) + + var publishedResponse *models.Response + + publishResponse := func(response *models.Response, tags nostr.Tags) { + publishedResponse = response + } + + permissionsSvc := permissions.NewPermissionsService(svc.DB, svc.EventPublisher) + transactionsSvc := transactions.NewTransactionsService(svc.DB, svc.EventPublisher) + NewNip47Controller(svc.LNClient, svc.DB, svc.EventPublisher, permissionsSvc, transactionsSvc). + HandlePayInvoiceEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse, nostr.Tags{}) + + assert.Equal(t, "123preimage", publishedResponse.Result.(payResponse).Preimage) + + transactionType := constants.TRANSACTION_TYPE_OUTGOING + transaction, err := transactionsSvc.LookupTransaction(ctx, "9ca7a7d6dfd1bd91f0987082ee85d92fb9919f2b79eb61f2a37de00f956b0ef9", &transactionType, svc.LNClient, &app.ID) + assert.NoError(t, err) + // from the request amount + assert.Equal(t, uint64(1234), transaction.AmountMsat) +} + func TestHandlePayInvoiceEvent_MalformedInvoice(t *testing.T) { ctx := context.TODO() defer tests.RemoveTestService() diff --git a/tests/mock_ln_client.go b/tests/mock_ln_client.go index 7e8debfb..a020cea0 100644 --- a/tests/mock_ln_client.go +++ b/tests/mock_ln_client.go @@ -14,6 +14,8 @@ const MockPaymentHash500 = "be8ad5d0b82071d538dcd160e3a3af444bd890de68388a4d771b const MockInvoice = "lntb1230n1pjypux0pp5xgxzcks5jtx06k784f9dndjh664wc08ucrganpqn52d0ftrh9n8sdqyw3jscqzpgxqyz5vqsp5rkx7cq252p3frx8ytjpzc55rkgyx2mfkzzraa272dqvr2j6leurs9qyyssqhutxa24r5hqxstchz5fxlslawprqjnarjujp5sm3xj7ex73s32sn54fthv2aqlhp76qmvrlvxppx9skd3r5ut5xutgrup8zuc6ay73gqmra29m" const MockPaymentHash = "320c2c5a1492ccfd5bc7aa4ad9b657d6aaec3cfcc0d1d98413a29af4ac772ccf" // for the above invoice +const Mock0AmountInvoice = "lnbc1pn428a6pp5njn604kl6x7eruycwzpwapwe97uer8et084kru4r0hsql9ttpmusdp82pshjgr5dusyymrfde4jq4mpd3kx2apq24ek2uscqzpuxqr8pqsp50nvadnrqlvghx44ftl89gjvx9lvrfpy5sypt2zahmpehvex4lp7q9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq395pa893uux2agv8jlqxsppyyx5e4rfdjn9dhynlzxhuq3sgwnv48akdqxut7s4fqrre8adnt8kmfmjaexdcevajqlxvmtjpp823cmspud7udk" + var MockNodeInfo = lnclient.NodeInfo{ Alias: "bob", Color: "#3399FF", @@ -74,7 +76,7 @@ func NewMockLn() (*MockLn, error) { return &MockLn{}, nil } -func (mln *MockLn) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) { +func (mln *MockLn) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) { if len(mln.PayInvoiceResponses) > 0 { response := mln.PayInvoiceResponses[0] err := mln.PayInvoiceErrors[0] diff --git a/transactions/app_payments_test.go b/transactions/app_payments_test.go index 6169e5e4..7ecfa18b 100644 --- a/transactions/app_payments_test.go +++ b/transactions/app_payments_test.go @@ -27,7 +27,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.Equal(t, "app does not have pay_invoice scope", err.Error()) @@ -56,7 +56,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -93,7 +93,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.ErrorIs(t, err, NewQuotaExceededError()) @@ -140,7 +140,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.ErrorIs(t, err, NewQuotaExceededError()) @@ -179,7 +179,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.ErrorIs(t, err, NewQuotaExceededError()) @@ -219,7 +219,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) diff --git a/transactions/isolated_app_payments_test.go b/transactions/isolated_app_payments_test.go index 1e8081d2..f0454d84 100644 --- a/transactions/isolated_app_payments_test.go +++ b/transactions/isolated_app_payments_test.go @@ -36,7 +36,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.ErrorIs(t, err, NewInsufficientBalanceError()) @@ -78,7 +78,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.ErrorIs(t, err, NewInsufficientBalanceError()) @@ -124,7 +124,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceSufficient(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -173,7 +173,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_OutstandingPayment(t *t }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.ErrorIs(t, err, NewInsufficientBalanceError()) @@ -219,7 +219,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_SettledPayment(t *testi }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.Error(t, err) assert.ErrorIs(t, err, NewInsufficientBalanceError()) @@ -264,7 +264,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceSufficient_UnrelatedPayment(t *testi }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -312,7 +312,7 @@ func TestSendPaymentSync_IsolatedApp_BalanceSufficient_FailedPayment(t *testing. }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) diff --git a/transactions/payments_test.go b/transactions/payments_test.go index 91818937..298ecb14 100644 --- a/transactions/payments_test.go +++ b/transactions/payments_test.go @@ -30,7 +30,7 @@ func TestSendPaymentSync_NoApp(t *testing.T) { } transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, metadata, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, nil, nil) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -47,6 +47,28 @@ func TestSendPaymentSync_NoApp(t *testing.T) { assert.Equal(t, 123, decodedMetadata.A) } +func TestSendPaymentSync_0Amount(t *testing.T) { + ctx := context.TODO() + + defer tests.RemoveTestService() + svc, err := tests.CreateTestService() + require.NoError(t, err) + + metadata := map[string]interface{}{ + "a": 123, + } + + transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) + amount := uint64(1234) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.Mock0AmountInvoice, &amount, metadata, svc.LNClient, nil, nil) + + assert.NoError(t, err) + assert.Equal(t, amount, transaction.AmountMsat) + assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State) + assert.Zero(t, transaction.FeeReserveMsat) + assert.Equal(t, "123preimage", *transaction.Preimage) +} + func TestSendPaymentSync_MetadataTooLarge(t *testing.T) { ctx := context.TODO() @@ -58,7 +80,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, metadata, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, metadata, svc.LNClient, 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()) @@ -80,7 +102,7 @@ func TestSendPaymentSync_Duplicate(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil) assert.Error(t, err) assert.Equal(t, "this invoice has already been paid", err.Error()) @@ -242,7 +264,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, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil) assert.Error(t, err) assert.Nil(t, transaction) @@ -272,7 +294,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, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, nil, nil) assert.Error(t, err) assert.Nil(t, transaction) diff --git a/transactions/self_payments_test.go b/transactions/self_payments_test.go index 03c7b203..f66386e7 100644 --- a/transactions/self_payments_test.go +++ b/transactions/self_payments_test.go @@ -33,7 +33,7 @@ func TestSendPaymentSync_SelfPayment_NoAppToNoApp(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -85,7 +85,7 @@ func TestSendPaymentSync_SelfPayment_NoAppToIsolatedApp(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -138,7 +138,7 @@ func TestSendPaymentSync_SelfPayment_NoAppToApp(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, svc.LNClient, nil, nil) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, nil, nil) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -207,7 +207,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToNoApp(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -282,7 +282,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToApp(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -364,7 +364,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, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) @@ -449,7 +449,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToSelf(t *testing.T) { }) transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher) - transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) + transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockInvoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID) assert.NoError(t, err) assert.Equal(t, uint64(123000), transaction.AmountMsat) diff --git a/transactions/transactions_service.go b/transactions/transactions_service.go index fa8dad17..28b077ed 100644 --- a/transactions/transactions_service.go +++ b/transactions/transactions_service.go @@ -36,7 +36,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, err error) - SendPaymentSync(ctx context.Context, payReq string, 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) (*Transaction, error) SendKeysend(ctx context.Context, amount uint64, destination string, customRecords []lnclient.TLVRecord, preimage string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) } @@ -182,7 +182,7 @@ func (svc *transactionsService) MakeInvoice(ctx context.Context, amount uint64, return &dbTransaction, nil } -func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq string, 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) (*Transaction, error) { var metadataBytes []byte if metadata != nil { var err error @@ -210,6 +210,11 @@ func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq stri var dbTransaction db.Transaction + paymentAmount := uint64(paymentRequest.MSatoshi) + if amountMsat != nil { + paymentAmount = *amountMsat + } + err = svc.db.Transaction(func(tx *gorm.DB) error { var existingSettledTransaction db.Transaction if tx.Limit(1).Find(&existingSettledTransaction, &db.Transaction{ @@ -221,7 +226,7 @@ func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq stri return errors.New("this invoice has already been paid") } - err := svc.validateCanPay(tx, appId, uint64(paymentRequest.MSatoshi), paymentRequest.Description) + err := svc.validateCanPay(tx, appId, paymentAmount, paymentRequest.Description) if err != nil { return err } @@ -236,8 +241,8 @@ func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq stri RequestEventId: requestEventId, Type: constants.TRANSACTION_TYPE_OUTGOING, State: constants.TRANSACTION_STATE_PENDING, - FeeReserveMsat: svc.calculateFeeReserveMsat(uint64(paymentRequest.MSatoshi)), - AmountMsat: uint64(paymentRequest.MSatoshi), + FeeReserveMsat: svc.calculateFeeReserveMsat(paymentAmount), + AmountMsat: paymentAmount, PaymentRequest: payReq, PaymentHash: paymentRequest.PaymentHash, Description: paymentRequest.Description, @@ -261,7 +266,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) + response, err = lnClient.SendPaymentSync(ctx, payReq, amountMsat) } if err != nil { diff --git a/wails/wails_handlers.go b/wails/wails_handlers.go index b5243b10..2b3c530c 100644 --- a/wails/wails_handlers.go +++ b/wails/wails_handlers.go @@ -284,7 +284,17 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string switch { case len(invoiceMatch) > 1: invoice := invoiceMatch[1] - paymentResponse, err := app.api.SendPayment(ctx, invoice) + payRequest := &api.PayInvoiceRequest{} + err := json.Unmarshal([]byte(body), payRequest) + 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()} + } + paymentResponse, err := app.api.SendPayment(ctx, invoice, payRequest.Amount) if err != nil { return WailsRequestRouterResponse{Body: nil, Error: err.Error()} }