Skip to content

Commit

Permalink
fix: LND listTransactions
Browse files Browse the repository at this point in the history
- unpaid check
- decode bolt11 to add extra properties to payments
- sort by created date desc
  • Loading branch information
rolznz committed Dec 15, 2023
1 parent 16b9757 commit 1adb12f
Showing 1 changed file with 77 additions and 50 deletions.
127 changes: 77 additions & 50 deletions lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"crypto/sha256"
"encoding/hex"
"errors"
"sort"
"time"

"github.com/getAlby/nostr-wallet-connect/lnd"
decodepay "github.com/nbd-wtf/ln-decodepay"

"github.com/sirupsen/logrus"
"gorm.io/gorm"
Expand Down Expand Up @@ -57,71 +59,96 @@ func (svc *LNDService) GetBalance(ctx context.Context, senderPubkey string) (bal
return int64(resp.LocalBalance.Sat), nil
}

func (svc *LNDService) ListTransactions(ctx context.Context, senderPubkey string, from, until, limit, offset uint64, unpaid bool, invoiceType string) (invoices []Nip47Transaction, err error) {
maxInvoices := uint64(limit)
if err != nil {
return nil, err
}
indexOffset := uint64(offset)
if err != nil {
return nil, err
}
// Fetch Incoming Payments
var incomingInvoices []*lnrpc.Invoice
func (svc *LNDService) ListTransactions(ctx context.Context, senderPubkey string, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Nip47Transaction, err error) {
// Fetch invoices
var invoices []*lnrpc.Invoice
if invoiceType == "" || invoiceType == "incoming" {
incomingResp, err := svc.client.ListInvoices(ctx, &lnrpc.ListInvoiceRequest{NumMaxInvoices: maxInvoices, IndexOffset: indexOffset})
incomingResp, err := svc.client.ListInvoices(ctx, &lnrpc.ListInvoiceRequest{NumMaxInvoices: limit, IndexOffset: offset})
if err != nil {
return nil, err
}
incomingInvoices = incomingResp.Invoices

if unpaid {
incomingUnpaidResp, err := svc.client.ListInvoices(ctx, &lnrpc.ListInvoiceRequest{NumMaxInvoices: maxInvoices, IndexOffset: indexOffset, PendingOnly: true})
if err != nil {
return nil, err
}
incomingInvoices = append(incomingInvoices, incomingUnpaidResp.Invoices...)
}
invoices = incomingResp.Invoices
}
for _, inv := range incomingInvoices {
invoice := Nip47Transaction{
for _, invoice := range invoices {
// this will cause retrieved amount to be less than limit if unpaid is false
if !unpaid && invoice.State != lnrpc.Invoice_SETTLED {
continue
}

transaction := Nip47Transaction{
Type: "incoming",
Invoice: inv.PaymentRequest,
Description: inv.Memo,
DescriptionHash: hex.EncodeToString(inv.DescriptionHash),
Preimage: hex.EncodeToString(inv.RPreimage),
PaymentHash: hex.EncodeToString(inv.RHash),
Amount: inv.ValueMsat,
FeesPaid: inv.AmtPaidMsat,
CreatedAt: time.Unix(inv.CreationDate, 0),
SettledAt: time.Unix(inv.SettleDate, 0),
ExpiresAt: time.Unix(inv.CreationDate+inv.Expiry, 0),
Invoice: invoice.PaymentRequest,
Description: invoice.Memo,
DescriptionHash: hex.EncodeToString(invoice.DescriptionHash),
Preimage: hex.EncodeToString(invoice.RPreimage),
PaymentHash: hex.EncodeToString(invoice.RHash),
Amount: invoice.ValueMsat,
FeesPaid: invoice.AmtPaidMsat,
CreatedAt: time.Unix(invoice.CreationDate, 0),
SettledAt: time.Unix(invoice.SettleDate, 0),
ExpiresAt: time.Unix(invoice.CreationDate+invoice.Expiry, 0),
// TODO: Metadata (e.g. keysend)
}
invoices = append(invoices, invoice)
transactions = append(transactions, transaction)
}
// Fetch Outgoing Invoices
var outgoingInvoices []*lnrpc.Payment
if invoiceType == "" || invoiceType == "incoming" {
// Fetch payments
var payments []*lnrpc.Payment
if invoiceType == "" || invoiceType == "outgoing" {
// Not just pending but failed payments will also be included because of IncludeIncomplete
outgoingResp, err := svc.client.ListPayments(ctx, &lnrpc.ListPaymentsRequest{MaxPayments: maxInvoices, IndexOffset: indexOffset, IncludeIncomplete: unpaid})
outgoingResp, err := svc.client.ListPayments(ctx, &lnrpc.ListPaymentsRequest{MaxPayments: limit, IndexOffset: offset, IncludeIncomplete: unpaid})
if err != nil {
return nil, err
}
outgoingInvoices = outgoingResp.Payments
payments = outgoingResp.Payments
}
for _, inv := range outgoingInvoices {
invoice := Nip47Transaction{
Type: "outgoing",
Invoice: inv.PaymentRequest,
Preimage: inv.PaymentPreimage,
PaymentHash: inv.PaymentHash,
Amount: inv.ValueMsat,
FeesPaid: inv.FeeMsat,
CreatedAt: time.Unix(0, inv.CreationTimeNs),
for _, payment := range payments {
var paymentRequest decodepay.Bolt11
var expiresAt time.Time
var description string
var descriptionHash string
if payment.PaymentRequest != "" {
paymentRequest, err = decodepay.Decodepay(payment.PaymentRequest)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"bolt11": payment.PaymentRequest,
}).Errorf("Failed to decode bolt11 invoice: %v", err)

return nil, err
}
expiresAt = time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second)
description = paymentRequest.Description
descriptionHash = paymentRequest.DescriptionHash
}
invoices = append(invoices, invoice)

var settledAt time.Time
if payment.Status == lnrpc.Payment_SUCCEEDED {
// FIXME: how to get the actual settled at time?
settledAt = time.Unix(0, payment.CreationTimeNs)
}

transaction := Nip47Transaction{
Type: "outgoing",
Invoice: payment.PaymentRequest,
Preimage: payment.PaymentPreimage,
PaymentHash: payment.PaymentHash,
Amount: payment.ValueMsat,
FeesPaid: payment.FeeMsat,
CreatedAt: time.Unix(0, payment.CreationTimeNs),
Description: description,
DescriptionHash: descriptionHash,
ExpiresAt: expiresAt,
SettledAt: settledAt,
//TODO: Metadata: (e.g. keysend),
}
transactions = append(transactions, transaction)
}
return invoices, nil

// sort by created date descending
sort.SliceStable(transactions, func(i, j int) bool {
return transactions[i].CreatedAt.After(transactions[j].CreatedAt)
})

return transactions, nil
}

func (svc *LNDService) GetInfo(ctx context.Context, senderPubkey string) (info *NodeInfo, err error) {
Expand Down

0 comments on commit 1adb12f

Please sign in to comment.