diff --git a/go.mod b/go.mod index ffdff34..e20e821 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ardevd/flash go 1.21.1 require ( - github.com/btcsuite/btcd/btcutil v1.1.4-0.20230904040416-d4f519f5dc05 + github.com/btcsuite/btcd/btcutil v1.1.5 github.com/charmbracelet/bubbles v0.17.1 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/huh v0.2.3 @@ -11,7 +11,7 @@ require ( github.com/charmbracelet/log v0.3.1 github.com/google/tink/go v1.7.0 github.com/lightninglabs/lndclient v1.0.1-0.20240116101412-67d851af8b54 - github.com/lightningnetwork/lnd v0.17.3-beta.rc1 + github.com/lightningnetwork/lnd v0.17.4-beta.rc1 github.com/muesli/termenv v0.15.2 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stretchr/testify v1.8.4 @@ -26,12 +26,12 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd v0.23.5-0.20230905170901-80f5a0ffdf36 // indirect + github.com/btcsuite/btcd v0.24.1-0.20240123000108-62e6af035ec5 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcwallet v0.16.10-0.20231129183218-5df09dd43358 // indirect + github.com/btcsuite/btcwallet v0.16.10-0.20240127010340-16b422a2e8bf // indirect github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect @@ -68,7 +68,7 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect diff --git a/go.sum b/go.sum index 5cb6d19..983bdea 100644 --- a/go.sum +++ b/go.sum @@ -70,10 +70,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g= -github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= -github.com/btcsuite/btcd v0.23.5-0.20230905170901-80f5a0ffdf36 h1:g/UbZ6iSzcUH9kEvC+rB8UBCqahmt69e8y6nCegczbg= -github.com/btcsuite/btcd v0.23.5-0.20230905170901-80f5a0ffdf36/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.1-0.20240123000108-62e6af035ec5 h1:8BHBWvtP6kkzvmCpyWEznq4eS0gfLOSVuXLesv413Xs= +github.com/btcsuite/btcd v0.24.1-0.20240123000108-62e6af035ec5/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= @@ -82,19 +82,19 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= -github.com/btcsuite/btcd/btcutil v1.1.4-0.20230904040416-d4f519f5dc05 h1:aemxF+69pT9sYC5E6Qj71zQVHcF72m0BNcVhCl3/thU= -github.com/btcsuite/btcd/btcutil v1.1.4-0.20230904040416-d4f519f5dc05/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg= github.com/btcsuite/btcd/btcutil/psbt v1.1.8/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.16.10-0.20231129183218-5df09dd43358 h1:lZUSo6TISHUJQxpn/AniW5gqaN1iRNS87SDWvV3AHfg= -github.com/btcsuite/btcwallet v0.16.10-0.20231129183218-5df09dd43358/go.mod h1:WSKhOJWUmUOHKCKEzdt+jWAHFAE/t4RqVbCwL2pEdiU= +github.com/btcsuite/btcwallet v0.16.10-0.20240127010340-16b422a2e8bf h1:eNjj5R0tKP48NQxDkuKr+C9frZsdzTAemEwu75ZDQg0= +github.com/btcsuite/btcwallet v0.16.10-0.20240127010340-16b422a2e8bf/go.mod h1:LzcW/LYkQLgDufv6Ouw4cOIW0YsY+A60MTtc61/OZTU= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGHjGyV5xTqsXi+ZQ715UU= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2/go.mod h1:Zpk/LOb2sKqwP2lmHjaZT9AdaKsHPSbNLm2Uql5IQ/0= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= @@ -307,8 +307,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -455,8 +455,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wl github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.17.3-beta.rc1 h1:hdYy8mcdfVOtSNQ/NPOhl+YvMdYc6IPXrdT9Ey/cvMk= -github.com/lightningnetwork/lnd v0.17.3-beta.rc1/go.mod h1:zKLs8Rb+jhXML7R9juPPNm6rjnINJ7Ry3haoM6jQeWo= +github.com/lightningnetwork/lnd v0.17.4-beta.rc1 h1:z2rb3MlSvwLMgjRl/IEMCmWCBZeFeM6cJtMxBlrgX1E= +github.com/lightningnetwork/lnd v0.17.4-beta.rc1/go.mod h1:S5hugoB/FWyF9Up9sjEnOsA/ohmhXzIqRHHMLlrtyFk= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= diff --git a/internal/lnd/util.go b/internal/lnd/util.go index c917d18..db3ad3e 100644 --- a/internal/lnd/util.go +++ b/internal/lnd/util.go @@ -1,8 +1,16 @@ package lnd -import "fmt" +import ( + "fmt" + "strings" +) func satsToShortString(sats float64) string { millionSats := (sats / 1000000.0) return fmt.Sprintf("%.1fm", millionSats) -} \ No newline at end of file +} + +func SantizeBoltInvoice(invoice string) string { + cleanedInvoice := strings.Replace(invoice, "lightning:", "", -1) + return cleanedInvoice +} diff --git a/internal/lnd/util_test.go b/internal/lnd/util_test.go new file mode 100644 index 0000000..d5cf678 --- /dev/null +++ b/internal/lnd/util_test.go @@ -0,0 +1,16 @@ +package lnd + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSantizeBoltInvoice(t *testing.T) { + invoiceStr := "lightning:lnbc10u1pju83nypp5ffwgtf2hheeyxt69u5pxku8ww83nsvy5n8jenl239cx8xq2fq3nqdp8fe5kxetgv9eksgzyv4cx7umfwssyjmnkda5kxegcqzysxqr8pqsp5z6dfwhvzkjwh8tzggnh82zhjk2mx3eweysaj93eaeuxs2mevz55q9qyyssqtpv4pq5enzaqv5d7yhftzpzwtxlmtq7wacv5jz4she9lphe99fazqehzff73k7hh64stmnsk4dvhcldazxpjaz9l6fwu5al0w9nq5wspvncy4m" + + cleanedInvoiceStr := SantizeBoltInvoice(invoiceStr) + + assert.True(t, strings.HasPrefix(cleanedInvoiceStr, "lnbc10")) +} diff --git a/internal/tui/dashboard.go b/internal/tui/dashboard.go index 5cf606c..165e780 100644 --- a/internal/tui/dashboard.go +++ b/internal/tui/dashboard.go @@ -257,17 +257,20 @@ func (m *DashboardModel) handleFormClick(component dashboardComponent) (tea.Mode switch component { case paymentTools: if m.forms[0].GetString("payments") == OPTION_PAYMENT_RECEIVE { - m.forms[0] = m.generatePaymentToolsForm() i = newInvoiceModel(m.ctx, &m.base, m.lndService, StateNone) + } else { + i = newPayInvoiceModel(m.lndService, &m.base) } + m.forms[0] = m.generatePaymentToolsForm() case messageTools: if m.forms[2].GetString("messages") == OPTION_MESSAGE_SIGN { - m.forms[2] = m.generateMessageToolsForm() + i = newSignMessageModel(m.lndService, &m.base) } else { - m.forms[2] = m.generateMessageToolsForm() + i = newVerifyMessageModel(m.lndService, &m.base) } + m.forms[2] = m.generateMessageToolsForm() } return i.Update(windowSizeMsg) diff --git a/internal/tui/pay_invoice.go b/internal/tui/pay_invoice.go new file mode 100644 index 0000000..37a0cbd --- /dev/null +++ b/internal/tui/pay_invoice.go @@ -0,0 +1,214 @@ +package tui + +import ( + "context" + "fmt" + "strings" + + "github.com/ardevd/flash/internal/lnd" + "github.com/btcsuite/btcd/btcutil" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/routing/route" +) + +// Model +type PayInvoiceModel struct { + styles *Styles + lndService *lndclient.GrpcLndServices + ctx context.Context + base *BaseModel + keys keyMap + form *huh.Form + invoiceState PaymentState + spinner spinner.Model +} + +// PaymentState indicates the state of a Bolt 11 invoice payment +type PaymentState int + +const ( + // PaymentStateNone is when the invoice is yet to be generated + PaymentStateNone PaymentState = iota + + // PaymentStateDecoded is when the invoice has been and parsed. + PaymentStateDecoded + + // PaymentStateSending is when the invoice payment is sending + PaymentStateSending + + // PaymentStateSettled is when the invoice has settled + PaymentStateSettled +) + +// Value container +var invoiceString string + +// Instantiate model +func newPayInvoiceModel(service *lndclient.GrpcLndServices, base *BaseModel) *PayInvoiceModel { + m := PayInvoiceModel{lndService: service, base: base, ctx: context.Background(), keys: Keymap} + m.styles = GetDefaultStyles() + m.base.pushView(&m) + m.form = getInvoicePaymentForm() + m.invoiceState = PaymentStateNone + s := spinner.New() + s.Spinner = spinner.Dot + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) + m.spinner = s + + return &m +} + +// Model update logic +func (m *PayInvoiceModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + // Handle base model events + model, cmd := m.base.Update(msg) + if model != nil { + return model, cmd + } + + var cmds []tea.Cmd + switch msg := msg.(type) { + case tea.WindowSizeMsg: + windowSizeMsg = msg + + case tea.KeyMsg: + switch { + // Enter will pay the invoice is model is in appropriate state + case key.Matches(msg, Keymap.Enter): + if m.form.State == huh.StateCompleted && m.invoiceState == PaymentStateNone { + m.invoiceState = PaymentStateSending + return m, paymentCreatedMsg + } + } + // Payment has been decoded and issued. + case paymentCreated: + cmds = append(cmds, m.payInvoice) + // Payment failed + case paymentError: + m.invoiceState = PaymentStateNone + // Payment has been settled + case paymentSettled: + m.invoiceState = PaymentStateSettled + } + + // Tick the spinner + m.spinner, cmd = m.spinner.Update(msg) + cmds = append(cmds, cmd) + + // Process the invoice form + if m.form != nil { + form, cmd := m.form.Update(msg) + if f, ok := form.(*huh.Form); ok { + m.form = f + cmds = append(cmds, cmd) + } + } + + return m, tea.Batch(cmds...) +} + +// Get the invoice payment form +func getInvoicePaymentForm() *huh.Form { + form := huh.NewForm( + huh.NewGroup(huh.NewNote(). + Title("Pay Invoice"). + Description("Pay the provided invoice"), + huh.NewInput(). + Title("BOLT11 Invoice"). + Prompt(">"). + Value(&invoiceString))) + + form.NextField() + return form +} + +// Init +func (m PayInvoiceModel) Init() tea.Cmd { + return m.spinner.Tick +} + +// Model view logic +func (m PayInvoiceModel) View() string { + s := m.styles + v := strings.TrimSuffix(m.form.View(), "\n") + form := lipgloss.DefaultRenderer().NewStyle().Margin(1, 0).Render(v) + if m.invoiceState == PaymentStateSending { + return lipgloss.JoinVertical(lipgloss.Left, s.BorderedStyle.Render(m.getPaymentPendingView())) + } else if m.invoiceState == PaymentStateSettled { + return lipgloss.JoinVertical(lipgloss.Left, s.BorderedStyle.Render(m.getPaymentSettledView())) + } else if m.form.State == huh.StateCompleted { + return lipgloss.JoinVertical(lipgloss.Left, s.BorderedStyle.Render(fmt.Sprintf("\n%s\n", s.HeaderText.Render("Pay Invoice?"))+ + "\n"+m.decodeInvoice())) + } + return lipgloss.JoinVertical(lipgloss.Left, form) +} + +// Get the Payment pending view +func (m PayInvoiceModel) getPaymentPendingView() string { + + return m.styles.HeaderText.Render("Invoice in flight") + "\n\n" + + fmt.Sprintf("\n\n %s Sending payment\n\n", m.spinner.View()) +} + +// Get the Payment settled view +func (m PayInvoiceModel) getPaymentSettledView() string { + s := m.styles + return s.HeaderText.Render("Invoice settled") + "\n\n" + + s.PositiveString("The invoice was successfully settled") + "\n" + + "Press Esc to return" +} + +// Get node name for a given public key +func (m PayInvoiceModel) getNodeName(pubkey route.Vertex) string { + nodeInfo, err := m.lndService.Client.GetNodeInfo(m.ctx, pubkey, false) + if err != nil { + return "" + } + + return nodeInfo.Alias +} + +// Decode an invoice string +func (m PayInvoiceModel) decodeInvoice() string { + // Decode the invoice string + invoiceString = lnd.SantizeBoltInvoice(invoiceString) + decodedInvoice, err := m.lndService.Client.DecodePaymentRequest(m.ctx, invoiceString) + if err != nil { + return "Error decoding invoice: " + err.Error() + } + + amountInSats := decodedInvoice.Value.ToSatoshis() + + s := m.styles + return s.Keyword("Amount: ") + amountInSats.String() + "\n" + + s.Keyword("To: ") + decodedInvoice.Destination.String() + "\n" + + s.Keyword("Node: ") + m.getNodeName(decodedInvoice.Destination) + "\n" + + s.Keyword("Description: ") + decodedInvoice.Description + "\n\n" + + s.SubKeyword("Press Enter to accept, Esc to cancel") +} + +// Pay the invoice +func (m *PayInvoiceModel) payInvoice() tea.Msg { + result := m.lndService.Client.PayInvoice(m.ctx, invoiceString, btcutil.Amount(10), nil) + defer close(result) + completion := make(chan tea.Msg) + + defer close(completion) + go func() { + for update := range result { + if update.Err != nil { + fmt.Println(update.Err.Error()) + completion <- paymentError{} + } else { + completion <- paymentSettled{} + } + } + }() + + return <-completion +} diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 21d426b..c0ae324 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -19,6 +19,7 @@ type DataLoaded lnd.NodeData type paymentSettled struct{} type paymentExpired struct{} type paymentCreated struct{} +type paymentError struct{} func GetData(service *lndclient.GrpcLndServices, ctx context.Context) lnd.NodeData { var nodeData lnd.NodeData