mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: add hold invoices support for LND & LDK (#1298)
* feat: add LND hold invoices support * fix: add HOLD_INVOICE_ACCEPTED_NOTIFICATION to notifications list returned by get_info * fix: revert * fix: remove unneeded null checks * docs: add extra event type to README * feat: add LND hold invoices support * fix: add HOLD_INVOICE_ACCEPTED_NOTIFICATION to notifications list returned by get_info * fix: revert * fix: remove unneeded null checks * docs: add extra event type to README * fix: remove 0 expiry checking in the make_hold_invoice_controller * fix: use JSON logging * revert fly.toml * fix: duplicated check * fix: move publishing nwc_hold_invoice_accepted out of the transaction * fix: move publishing nwc_hold_invoice_accepted out of the transaction * fix: check the invoice state ACCEPTED before calling the lnClient.SettleHoldInvoice * fix: check the invoice state ACCEPTED before calling the lnClient.SettleHoldInvoice * fix: check the invoice state ACCEPTED before calling the lnClient.SettleHoldInvoice * fix: check the invoice state ACCEPTED before calling the lnClient.SettleHoldInvoice * fix: cancel hold invoice tests * fix: make hold invoice tests * fix: make hold invoice tests * fix: settle hold invoice tests * fix: resubscribe to pending hold invoices * fix: missing WatchHoldInvoice in the mocks * feat: add LDK impl * fix: cleanup * fix: payment_hash * fix: remove unneeded update * feat: add support for self payments for hold invoices (#1304) Co-authored-by: frnandu <frnandu@gmail.com> * fix: remove hold invoices scope * fix: remove hold invoices scope * fix: mock hold bolt11 expiry to 10 years * fix: sleep 1 second to give a change of lookupinvoice to read cancelled * fix: missing timeout param * feat: add hold invoice settle deadline to transactions (#1324) * chore: update mockery, remove unused hold invoice method * chore: remove unnecessary comment * chore: remove unused code * fix: do not return hold transaction if lookup failed * chore: remove unused code * fix: return correct errors from nip47 controllers, remove unused code * fix: failing test * chore: remove unnecessary code * fix: make error message more general * chore: remove unused code * fix: use correct context in lnd service * fix: unstable hold payments test --------- Co-authored-by: Roland Bewick <roland.bewick@gmail.com> Co-authored-by: Roland <33993199+rolznz@users.noreply.github.com>
This commit is contained in:
parent
56fa7c89cf
commit
04fc9c90dc
47 changed files with 2508 additions and 839 deletions
|
|
@ -1,19 +1,14 @@
|
|||
filename: "{{.InterfaceName}}.go"
|
||||
dir: tests/mocks
|
||||
outpkg: mocks
|
||||
|
||||
# Fix deprecation warnings:
|
||||
issue-845-fix: True
|
||||
resolve-type-alias: False
|
||||
|
||||
pkgname: mocks
|
||||
template: testify
|
||||
packages:
|
||||
github.com/getAlby/hub/service:
|
||||
interfaces:
|
||||
Service:
|
||||
|
||||
github.com/getAlby/hub/lnclient:
|
||||
interfaces:
|
||||
LNClient:
|
||||
github.com/getAlby/hub/config:
|
||||
interfaces:
|
||||
Config:
|
||||
Config: {}
|
||||
github.com/getAlby/hub/lnclient:
|
||||
interfaces:
|
||||
LNClient: {}
|
||||
github.com/getAlby/hub/service:
|
||||
interfaces:
|
||||
Service: {}
|
||||
|
|
|
|||
|
|
@ -529,6 +529,8 @@ Internally Alby Hub uses a basic implementation of the pubsub messaging pattern
|
|||
- `nwc_payment_failed` - failed to make a lightning payment
|
||||
- `nwc_payment_sent` - successfully made a lightning payment
|
||||
- `nwc_payment_received` - received a lightning payment
|
||||
- `nwc_hold_invoice_accepted` - accepted a lightning payment, but it needs to be cancelled or settled
|
||||
- `nwc_hold_invoice_canceled` - accepted hold payment was explicitly cancelled
|
||||
- `nwc_budget_warning` - successfully made a lightning payment, but budget is nearly exceeded
|
||||
- `nwc_app_created` - a new app connection was created
|
||||
- `nwc_app_deleted` - a new app connection was deleted
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const (
|
|||
TRANSACTION_STATE_PENDING = "PENDING"
|
||||
TRANSACTION_STATE_SETTLED = "SETTLED"
|
||||
TRANSACTION_STATE_FAILED = "FAILED"
|
||||
TRANSACTION_STATE_ACCEPTED = "ACCEPTED"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
26
db/migrations/202504231037_hold_invoices.go
Normal file
26
db/migrations/202504231037_hold_invoices.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _202505091314_hold_invoices = &gormigrate.Migration{
|
||||
ID: "202505091314_hold_invoices",
|
||||
Migrate: func(db *gorm.DB) error {
|
||||
|
||||
if err := db.Exec(`
|
||||
ALTER TABLE transactions ADD COLUMN hold BOOLEAN;
|
||||
ALTER TABLE transactions ADD COLUMN settle_deadline integer;
|
||||
`).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ func Migrate(gormDB *gorm.DB) error {
|
|||
_202410141503_add_wallet_pubkey,
|
||||
_202412212345_fix_types,
|
||||
_202504231037_add_indexes,
|
||||
_202505091314_hold_invoices,
|
||||
})
|
||||
|
||||
return m.Migrate()
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ type Transaction struct {
|
|||
SelfPayment bool
|
||||
Boostagram datatypes.JSON
|
||||
FailureReason string
|
||||
Hold bool
|
||||
SettleDeadline *uint32 // block number for accepted hold invoices
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -126,7 +126,12 @@ const NewAppInternal = ({ capabilities }: NewAppInternalProps) => {
|
|||
if (requestMethodsSet.has("get_balance")) {
|
||||
scopes.push("get_balance");
|
||||
}
|
||||
if (requestMethodsSet.has("make_invoice")) {
|
||||
if (
|
||||
requestMethodsSet.has("make_invoice") ||
|
||||
requestMethodsSet.has("make_hold_invoice") ||
|
||||
requestMethodsSet.has("settle_hold_invoice") ||
|
||||
requestMethodsSet.has("cancel_hold_invoice")
|
||||
) {
|
||||
scopes.push("make_invoice");
|
||||
}
|
||||
if (requestMethodsSet.has("lookup_invoice")) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ export type Nip47RequestMethod =
|
|||
| "list_transactions"
|
||||
| "sign_message"
|
||||
| "multi_pay_invoice"
|
||||
| "multi_pay_keysend";
|
||||
| "multi_pay_keysend"
|
||||
| "make_hold_invoice"
|
||||
| "settle_hold_invoice"
|
||||
| "cancel_hold_invoice";
|
||||
|
||||
export type BudgetRenewalType =
|
||||
| "daily"
|
||||
|
|
|
|||
|
|
@ -120,6 +120,18 @@ func (cs *CashuService) MakeInvoice(ctx context.Context, amount int64, descripti
|
|||
return cs.cashuMintQuoteToTransaction(mintQuote), nil
|
||||
}
|
||||
|
||||
func (cs *CashuService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (cs *CashuService) SettleHoldInvoice(ctx context.Context, preimage string) (err error) {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (cs *CashuService) CancelHoldInvoice(ctx context.Context, paymentHash string) (err error) {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (cs *CashuService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
mintQuote := cs.getMintQuoteByPaymentHash(paymentHash)
|
||||
if mintQuote != nil {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import (
|
|||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/lsp"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/getAlby/hub/nip47/notifications"
|
||||
"github.com/getAlby/hub/service/keys"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
)
|
||||
|
|
@ -1602,6 +1604,35 @@ func (ls *LDKService) handleLdkEvent(event *ldk_node.Event) {
|
|||
"total_fee_earned_msat": eventType.TotalFeeEarnedMsat,
|
||||
"outbound_amount_forwarded_msat": eventType.OutboundAmountForwardedMsat,
|
||||
}).Info("LDK Payment forwarded")
|
||||
|
||||
case ldk_node.EventPaymentClaimable:
|
||||
if eventType.ClaimDeadline == nil {
|
||||
logger.Logger.WithField("payment_id", eventType.PaymentId).Error("claimable payment has no claim deadline")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"claimable_amount_msats": eventType.ClaimableAmountMsat,
|
||||
"payment_hash": eventType.PaymentHash,
|
||||
"claim_deadline": *eventType.ClaimDeadline,
|
||||
}).Info("LDK Payment Claimable")
|
||||
|
||||
payment := ls.node.Payment(eventType.PaymentId)
|
||||
if payment == nil {
|
||||
logger.Logger.WithField("payment_id", eventType.PaymentId).Error("could not find LDK payment")
|
||||
return
|
||||
}
|
||||
|
||||
transaction, err := ls.ldkPaymentToTransaction(payment)
|
||||
if err != nil {
|
||||
logger.Logger.WithField("payment_id", eventType.PaymentId).Error("failed to convert LDK payment to transaction")
|
||||
return
|
||||
}
|
||||
transaction.SettleDeadline = eventType.ClaimDeadline
|
||||
ls.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_lnclient_hold_invoice_accepted",
|
||||
Properties: transaction,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1823,11 +1854,30 @@ func (ls *LDKService) UpdateLastWalletSyncRequest() {
|
|||
}
|
||||
|
||||
func (ls *LDKService) GetSupportedNIP47Methods() []string {
|
||||
return []string{"pay_invoice", "pay_keysend", "get_balance", "get_budget", "get_info", "make_invoice", "lookup_invoice", "list_transactions", "multi_pay_invoice", "multi_pay_keysend", "sign_message"}
|
||||
return []string{
|
||||
models.PAY_INVOICE_METHOD,
|
||||
models.PAY_KEYSEND_METHOD,
|
||||
models.GET_BALANCE_METHOD,
|
||||
models.GET_BUDGET_METHOD,
|
||||
models.GET_INFO_METHOD,
|
||||
models.MAKE_INVOICE_METHOD,
|
||||
models.LOOKUP_INVOICE_METHOD,
|
||||
models.LIST_TRANSACTIONS_METHOD,
|
||||
models.MULTI_PAY_INVOICE_METHOD,
|
||||
models.MULTI_PAY_KEYSEND_METHOD,
|
||||
models.SIGN_MESSAGE_METHOD,
|
||||
models.MAKE_HOLD_INVOICE_METHOD,
|
||||
models.SETTLE_HOLD_INVOICE_METHOD,
|
||||
models.CANCEL_HOLD_INVOICE_METHOD,
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *LDKService) GetSupportedNIP47NotificationTypes() []string {
|
||||
return []string{"payment_received", "payment_sent"}
|
||||
return []string{
|
||||
notifications.PAYMENT_RECEIVED_NOTIFICATION,
|
||||
notifications.PAYMENT_SENT_NOTIFICATION,
|
||||
notifications.HOLD_INVOICE_ACCEPTED_NOTIFICATION,
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *LDKService) getPaymentFailReason(eventPaymentFailed *ldk_node.EventPaymentFailed) string {
|
||||
|
|
@ -1912,6 +1962,133 @@ func (ls *LDKService) ExecuteCustomNodeCommand(ctx context.Context, command *lnc
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (ls *LDKService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (*lnclient.Transaction, error) {
|
||||
if time.Duration(expiry)*time.Second > maxInvoiceExpiry {
|
||||
return nil, errors.New("expiry is too long")
|
||||
}
|
||||
|
||||
maxReceivable := ls.getMaxReceivable()
|
||||
|
||||
if amount > maxReceivable {
|
||||
ls.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_incoming_liquidity_required",
|
||||
Properties: map[string]interface{}{
|
||||
"node_type": config.LDKBackendType,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if expiry == 0 {
|
||||
expiry = lnclient.DEFAULT_INVOICE_EXPIRY
|
||||
}
|
||||
|
||||
var descriptionType ldk_node.Bolt11InvoiceDescription
|
||||
descriptionType = ldk_node.Bolt11InvoiceDescriptionDirect{
|
||||
Description: description,
|
||||
}
|
||||
if description == "" && descriptionHash != "" {
|
||||
descriptionType = ldk_node.Bolt11InvoiceDescriptionHash{
|
||||
Hash: descriptionHash,
|
||||
}
|
||||
}
|
||||
|
||||
decodedPaymentHash, err := hex.DecodeString(paymentHash)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Error("Failed to decode payment hash for MakeHoldInvoice")
|
||||
return nil, fmt.Errorf("failed to decode payment hash: %w", err)
|
||||
}
|
||||
if len(decodedPaymentHash) != 32 {
|
||||
return nil, errors.New("payment hash must be 32 bytes")
|
||||
}
|
||||
var paymentHash32 [32]byte
|
||||
copy(paymentHash32[:], decodedPaymentHash)
|
||||
|
||||
ldkPaymentHash := ldk_node.PaymentHash(hex.EncodeToString(paymentHash32[:]))
|
||||
|
||||
invoice, err := checkLDKErr(ls.node.Bolt11Payment().ReceiveForHash(uint64(amount),
|
||||
descriptionType,
|
||||
uint32(expiry),
|
||||
ldkPaymentHash))
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("MakeHoldInvoice failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var expiresAt *int64
|
||||
paymentRequest, err := decodepay.Decodepay(invoice)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"bolt11": invoice,
|
||||
}).WithError(err).Error("Failed to decode bolt11 invoice")
|
||||
return nil, err
|
||||
}
|
||||
expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
|
||||
expiresAt = &expiresAtUnix
|
||||
description = paymentRequest.Description
|
||||
descriptionHash = paymentRequest.DescriptionHash
|
||||
|
||||
transaction := &lnclient.Transaction{
|
||||
Type: "incoming",
|
||||
Invoice: invoice,
|
||||
PaymentHash: paymentRequest.PaymentHash,
|
||||
Amount: amount,
|
||||
CreatedAt: int64(paymentRequest.CreatedAt),
|
||||
ExpiresAt: expiresAt,
|
||||
Description: description,
|
||||
DescriptionHash: descriptionHash,
|
||||
}
|
||||
|
||||
return transaction, nil
|
||||
}
|
||||
|
||||
func (ls *LDKService) CancelHoldInvoice(ctx context.Context, paymentHash string) error {
|
||||
_, err := hex.DecodeString(paymentHash)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Error("Failed to decode payment hash for CancelHoldInvoice")
|
||||
return err
|
||||
}
|
||||
|
||||
err = ls.node.Bolt11Payment().FailForHash(paymentHash).AsError()
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Error("CancelHoldInvoice failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ls *LDKService) SettleHoldInvoice(ctx context.Context, preimage string) error {
|
||||
decodedPreimage, err := hex.DecodeString(preimage)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("preimage", preimage).Error("Failed to decode preimage for SettleHoldInvoice")
|
||||
return err
|
||||
}
|
||||
if len(decodedPreimage) != 32 {
|
||||
return errors.New("preimage must be 32 bytes")
|
||||
}
|
||||
|
||||
paymentHash256 := sha256.New()
|
||||
paymentHash256.Write(decodedPreimage)
|
||||
paymentHashBytes := paymentHash256.Sum(nil)
|
||||
paymentHash := hex.EncodeToString(paymentHashBytes)
|
||||
|
||||
paymentDetails := ls.node.Payment(paymentHash)
|
||||
|
||||
if paymentDetails == nil {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("SettleHoldInvoice: Could not find payment by derived hash")
|
||||
return errors.New("payment not found for derived hash")
|
||||
}
|
||||
if paymentDetails.AmountMsat == nil {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("SettleHoldInvoice: Payment has no amount_msat")
|
||||
return errors.New("payment has no amount_msat")
|
||||
}
|
||||
|
||||
err = ls.node.Bolt11Payment().ClaimForHash(paymentHash, *paymentDetails.AmountMsat, preimage).AsError()
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("preimage", preimage).WithField("derived_payment_hash", paymentHash).Error("SettleHoldInvoice failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func GetVssNodeIdentifier(keys keys.Keys) (string, error) {
|
||||
key, err := keys.DeriveKey([]uint32{bip32.FirstHardenedChild + 2})
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,15 @@ import (
|
|||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/lnclient/lnd/wrapper"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/getAlby/hub/nip47/notifications"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
// "gorm.io/gorm"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
)
|
||||
|
||||
|
|
@ -37,6 +40,7 @@ type LNDService struct {
|
|||
client *wrapper.LNDWrapper
|
||||
nodeInfo *lnclient.NodeInfo
|
||||
cancel context.CancelFunc
|
||||
ctx context.Context
|
||||
eventPublisher events.EventPublisher
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +83,14 @@ func NewLNDService(ctx context.Context, eventPublisher events.EventPublisher, ln
|
|||
client: lndClient,
|
||||
nodeInfo: nodeInfo,
|
||||
cancel: cancel,
|
||||
ctx: lndCtx,
|
||||
eventPublisher: eventPublisher,
|
||||
}
|
||||
|
||||
go lndService.subscribePayments(lndCtx)
|
||||
go lndService.subscribeInvoices(lndCtx)
|
||||
go lndService.subscribeChannelEvents(lndCtx)
|
||||
go lndService.subscribeOpenHoldInvoices(lndCtx)
|
||||
|
||||
logger.Logger.WithField("alias", nodeInfo.Alias).Info("Connected to LND")
|
||||
|
||||
|
|
@ -279,6 +285,107 @@ func (svc *LNDService) subscribeChannelEvents(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (svc *LNDService) subscribeOpenHoldInvoices(ctx context.Context) {
|
||||
oneWeekAgo := time.Now().AddDate(0, 0, -7).Unix()
|
||||
|
||||
listInvoicesResponse, err := svc.client.ListInvoices(ctx, &lnrpc.ListInvoiceRequest{
|
||||
PendingOnly: true,
|
||||
CreationDateStart: uint64(oneWeekAgo),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to list invoices for open hold invoices subscription")
|
||||
return
|
||||
}
|
||||
|
||||
for _, invoice := range listInvoicesResponse.Invoices {
|
||||
if invoice.State == lnrpc.Invoice_OPEN {
|
||||
paymentHashHex := hex.EncodeToString(invoice.RHash)
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHashHex,
|
||||
"addIndex": invoice.AddIndex,
|
||||
}).Info("Resubscribing to pending hold invoice")
|
||||
go svc.subscribeSingleInvoice(invoice.RHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *LNDService) subscribeSingleInvoice(paymentHashBytes []byte) {
|
||||
// Use the global context for the lifetime of this subscription, but create a cancellable one for this specific task
|
||||
// This allows the goroutine to be potentially cancelled externally if needed, though it primarily exits on invoice state change.
|
||||
// We use a background context derived from the global one to avoid cancelling if the original request context finishes.
|
||||
ctx, cancel := context.WithCancel(svc.ctx)
|
||||
defer cancel() // Ensure cancellation happens on exit
|
||||
|
||||
paymentHashHex := hex.EncodeToString(paymentHashBytes)
|
||||
log := logger.Logger.WithField("paymentHash", paymentHashHex)
|
||||
|
||||
log.Info("Starting subscribeSingleInvoice goroutine")
|
||||
|
||||
subReq := &invoicesrpc.SubscribeSingleInvoiceRequest{
|
||||
RHash: paymentHashBytes,
|
||||
}
|
||||
|
||||
invoiceStream, err := svc.client.SubscribeSingleInvoice(ctx, subReq)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("SubscribeSingleInvoice call failed")
|
||||
// Goroutine will exit
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("Successfully subscribed to single invoice stream")
|
||||
|
||||
defer func() {
|
||||
log.Info("Exiting subscribeSingleInvoice goroutine")
|
||||
if r := recover(); r != nil {
|
||||
log.WithField("panic", r).Errorf("PANIC recovered in single invoice stream processing")
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
invoice, err := invoiceStream.Recv()
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to receive single invoice update from stream")
|
||||
return
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
log.Info("Context cancelled, exiting single invoice subscription loop")
|
||||
return
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"rawState": invoice.State.String(),
|
||||
"addIndex": invoice.AddIndex,
|
||||
"settleIndex": invoice.SettleIndex,
|
||||
"amtPaidMsat": invoice.AmtPaidMsat,
|
||||
}).Info("Raw update received from single invoice stream")
|
||||
|
||||
switch invoice.State {
|
||||
case lnrpc.Invoice_ACCEPTED:
|
||||
log.Info("Hold invoice accepted, publishing internal event")
|
||||
transaction := lndInvoiceToTransaction(invoice)
|
||||
var minExpiry uint32
|
||||
for _, htlc := range invoice.Htlcs {
|
||||
if htlc.ExpiryHeight < int32(minExpiry) || minExpiry == 0 {
|
||||
minExpiry = uint32(htlc.ExpiryHeight)
|
||||
}
|
||||
}
|
||||
transaction.SettleDeadline = &minExpiry
|
||||
svc.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_lnclient_hold_invoice_accepted",
|
||||
Properties: transaction,
|
||||
})
|
||||
case lnrpc.Invoice_CANCELED:
|
||||
log.Info("Hold invoice canceled, ending subscription")
|
||||
return // Invoice reached final state, exit goroutine
|
||||
case lnrpc.Invoice_SETTLED:
|
||||
return // Invoice reached final state, exit goroutine
|
||||
case lnrpc.Invoice_OPEN:
|
||||
// Continue loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *LNDService) Shutdown() error {
|
||||
logger.Logger.Info("cancelling LND context")
|
||||
svc.cancel()
|
||||
|
|
@ -512,6 +619,127 @@ func (svc *LNDService) MakeInvoice(ctx context.Context, amount int64, descriptio
|
|||
return transaction, nil
|
||||
}
|
||||
|
||||
func (svc *LNDService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
var descriptionHashBytes []byte
|
||||
var paymentHashBytes []byte
|
||||
|
||||
if descriptionHash != "" {
|
||||
descriptionHashBytes, err = hex.DecodeString(descriptionHash)
|
||||
if err != nil || len(descriptionHashBytes) != 32 {
|
||||
if err == nil {
|
||||
err = errors.New("description hash must be 32 bytes hex")
|
||||
}
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"descriptionHash": descriptionHash,
|
||||
}).WithError(err).Error("Invalid description hash")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
paymentHashBytes, err = hex.DecodeString(paymentHash)
|
||||
if err != nil || len(paymentHashBytes) != 32 {
|
||||
if err == nil {
|
||||
err = errors.New("payment hash must be 32 bytes hex")
|
||||
}
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
}).WithError(err).Error("Invalid payment hash")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expiry == 0 {
|
||||
expiry = lnclient.DEFAULT_INVOICE_EXPIRY
|
||||
}
|
||||
|
||||
channels, err := svc.ListChannels(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasPublicChannels := false
|
||||
for _, channel := range channels {
|
||||
if channel.Active && channel.Public {
|
||||
hasPublicChannels = true
|
||||
}
|
||||
}
|
||||
|
||||
addInvoiceRequest := &invoicesrpc.AddHoldInvoiceRequest{
|
||||
ValueMsat: amount,
|
||||
Memo: description,
|
||||
DescriptionHash: descriptionHashBytes,
|
||||
Expiry: expiry,
|
||||
Private: !hasPublicChannels,
|
||||
Hash: paymentHashBytes,
|
||||
}
|
||||
|
||||
_, err = svc.client.AddHoldInvoice(ctx, addInvoiceRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to create hold invoice")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start subscribing to updates for this specific hold invoice in a separate goroutine
|
||||
go svc.subscribeSingleInvoice(paymentHashBytes)
|
||||
logger.Logger.WithField("paymentHash", paymentHash).Info("Launched single invoice subscription goroutine")
|
||||
|
||||
inv, err := svc.client.LookupInvoice(ctx, &lnrpc.PaymentHash{RHash: paymentHashBytes})
|
||||
if err != nil {
|
||||
logger.Logger.WithField("paymentHash", paymentHash).WithError(err).Error("Failed to lookup hold invoice after creation")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transaction = lndInvoiceToTransaction(inv)
|
||||
return transaction, nil
|
||||
}
|
||||
|
||||
func (svc *LNDService) SettleHoldInvoice(ctx context.Context, preimage string) (err error) {
|
||||
preimageBytes, err := hex.DecodeString(preimage)
|
||||
if err != nil || len(preimageBytes) != 32 {
|
||||
if err == nil {
|
||||
err = errors.New("preimage must be 32 bytes hex")
|
||||
}
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"preimage": preimage,
|
||||
}).WithError(err).Error("Invalid preimage")
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.client.SettleInvoice(ctx, &invoicesrpc.SettleInvoiceMsg{
|
||||
Preimage: preimageBytes,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"preimage": preimage,
|
||||
}).WithError(err).Error("Failed to settle hold invoice")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *LNDService) CancelHoldInvoice(ctx context.Context, paymentHash string) (err error) {
|
||||
paymentHashBytes, err := hex.DecodeString(paymentHash)
|
||||
if err != nil || len(paymentHashBytes) != 32 {
|
||||
if err == nil {
|
||||
err = errors.New("payment hash must be 32 bytes hex")
|
||||
}
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
}).WithError(err).Error("Invalid payment hash")
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.client.CancelInvoice(ctx, &invoicesrpc.CancelInvoiceMsg{
|
||||
PaymentHash: paymentHashBytes,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
}).WithError(err).Error("Failed to cancel hold invoice")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *LNDService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
paymentHashBytes, err := hex.DecodeString(paymentHash)
|
||||
if err != nil || len(paymentHashBytes) != 32 {
|
||||
|
|
@ -1202,12 +1430,25 @@ func (svc *LNDService) UpdateLastWalletSyncRequest() {}
|
|||
|
||||
func (svc *LNDService) GetSupportedNIP47Methods() []string {
|
||||
return []string{
|
||||
"pay_invoice", "pay_keysend", "get_balance", "get_budget", "get_info", "make_invoice", "lookup_invoice", "list_transactions", "multi_pay_invoice", "multi_pay_keysend", "sign_message",
|
||||
models.PAY_INVOICE_METHOD,
|
||||
models.PAY_KEYSEND_METHOD,
|
||||
models.GET_BALANCE_METHOD,
|
||||
models.GET_BUDGET_METHOD,
|
||||
models.GET_INFO_METHOD,
|
||||
models.MAKE_INVOICE_METHOD,
|
||||
models.LOOKUP_INVOICE_METHOD,
|
||||
models.LIST_TRANSACTIONS_METHOD,
|
||||
models.MULTI_PAY_INVOICE_METHOD,
|
||||
models.MULTI_PAY_KEYSEND_METHOD,
|
||||
models.SIGN_MESSAGE_METHOD,
|
||||
models.MAKE_HOLD_INVOICE_METHOD,
|
||||
models.SETTLE_HOLD_INVOICE_METHOD,
|
||||
models.CANCEL_HOLD_INVOICE_METHOD,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *LNDService) GetSupportedNIP47NotificationTypes() []string {
|
||||
return []string{"payment_received", "payment_sent"}
|
||||
return []string{notifications.PAYMENT_RECEIVED_NOTIFICATION, notifications.PAYMENT_SENT_NOTIFICATION, notifications.HOLD_INVOICE_ACCEPTED_NOTIFICATION}
|
||||
}
|
||||
|
||||
func (svc *LNDService) GetPubkey() string {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
|
@ -13,7 +14,11 @@ type LightningClientWrapper interface {
|
|||
SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error)
|
||||
ChannelBalance(ctx context.Context, req *lnrpc.ChannelBalanceRequest, options ...grpc.CallOption) (*lnrpc.ChannelBalanceResponse, error)
|
||||
AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error)
|
||||
AddHoldInvoice(ctx context.Context, req *invoicesrpc.AddHoldInvoiceRequest, options ...grpc.CallOption) (*invoicesrpc.AddHoldInvoiceResp, error)
|
||||
SettleInvoice(ctx context.Context, req *invoicesrpc.SettleInvoiceMsg, options ...grpc.CallOption) (*invoicesrpc.SettleInvoiceResp, error)
|
||||
CancelInvoice(ctx context.Context, req *invoicesrpc.CancelInvoiceMsg, options ...grpc.CallOption) (*invoicesrpc.CancelInvoiceResp, error)
|
||||
SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error)
|
||||
SubscribeSingleInvoice(ctx context.Context, req *invoicesrpc.SubscribeSingleInvoiceRequest, options ...grpc.CallOption) (SubscribeSingleInvoiceWrapper, error) // Added
|
||||
SubscribePayment(ctx context.Context, req *routerrpc.TrackPaymentRequest, options ...grpc.CallOption) (SubscribePaymentWrapper, error)
|
||||
LookupInvoice(ctx context.Context, req *lnrpc.PaymentHash, options ...grpc.CallOption) (*lnrpc.Invoice, error)
|
||||
GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error)
|
||||
|
|
@ -26,6 +31,11 @@ type LightningClientWrapper interface {
|
|||
type SubscribeInvoicesWrapper interface {
|
||||
Recv() (*lnrpc.Invoice, error)
|
||||
}
|
||||
|
||||
type SubscribeSingleInvoiceWrapper interface {
|
||||
Recv() (*lnrpc.Invoice, error)
|
||||
}
|
||||
|
||||
type SubscribePaymentWrapper interface {
|
||||
Recv() (*lnrpc.Payment, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -33,6 +34,7 @@ type LNDWrapper struct {
|
|||
client lnrpc.LightningClient
|
||||
routerClient routerrpc.RouterClient
|
||||
stateClient lnrpc.StateClient
|
||||
invoicesClient invoicesrpc.InvoicesClient
|
||||
IdentityPubkey string
|
||||
}
|
||||
|
||||
|
|
@ -85,9 +87,10 @@ func NewLNDclient(lndOptions LNDoptions) (result *LNDWrapper, err error) {
|
|||
}
|
||||
lnClient := lnrpc.NewLightningClient(conn)
|
||||
return &LNDWrapper{
|
||||
client: lnClient,
|
||||
routerClient: routerrpc.NewRouterClient(conn),
|
||||
stateClient: lnrpc.NewStateClient(conn),
|
||||
client: lnClient,
|
||||
routerClient: routerrpc.NewRouterClient(conn),
|
||||
stateClient: lnrpc.NewStateClient(conn),
|
||||
invoicesClient: invoicesrpc.NewInvoicesClient(conn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -115,10 +118,26 @@ func (wrapper *LNDWrapper) AddInvoice(ctx context.Context, req *lnrpc.Invoice, o
|
|||
return wrapper.client.AddInvoice(ctx, req, options...)
|
||||
}
|
||||
|
||||
func (wrapper *LNDWrapper) AddHoldInvoice(ctx context.Context, req *invoicesrpc.AddHoldInvoiceRequest, options ...grpc.CallOption) (*invoicesrpc.AddHoldInvoiceResp, error) {
|
||||
return wrapper.invoicesClient.AddHoldInvoice(ctx, req, options...)
|
||||
}
|
||||
|
||||
func (wrapper *LNDWrapper) SettleInvoice(ctx context.Context, req *invoicesrpc.SettleInvoiceMsg, options ...grpc.CallOption) (*invoicesrpc.SettleInvoiceResp, error) {
|
||||
return wrapper.invoicesClient.SettleInvoice(ctx, req, options...)
|
||||
}
|
||||
|
||||
func (wrapper *LNDWrapper) CancelInvoice(ctx context.Context, req *invoicesrpc.CancelInvoiceMsg, options ...grpc.CallOption) (*invoicesrpc.CancelInvoiceResp, error) {
|
||||
return wrapper.invoicesClient.CancelInvoice(ctx, req, options...)
|
||||
}
|
||||
|
||||
func (wrapper *LNDWrapper) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) {
|
||||
return wrapper.client.SubscribeInvoices(ctx, req, options...)
|
||||
}
|
||||
|
||||
func (wrapper *LNDWrapper) SubscribeSingleInvoice(ctx context.Context, req *invoicesrpc.SubscribeSingleInvoiceRequest, options ...grpc.CallOption) (SubscribeSingleInvoiceWrapper, error) {
|
||||
return wrapper.invoicesClient.SubscribeSingleInvoice(ctx, req, options...)
|
||||
}
|
||||
|
||||
func (wrapper *LNDWrapper) SubscribePayments(ctx context.Context, req *routerrpc.TrackPaymentsRequest, options ...grpc.CallOption) (routerrpc.Router_TrackPaymentsClient, error) {
|
||||
return wrapper.routerClient.TrackPayments(ctx, req, options...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type Transaction struct {
|
|||
ExpiresAt *int64
|
||||
SettledAt *int64
|
||||
Metadata Metadata
|
||||
SettleDeadline *uint32 // block number for accepted hold invoices
|
||||
}
|
||||
|
||||
type OnchainTransaction struct {
|
||||
|
|
@ -61,6 +62,9 @@ type LNClient interface {
|
|||
GetPubkey() string
|
||||
GetInfo(ctx context.Context) (info *NodeInfo, err error)
|
||||
MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *Transaction, err error)
|
||||
MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (transaction *Transaction, err error)
|
||||
SettleHoldInvoice(ctx context.Context, preimage string) (err error)
|
||||
CancelHoldInvoice(ctx context.Context, paymentHash string) (err error)
|
||||
LookupInvoice(ctx context.Context, paymentHash string) (transaction *Transaction, err error)
|
||||
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Transaction, err error)
|
||||
ListOnchainTransactions(ctx context.Context) ([]OnchainTransaction, error)
|
||||
|
|
@ -249,3 +253,14 @@ func NewTimeoutError() error {
|
|||
func (err *timeoutError) Error() string {
|
||||
return "Timeout"
|
||||
}
|
||||
|
||||
type holdInvoiceCanceledError struct {
|
||||
}
|
||||
|
||||
func NewHoldInvoiceCanceledError() error {
|
||||
return &holdInvoiceCanceledError{}
|
||||
}
|
||||
|
||||
func (err *holdInvoiceCanceledError) Error() string {
|
||||
return "Hold invoice canceled"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,6 +324,18 @@ func (svc *PhoenixService) MakeInvoice(ctx context.Context, amount int64, descri
|
|||
return tx, nil
|
||||
}
|
||||
|
||||
func (svc *PhoenixService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (svc *PhoenixService) SettleHoldInvoice(ctx context.Context, preimage string) (err error) {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (svc *PhoenixService) CancelHoldInvoice(ctx context.Context, paymentHash string) (err error) {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
req, err := http.NewRequest(http.MethodGet, svc.Address+"/payments/incoming/"+paymentHash, nil)
|
||||
if err != nil {
|
||||
|
|
@ -394,6 +406,7 @@ func (svc *PhoenixService) ResetRouter(key string) error {
|
|||
}
|
||||
|
||||
func (svc *PhoenixService) Shutdown() error {
|
||||
// No specific shutdown actions needed for Phoenixd client via HTTP
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
50
nip47/controllers/cancel_hold_invoice_controller.go
Normal file
50
nip47/controllers/cancel_hold_invoice_controller.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type cancelHoldInvoiceParams struct {
|
||||
PaymentHash string `json:"payment_hash"`
|
||||
}
|
||||
type cancelHoldInvoiceResponse struct{}
|
||||
|
||||
func (controller *nip47Controller) HandleCancelHoldInvoiceEvent(ctx context.Context, nip47Request *models.Request, requestEventId uint, appId uint, publishResponse func(*models.Response, nostr.Tags)) {
|
||||
cancelHoldInvoiceParams := &cancelHoldInvoiceParams{}
|
||||
decodeErrResp := decodeRequest(nip47Request, cancelHoldInvoiceParams)
|
||||
if decodeErrResp != nil {
|
||||
publishResponse(decodeErrResp, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"requestEventId": requestEventId,
|
||||
"appId": appId,
|
||||
"paymentHash": cancelHoldInvoiceParams.PaymentHash,
|
||||
}).Info("Canceling hold invoice")
|
||||
|
||||
err := controller.transactionsService.CancelHoldInvoice(ctx, cancelHoldInvoiceParams.PaymentHash, controller.lnClient)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"request_event_id": requestEventId,
|
||||
"appId": appId,
|
||||
"paymentHash": cancelHoldInvoiceParams.PaymentHash,
|
||||
}).WithError(err).Error("Failed to cancel hold invoice")
|
||||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Result: &cancelHoldInvoiceResponse{},
|
||||
}, nostr.Tags{})
|
||||
}
|
||||
135
nip47/controllers/cancel_hold_invoice_controller_test.go
Normal file
135
nip47/controllers/cancel_hold_invoice_controller_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/getAlby/hub/tests"
|
||||
)
|
||||
|
||||
const nip47CancelHoldInvoiceJson = `
|
||||
{
|
||||
"method": "cancel_hold_invoice",
|
||||
"params": {
|
||||
"payment_hash": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testCancelPaymentHash = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
|
||||
type cancelHoldInvoiceTestSetup struct {
|
||||
ctx context.Context
|
||||
svc *tests.TestService
|
||||
nip47Request *models.Request
|
||||
app *db.App
|
||||
dbRequestEvent *db.RequestEvent
|
||||
publishCalled bool
|
||||
response *models.Response
|
||||
}
|
||||
|
||||
func setupCancelHoldInvoiceTest(t *testing.T, paymentHash string, initialTransactionState *string) *cancelHoldInvoiceTestSetup {
|
||||
ctx := context.TODO()
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
// Not using defer svc.Remove() as it might be called before the test function finishes in some cases.
|
||||
// Caller should call svc.Remove()
|
||||
|
||||
nip47Request := &models.Request{}
|
||||
requestJson := `
|
||||
{
|
||||
"method": "cancel_hold_invoice",
|
||||
"params": {
|
||||
"payment_hash": "` + paymentHash + `"
|
||||
}
|
||||
}
|
||||
`
|
||||
err = json.Unmarshal([]byte(requestJson), nip47Request)
|
||||
require.NoError(t, err)
|
||||
|
||||
app, _, err := tests.CreateApp(svc)
|
||||
require.NoError(t, err)
|
||||
|
||||
appPermission := &db.AppPermission{
|
||||
AppId: app.ID,
|
||||
Scope: constants.MAKE_INVOICE_SCOPE,
|
||||
}
|
||||
err = svc.DB.Create(appPermission).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
if initialTransactionState != nil {
|
||||
transaction := &db.Transaction{
|
||||
AppId: &app.ID,
|
||||
PaymentHash: paymentHash,
|
||||
Type: constants.TRANSACTION_TYPE_INCOMING,
|
||||
State: *initialTransactionState,
|
||||
AmountMsat: 1000,
|
||||
}
|
||||
err = svc.DB.Create(transaction).Error
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
dbRequestEvent := &db.RequestEvent{
|
||||
AppId: &app.ID,
|
||||
}
|
||||
err = svc.DB.Create(&dbRequestEvent).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
setup := &cancelHoldInvoiceTestSetup{
|
||||
ctx: ctx,
|
||||
svc: svc,
|
||||
nip47Request: nip47Request,
|
||||
app: app,
|
||||
dbRequestEvent: dbRequestEvent,
|
||||
}
|
||||
|
||||
return setup
|
||||
}
|
||||
|
||||
func (s *cancelHoldInvoiceTestSetup) TearDown() {
|
||||
s.svc.Remove()
|
||||
}
|
||||
|
||||
func (s *cancelHoldInvoiceTestSetup) PublishResponse(response *models.Response, tags nostr.Tags) {
|
||||
s.publishCalled = true
|
||||
s.response = response
|
||||
}
|
||||
|
||||
func TestHandleCancelHoldInvoiceEvent(t *testing.T) {
|
||||
initialState := constants.TRANSACTION_STATE_ACCEPTED
|
||||
setup := setupCancelHoldInvoiceTest(t, testCancelPaymentHash, &initialState)
|
||||
defer setup.TearDown()
|
||||
|
||||
NewTestNip47Controller(setup.svc).
|
||||
HandleCancelHoldInvoiceEvent(setup.ctx, setup.nip47Request, setup.dbRequestEvent.ID, *setup.dbRequestEvent.AppId, setup.PublishResponse)
|
||||
|
||||
assert.True(t, setup.publishCalled)
|
||||
assert.Nil(t, setup.response.Error)
|
||||
assert.Equal(t, &cancelHoldInvoiceResponse{}, setup.response.Result)
|
||||
|
||||
var updatedTransaction db.Transaction
|
||||
err := setup.svc.DB.First(&updatedTransaction, "payment_hash = ?", testCancelPaymentHash).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_FAILED, updatedTransaction.State)
|
||||
}
|
||||
|
||||
func TestHandleCancelHoldInvoiceEvent_InvoiceNotFound(t *testing.T) {
|
||||
nonExistentPaymentHash := "nonexistentpaymenthashnonexistentpaymenthashnonexistentpaymenthash"
|
||||
setup := setupCancelHoldInvoiceTest(t, nonExistentPaymentHash, nil) // nil for initialTransactionState means no transaction created
|
||||
defer setup.TearDown()
|
||||
|
||||
NewTestNip47Controller(setup.svc).
|
||||
HandleCancelHoldInvoiceEvent(setup.ctx, setup.nip47Request, setup.dbRequestEvent.ID, *setup.dbRequestEvent.AppId, setup.PublishResponse)
|
||||
|
||||
assert.True(t, setup.publishCalled)
|
||||
require.NotNil(t, setup.response.Error)
|
||||
assert.Equal(t, constants.ERROR_NOT_FOUND, setup.response.Error.Code)
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ func (controller *nip47Controller) HandleCreateConnectionEvent(ctx context.Conte
|
|||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: "cannot create a new app that has create_connection permission via NWC",
|
||||
},
|
||||
}, nostr.Tags{})
|
||||
|
|
@ -69,7 +69,7 @@ func (controller *nip47Controller) HandleCreateConnectionEvent(ctx context.Conte
|
|||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: "No request methods provided",
|
||||
},
|
||||
}, nostr.Tags{})
|
||||
|
|
@ -83,7 +83,7 @@ func (controller *nip47Controller) HandleCreateConnectionEvent(ctx context.Conte
|
|||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: "One or more methods are not supported by the current LNClient",
|
||||
},
|
||||
}, nostr.Tags{})
|
||||
|
|
@ -100,7 +100,7 @@ func (controller *nip47Controller) HandleCreateConnectionEvent(ctx context.Conte
|
|||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: "One or more notification types are not supported by the current LNClient",
|
||||
},
|
||||
}, nostr.Tags{})
|
||||
|
|
@ -116,10 +116,7 @@ func (controller *nip47Controller) HandleCreateConnectionEvent(ctx context.Conte
|
|||
}).WithError(err).Error("Failed to create app")
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Message: err.Error(),
|
||||
},
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ func TestHandleCreateConnectionEvent_NoMethods(t *testing.T) {
|
|||
HandleCreateConnectionEvent(ctx, nip47Request, dbRequestEvent.ID, publishResponse)
|
||||
|
||||
assert.NotNil(t, publishedResponse.Error)
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, publishedResponse.Error.Code)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, publishedResponse.Error.Code)
|
||||
assert.Equal(t, "No request methods provided", publishedResponse.Error.Message)
|
||||
assert.Equal(t, models.CREATE_CONNECTION_METHOD, publishedResponse.ResultType)
|
||||
assert.Nil(t, publishedResponse.Result)
|
||||
|
|
@ -265,7 +265,7 @@ func TestHandleCreateConnectionEvent_UnsupportedMethod(t *testing.T) {
|
|||
HandleCreateConnectionEvent(ctx, nip47Request, dbRequestEvent.ID, publishResponse)
|
||||
|
||||
assert.NotNil(t, publishedResponse.Error)
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, publishedResponse.Error.Code)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, publishedResponse.Error.Code)
|
||||
assert.Equal(t, "One or more methods are not supported by the current LNClient", publishedResponse.Error.Message)
|
||||
assert.Equal(t, models.CREATE_CONNECTION_METHOD, publishedResponse.ResultType)
|
||||
assert.Nil(t, publishedResponse.Result)
|
||||
|
|
@ -311,7 +311,7 @@ func TestHandleCreateConnectionEvent_UnsupportedNotificationType(t *testing.T) {
|
|||
HandleCreateConnectionEvent(ctx, nip47Request, dbRequestEvent.ID, publishResponse)
|
||||
|
||||
assert.NotNil(t, publishedResponse.Error)
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, publishedResponse.Error.Code)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, publishedResponse.Error.Code)
|
||||
assert.Equal(t, "One or more notification types are not supported by the current LNClient", publishedResponse.Error.Message)
|
||||
assert.Equal(t, models.CREATE_CONNECTION_METHOD, publishedResponse.ResultType)
|
||||
assert.Nil(t, publishedResponse.Result)
|
||||
|
|
@ -356,7 +356,7 @@ func TestHandleCreateConnectionEvent_DoNotAllowCreateConnectionMethod(t *testing
|
|||
HandleCreateConnectionEvent(ctx, nip47Request, dbRequestEvent.ID, publishResponse)
|
||||
|
||||
assert.NotNil(t, publishedResponse.Error)
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, publishedResponse.Error.Code)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, publishedResponse.Error.Code)
|
||||
assert.Equal(t, "cannot create a new app that has create_connection permission via NWC", publishedResponse.Error.Message)
|
||||
assert.Equal(t, models.CREATE_CONNECTION_METHOD, publishedResponse.ResultType)
|
||||
assert.Nil(t, publishedResponse.Result)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/db/queries"
|
||||
"github.com/getAlby/hub/logger"
|
||||
|
|
@ -41,10 +40,7 @@ func (controller *nip47Controller) HandleGetBalanceEvent(ctx context.Context, ni
|
|||
}).WithError(err).Error("Failed to fetch balance")
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Message: err.Error(),
|
||||
},
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,10 +53,7 @@ func (controller *nip47Controller) HandleGetInfoEvent(ctx context.Context, nip47
|
|||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Message: err.Error(),
|
||||
},
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
|
|
@ -60,10 +59,7 @@ func (controller *nip47Controller) HandleListTransactionsEvent(ctx context.Conte
|
|||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Message: err.Error(),
|
||||
},
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func (controller *nip47Controller) HandleLookupInvoiceEvent(ctx context.Context,
|
|||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()),
|
||||
},
|
||||
}, nostr.Tags{})
|
||||
|
|
|
|||
101
nip47/controllers/make_hold_invoice_controller.go
Normal file
101
nip47/controllers/make_hold_invoice_controller.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type makeHoldInvoiceParams struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
PaymentHash string `json:"payment_hash"`
|
||||
Description string `json:"description"`
|
||||
DescriptionHash string `json:"description_hash"`
|
||||
Expiry uint64 `json:"expiry"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
type makeHoldInvoiceResponse struct {
|
||||
models.Transaction
|
||||
}
|
||||
|
||||
func (controller *nip47Controller) HandleMakeHoldInvoiceEvent(ctx context.Context, nip47Request *models.Request, requestEventId uint, appId uint, publishResponse func(*models.Response, nostr.Tags)) {
|
||||
makeHoldInvoiceParams := &makeHoldInvoiceParams{}
|
||||
decodeErrResp := decodeRequest(nip47Request, makeHoldInvoiceParams)
|
||||
if decodeErrResp != nil {
|
||||
publishResponse(decodeErrResp, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
||||
if makeHoldInvoiceParams.PaymentHash == "" {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"requestEventId": requestEventId,
|
||||
"appId": appId,
|
||||
}).Error("Payment hash is missing for make_hold_invoice")
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: "payment_hash is required for make_hold_invoice",
|
||||
},
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"requestEventId": requestEventId,
|
||||
"appId": appId,
|
||||
"amount": makeHoldInvoiceParams.Amount,
|
||||
"description": makeHoldInvoiceParams.Description,
|
||||
"descriptionHash": makeHoldInvoiceParams.DescriptionHash,
|
||||
"expiry": makeHoldInvoiceParams.Expiry,
|
||||
"paymentHash": makeHoldInvoiceParams.PaymentHash,
|
||||
"metadata": makeHoldInvoiceParams.Metadata,
|
||||
}).Info("Making hold invoice")
|
||||
|
||||
requestEventIdUint := uint(requestEventId)
|
||||
transaction, err := controller.transactionsService.MakeHoldInvoice(
|
||||
ctx,
|
||||
makeHoldInvoiceParams.Amount,
|
||||
makeHoldInvoiceParams.Description,
|
||||
makeHoldInvoiceParams.DescriptionHash,
|
||||
makeHoldInvoiceParams.Expiry,
|
||||
makeHoldInvoiceParams.PaymentHash,
|
||||
makeHoldInvoiceParams.Metadata,
|
||||
controller.lnClient,
|
||||
&appId,
|
||||
&requestEventIdUint,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"request_event_id": requestEventId,
|
||||
"appId": appId,
|
||||
"amount": makeHoldInvoiceParams.Amount,
|
||||
"description": makeHoldInvoiceParams.Description,
|
||||
"descriptionHash": makeHoldInvoiceParams.DescriptionHash,
|
||||
"expiry": makeHoldInvoiceParams.Expiry,
|
||||
"paymentHash": makeHoldInvoiceParams.PaymentHash,
|
||||
}).WithError(err).Error("Failed to make invoice")
|
||||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
||||
nip47Transaction := models.ToNip47Transaction(transaction)
|
||||
|
||||
responsePayload := &makeHoldInvoiceResponse{
|
||||
Transaction: *nip47Transaction,
|
||||
}
|
||||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Result: responsePayload,
|
||||
}, nostr.Tags{})
|
||||
}
|
||||
129
nip47/controllers/make_hold_invoice_controller_test.go
Normal file
129
nip47/controllers/make_hold_invoice_controller_test.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/getAlby/hub/tests"
|
||||
)
|
||||
|
||||
const nip47MakeHoldInvoiceJson = `
|
||||
{
|
||||
"method": "make_hold_invoice",
|
||||
"params": {
|
||||
"amount": 1000,
|
||||
"description": "Hello, world",
|
||||
"payment_hash": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||
"expiry": 3600,
|
||||
"metadata": {
|
||||
"a": 1,
|
||||
"b": "2",
|
||||
"c": {
|
||||
"d": 3,
|
||||
"e": [{
|
||||
"f": "g"
|
||||
},{
|
||||
"h": "i"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestHandleMakeHoldInvoiceEvent(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
defer svc.Remove()
|
||||
|
||||
nip47Request := &models.Request{}
|
||||
err = json.Unmarshal([]byte(nip47MakeHoldInvoiceJson), nip47Request)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app, _, err := tests.CreateApp(svc)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbRequestEvent := &db.RequestEvent{
|
||||
AppId: &app.ID,
|
||||
}
|
||||
err = svc.DB.Create(&dbRequestEvent).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
var publishedResponse *models.Response
|
||||
|
||||
publishResponse := func(response *models.Response, tags nostr.Tags) {
|
||||
publishedResponse = response
|
||||
}
|
||||
|
||||
NewTestNip47Controller(svc).
|
||||
HandleMakeHoldInvoiceEvent(ctx, nip47Request, dbRequestEvent.ID, *dbRequestEvent.AppId, publishResponse)
|
||||
|
||||
expectedMetadata := map[string]interface{}{
|
||||
"a": float64(1),
|
||||
"b": "2",
|
||||
"c": map[string]interface{}{
|
||||
"d": float64(3),
|
||||
"e": []interface{}{
|
||||
map[string]interface{}{"f": "g"},
|
||||
map[string]interface{}{"h": "i"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Nil(t, publishedResponse.Error)
|
||||
assert.Equal(t, tests.MockLNClientHoldTransaction.Invoice, publishedResponse.Result.(*makeHoldInvoiceResponse).Invoice)
|
||||
assert.Equal(t, tests.MockLNClientHoldTransaction.PaymentHash, publishedResponse.Result.(*makeHoldInvoiceResponse).PaymentHash)
|
||||
assert.Equal(t, expectedMetadata, publishedResponse.Result.(*makeHoldInvoiceResponse).Metadata)
|
||||
}
|
||||
|
||||
const nip47MakeHoldInvoiceMissingPaymentHashJson = `
|
||||
{
|
||||
"method": "make_hold_invoice",
|
||||
"params": {
|
||||
"amount": 1000,
|
||||
"description": "Hello, world",
|
||||
"expiry": 3600
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestHandleMakeHoldInvoiceEvent_MissingPaymentHash(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
defer svc.Remove()
|
||||
|
||||
nip47Request := &models.Request{}
|
||||
err = json.Unmarshal([]byte(nip47MakeHoldInvoiceMissingPaymentHashJson), nip47Request)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app, _, err := tests.CreateApp(svc)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbRequestEvent := &db.RequestEvent{
|
||||
AppId: &app.ID,
|
||||
}
|
||||
err = svc.DB.Create(&dbRequestEvent).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
var publishedResponse *models.Response
|
||||
|
||||
publishResponse := func(response *models.Response, tags nostr.Tags) {
|
||||
publishedResponse = response
|
||||
}
|
||||
|
||||
NewTestNip47Controller(svc).
|
||||
HandleMakeHoldInvoiceEvent(ctx, nip47Request, dbRequestEvent.ID, *dbRequestEvent.AppId, publishResponse)
|
||||
|
||||
require.NotNil(t, publishedResponse.Error)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, publishedResponse.Error.Code)
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
|
|
@ -53,10 +52,7 @@ func (controller *nip47Controller) HandleMakeInvoiceEvent(ctx context.Context, n
|
|||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Message: err.Error(),
|
||||
},
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (controller *nip47Controller) HandleMultiPayInvoiceEvent(ctx context.Contex
|
|||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()),
|
||||
},
|
||||
}, nostr.Tags{dTag})
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ func TestHandleMultiPayInvoiceEvent_OneMalformedInvoice(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, "invoiceId123", dTags[0].GetFirst([]string{"d"}).Value())
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, responses[0].Error.Code)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, responses[0].Error.Code)
|
||||
assert.Nil(t, responses[0].Result)
|
||||
|
||||
assert.Equal(t, tests.MockPaymentHash, dTags[1].GetFirst([]string{"d"}).Value())
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func (controller *nip47Controller) HandlePayInvoiceEvent(ctx context.Context, ni
|
|||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()),
|
||||
},
|
||||
}, tags)
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ func TestHandlePayInvoiceEvent_MalformedInvoice(t *testing.T) {
|
|||
HandlePayInvoiceEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse, nostr.Tags{})
|
||||
|
||||
assert.Nil(t, publishedResponse.Result)
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, publishedResponse.Error.Code)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, publishedResponse.Error.Code)
|
||||
assert.Equal(t, "Failed to decode bolt11 invoice: bolt11 too short", publishedResponse.Error.Message)
|
||||
}
|
||||
|
||||
|
|
|
|||
50
nip47/controllers/settle_hold_invoice_controller.go
Normal file
50
nip47/controllers/settle_hold_invoice_controller.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type settleHoldInvoiceParams struct {
|
||||
Preimage string `json:"preimage"`
|
||||
}
|
||||
type settleHoldInvoiceResponse struct{}
|
||||
|
||||
func (controller *nip47Controller) HandleSettleHoldInvoiceEvent(ctx context.Context, nip47Request *models.Request, requestEventId uint, appId uint, publishResponse func(*models.Response, nostr.Tags)) {
|
||||
settleHoldInvoiceParams := &settleHoldInvoiceParams{}
|
||||
decodeErrResp := decodeRequest(nip47Request, settleHoldInvoiceParams)
|
||||
if decodeErrResp != nil {
|
||||
publishResponse(decodeErrResp, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"requestEventId": requestEventId,
|
||||
"appId": appId,
|
||||
"preimage": settleHoldInvoiceParams.Preimage,
|
||||
}).Info("Settling hold invoice")
|
||||
|
||||
_, err := controller.transactionsService.SettleHoldInvoice(ctx, settleHoldInvoiceParams.Preimage, controller.lnClient)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"request_event_id": requestEventId,
|
||||
"appId": appId,
|
||||
"preimage": settleHoldInvoiceParams.Preimage,
|
||||
}).WithError(err).Error("Failed to settle hold invoice")
|
||||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Result: &settleHoldInvoiceResponse{},
|
||||
}, nostr.Tags{})
|
||||
}
|
||||
151
nip47/controllers/settle_hold_invoice_controller_test.go
Normal file
151
nip47/controllers/settle_hold_invoice_controller_test.go
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/getAlby/hub/tests"
|
||||
)
|
||||
|
||||
const nip47SettleHoldInvoiceJson = `
|
||||
{
|
||||
"method": "settle_hold_invoice",
|
||||
"params": {
|
||||
"preimage": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testSettlePreimage = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
const testSettlePaymentHash = "b7e060a60bb7a82f536a73c17bde37a1b6cf5769ee4a8325bff76c55a95b6aa4"
|
||||
|
||||
type settleHoldInvoiceTestSetup struct {
|
||||
ctx context.Context
|
||||
svc *tests.TestService
|
||||
nip47Request *models.Request
|
||||
app *db.App
|
||||
dbRequestEvent *db.RequestEvent
|
||||
publishCalled bool
|
||||
response *models.Response
|
||||
}
|
||||
|
||||
func setupSettleHoldInvoiceTest(t *testing.T, preimage string, paymentHashToCreate string, initialTransactionState string) *settleHoldInvoiceTestSetup {
|
||||
ctx := context.TODO()
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
|
||||
nip47Request := &models.Request{}
|
||||
requestJson := `
|
||||
{
|
||||
"method": "settle_hold_invoice",
|
||||
"params": {
|
||||
"preimage": "` + preimage + `"
|
||||
}
|
||||
}
|
||||
`
|
||||
err = json.Unmarshal([]byte(requestJson), nip47Request)
|
||||
require.NoError(t, err)
|
||||
|
||||
app, _, err := tests.CreateApp(svc)
|
||||
require.NoError(t, err)
|
||||
|
||||
appPermission := &db.AppPermission{
|
||||
AppId: app.ID,
|
||||
Scope: constants.MAKE_INVOICE_SCOPE,
|
||||
}
|
||||
err = svc.DB.Create(appPermission).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
if paymentHashToCreate != "" && initialTransactionState != "" {
|
||||
expiresAtVar := time.Now().Add(1 * time.Hour)
|
||||
appIDForTx := app.ID
|
||||
holdInvoice := &db.Transaction{
|
||||
AppId: &appIDForTx,
|
||||
Type: constants.TRANSACTION_TYPE_INCOMING,
|
||||
State: initialTransactionState,
|
||||
PaymentHash: paymentHashToCreate,
|
||||
AmountMsat: 1000,
|
||||
ExpiresAt: &expiresAtVar,
|
||||
}
|
||||
err = svc.DB.Create(holdInvoice).Error
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
dbRequestEvent := &db.RequestEvent{
|
||||
AppId: &app.ID,
|
||||
}
|
||||
err = svc.DB.Create(&dbRequestEvent).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
setup := &settleHoldInvoiceTestSetup{
|
||||
ctx: ctx,
|
||||
svc: svc,
|
||||
nip47Request: nip47Request,
|
||||
app: app,
|
||||
dbRequestEvent: dbRequestEvent,
|
||||
}
|
||||
return setup
|
||||
}
|
||||
|
||||
func (s *settleHoldInvoiceTestSetup) TearDown() {
|
||||
s.svc.Remove()
|
||||
}
|
||||
|
||||
func (s *settleHoldInvoiceTestSetup) PublishResponse(response *models.Response, tags nostr.Tags) {
|
||||
s.publishCalled = true
|
||||
s.response = response
|
||||
}
|
||||
|
||||
func TestHandleSettleHoldInvoiceEvent(t *testing.T) {
|
||||
preimageBytesForCheck, err := hex.DecodeString(testSettlePreimage)
|
||||
require.NoError(t, err)
|
||||
calculatedHashBytesForCheck := sha256.Sum256(preimageBytesForCheck)
|
||||
calculatedPaymentHashForCheck := hex.EncodeToString(calculatedHashBytesForCheck[:])
|
||||
assert.Equal(t, testSettlePaymentHash, calculatedPaymentHashForCheck)
|
||||
|
||||
setup := setupSettleHoldInvoiceTest(t, testSettlePreimage, testSettlePaymentHash, constants.TRANSACTION_STATE_ACCEPTED)
|
||||
defer setup.TearDown()
|
||||
|
||||
controller := NewTestNip47Controller(setup.svc)
|
||||
|
||||
controller.HandleSettleHoldInvoiceEvent(setup.ctx, setup.nip47Request, setup.dbRequestEvent.ID, *setup.dbRequestEvent.AppId, setup.PublishResponse)
|
||||
|
||||
assert.True(t, setup.publishCalled)
|
||||
assert.Nil(t, setup.response.Error)
|
||||
assert.Equal(t, &settleHoldInvoiceResponse{}, setup.response.Result)
|
||||
|
||||
var settledTx db.Transaction
|
||||
err = setup.svc.DB.First(&settledTx, "payment_hash = ?", testSettlePaymentHash).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, settledTx.State)
|
||||
assert.NotNil(t, settledTx.Preimage)
|
||||
assert.Equal(t, testSettlePreimage, *settledTx.Preimage)
|
||||
}
|
||||
|
||||
func TestHandleSettleHoldInvoiceEvent_InvalidPreimage(t *testing.T) {
|
||||
invalidPreimage := "invalidpreimageinvalidpreimageinvalidpreimageinvalidpreimageinvalid"
|
||||
setup := setupSettleHoldInvoiceTest(t, invalidPreimage, testSettlePaymentHash, constants.TRANSACTION_STATE_ACCEPTED)
|
||||
defer setup.TearDown()
|
||||
|
||||
controller := NewTestNip47Controller(setup.svc)
|
||||
|
||||
controller.HandleSettleHoldInvoiceEvent(setup.ctx, setup.nip47Request, setup.dbRequestEvent.ID, *setup.dbRequestEvent.AppId, setup.PublishResponse)
|
||||
|
||||
assert.True(t, setup.publishCalled)
|
||||
require.NotNil(t, setup.response.Error)
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, setup.response.Error.Code)
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/models"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
|
|
@ -38,10 +37,7 @@ func (controller *nip47Controller) HandleSignMessageEvent(ctx context.Context, n
|
|||
}).WithError(err).Error("Failed to sign message")
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Message: err.Error(),
|
||||
},
|
||||
Error: mapNip47Error(err),
|
||||
}, nostr.Tags{})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
|
|||
|
||||
nip47Response = &models.Response{
|
||||
Error: &models.Error{
|
||||
Code: constants.ERROR_INTERNAL,
|
||||
Code: constants.ERROR_BAD_REQUEST,
|
||||
Message: fmt.Sprintf("failed to decrypt: %s", decryptionErr.Error()),
|
||||
},
|
||||
}
|
||||
|
|
@ -425,6 +425,15 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
|
|||
case models.CREATE_CONNECTION_METHOD:
|
||||
controller.
|
||||
HandleCreateConnectionEvent(ctx, nip47Request, requestEvent.ID, publishResponse)
|
||||
case models.MAKE_HOLD_INVOICE_METHOD:
|
||||
controller.
|
||||
HandleMakeHoldInvoiceEvent(ctx, nip47Request, requestEvent.ID, app.ID, publishResponse)
|
||||
case models.CANCEL_HOLD_INVOICE_METHOD:
|
||||
controller.
|
||||
HandleCancelHoldInvoiceEvent(ctx, nip47Request, requestEvent.ID, app.ID, publishResponse)
|
||||
case models.SETTLE_HOLD_INVOICE_METHOD:
|
||||
controller.
|
||||
HandleSettleHoldInvoiceEvent(ctx, nip47Request, requestEvent.ID, app.ID, publishResponse)
|
||||
default:
|
||||
publishResponse(&models.Response{
|
||||
ResultType: nip47Request.Method,
|
||||
|
|
|
|||
|
|
@ -665,6 +665,6 @@ func doTestHandleResponse_EncryptionTagDoesNotMatchPayload(t *testing.T, svc *te
|
|||
assert.NoError(t, err)
|
||||
assert.Nil(t, unmarshalledResponse.Result)
|
||||
// assert.Equal(t, models.GET_INFO_METHOD, unmarshalledResponse.ResultType)
|
||||
assert.Equal(t, constants.ERROR_INTERNAL, unmarshalledResponse.Error.Code)
|
||||
assert.Equal(t, constants.ERROR_BAD_REQUEST, unmarshalledResponse.Error.Code)
|
||||
assert.Contains(t, unmarshalledResponse.Error.Message, "failed to decrypt:")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,18 +12,21 @@ const (
|
|||
NOTIFICATION_KIND = 23197
|
||||
|
||||
// request methods
|
||||
PAY_INVOICE_METHOD = "pay_invoice"
|
||||
GET_BALANCE_METHOD = "get_balance"
|
||||
GET_BUDGET_METHOD = "get_budget"
|
||||
GET_INFO_METHOD = "get_info"
|
||||
MAKE_INVOICE_METHOD = "make_invoice"
|
||||
LOOKUP_INVOICE_METHOD = "lookup_invoice"
|
||||
LIST_TRANSACTIONS_METHOD = "list_transactions"
|
||||
PAY_KEYSEND_METHOD = "pay_keysend"
|
||||
MULTI_PAY_INVOICE_METHOD = "multi_pay_invoice"
|
||||
MULTI_PAY_KEYSEND_METHOD = "multi_pay_keysend"
|
||||
SIGN_MESSAGE_METHOD = "sign_message"
|
||||
CREATE_CONNECTION_METHOD = "create_connection"
|
||||
PAY_INVOICE_METHOD = "pay_invoice"
|
||||
GET_BALANCE_METHOD = "get_balance"
|
||||
GET_BUDGET_METHOD = "get_budget"
|
||||
GET_INFO_METHOD = "get_info"
|
||||
MAKE_INVOICE_METHOD = "make_invoice"
|
||||
LOOKUP_INVOICE_METHOD = "lookup_invoice"
|
||||
LIST_TRANSACTIONS_METHOD = "list_transactions"
|
||||
PAY_KEYSEND_METHOD = "pay_keysend"
|
||||
MULTI_PAY_INVOICE_METHOD = "multi_pay_invoice"
|
||||
MULTI_PAY_KEYSEND_METHOD = "multi_pay_keysend"
|
||||
SIGN_MESSAGE_METHOD = "sign_message"
|
||||
CREATE_CONNECTION_METHOD = "create_connection"
|
||||
MAKE_HOLD_INVOICE_METHOD = "make_hold_invoice"
|
||||
CANCEL_HOLD_INVOICE_METHOD = "cancel_hold_invoice"
|
||||
SETTLE_HOLD_INVOICE_METHOD = "settle_hold_invoice"
|
||||
)
|
||||
|
||||
type Transaction struct {
|
||||
|
|
@ -39,6 +42,7 @@ type Transaction struct {
|
|||
CreatedAt int64 `json:"created_at"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
SettledAt *int64 `json:"settled_at"`
|
||||
SettleDeadline *uint32 `json:"settle_deadline"` // block number for accepted hold invoices
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,5 +49,6 @@ func ToNip47Transaction(transaction *transactions.Transaction) *Transaction {
|
|||
ExpiresAt: expiresAt,
|
||||
SettledAt: settledAt,
|
||||
Metadata: metadata,
|
||||
SettleDeadline: transaction.SettleDeadline,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ type Notification struct {
|
|||
}
|
||||
|
||||
const (
|
||||
PAYMENT_RECEIVED_NOTIFICATION = "payment_received"
|
||||
PAYMENT_SENT_NOTIFICATION = "payment_sent"
|
||||
PAYMENT_RECEIVED_NOTIFICATION = "payment_received"
|
||||
PAYMENT_SENT_NOTIFICATION = "payment_sent"
|
||||
HOLD_INVOICE_ACCEPTED_NOTIFICATION = "hold_invoice_accepted"
|
||||
)
|
||||
|
||||
type PaymentSentNotification struct {
|
||||
|
|
@ -19,3 +20,7 @@ type PaymentSentNotification struct {
|
|||
type PaymentReceivedNotification struct {
|
||||
models.Transaction
|
||||
}
|
||||
|
||||
type HoldInvoiceAcceptedNotification struct {
|
||||
models.Transaction
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,24 @@ func (notifier *Nip47Notifier) ConsumeEvent(ctx context.Context, event *events.E
|
|||
Notification: notification,
|
||||
NotificationType: PAYMENT_SENT_NOTIFICATION,
|
||||
}, nostr.Tags{}, transaction.AppId)
|
||||
|
||||
case "nwc_hold_invoice_accepted":
|
||||
dbTransaction, ok := event.Properties.(*db.Transaction)
|
||||
if !ok {
|
||||
logger.Logger.WithField("event", event).Error("Failed to cast event properties to db.Transaction for hold invoice accepted")
|
||||
return
|
||||
}
|
||||
|
||||
nip47Transaction := models.ToNip47Transaction(dbTransaction)
|
||||
|
||||
notification := HoldInvoiceAcceptedNotification{
|
||||
Transaction: *nip47Transaction,
|
||||
}
|
||||
|
||||
notifier.notifySubscribers(ctx, &Notification{
|
||||
Notification: notification,
|
||||
NotificationType: HOLD_INVOICE_ACCEPTED_NOTIFICATION,
|
||||
}, nostr.Tags{}, dbTransaction.AppId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ func scopeToRequestMethods(scope string) []string {
|
|||
case constants.GET_INFO_SCOPE:
|
||||
return []string{models.GET_INFO_METHOD}
|
||||
case constants.MAKE_INVOICE_SCOPE:
|
||||
return []string{models.MAKE_INVOICE_METHOD}
|
||||
return []string{models.MAKE_INVOICE_METHOD, models.MAKE_HOLD_INVOICE_METHOD, models.SETTLE_HOLD_INVOICE_METHOD, models.CANCEL_HOLD_INVOICE_METHOD}
|
||||
case constants.LOOKUP_INVOICE_SCOPE:
|
||||
return []string{models.LOOKUP_INVOICE_METHOD}
|
||||
case constants.LIST_TRANSACTIONS_SCOPE:
|
||||
|
|
@ -165,6 +165,8 @@ func RequestMethodToScope(requestMethod string) (string, error) {
|
|||
return constants.LIST_TRANSACTIONS_SCOPE, nil
|
||||
case models.SIGN_MESSAGE_METHOD:
|
||||
return constants.SIGN_MESSAGE_SCOPE, nil
|
||||
case models.MAKE_HOLD_INVOICE_METHOD, models.SETTLE_HOLD_INVOICE_METHOD, models.CANCEL_HOLD_INVOICE_METHOD:
|
||||
return constants.MAKE_INVOICE_SCOPE, nil
|
||||
case models.CREATE_CONNECTION_METHOD:
|
||||
return constants.SUPERUSER_SCOPE, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,16 @@ var MockLNClientTransactions = []lnclient.Transaction{
|
|||
}
|
||||
var MockLNClientTransaction = &MockLNClientTransactions[0]
|
||||
|
||||
var MockLNClientHoldTransaction = &lnclient.Transaction{
|
||||
Type: "incoming",
|
||||
Invoice: "lntb10n1p5zg5p7dqud4hkx6eqdphkcepqd9h8vmmfvdjsnp4qw988hn4lhpu0my4rf0qkraft3wdx5aa0jnjmusgd23z3s0e9qv62pp5yaulxt6x83u4u0x2pck5pyg7fdxhjsd65c9lmu2a9r05qh0cgl6ssp5x57tsnnuc9hr99a9xzg5ylqma5fwvckxa50jqqay5zykqp83h9kq9qyysgqcqpcxqxf92hqqxh0avuskdnuzkk7mxslsdwem3qq3sf79a4ypmx0hax3rupp043yhv97h25vaarj0xrlcg2fdfdhpztsthettskyaylrz6vweztn0twqqv7kxmr",
|
||||
Description: "mock hold invoice",
|
||||
DescriptionHash: "",
|
||||
Preimage: "4aa083cad11038359b4f614f3a3d6a8298ae17d5275412bc3eca4f5f4d27f2d4",
|
||||
PaymentHash: "2779f32f463c795e3cca0e2d40911e4b4d7941baa60bfdf15d28df405df847f5",
|
||||
Amount: 2000,
|
||||
}
|
||||
|
||||
type MockLn struct {
|
||||
PayInvoiceResponses []*lnclient.PayInvoiceResponse
|
||||
PayInvoiceErrors []error
|
||||
|
|
@ -106,6 +116,18 @@ func (mln *MockLn) MakeInvoice(ctx context.Context, amount int64, description st
|
|||
return MockLNClientTransaction, nil
|
||||
}
|
||||
|
||||
func (mln *MockLn) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
return MockLNClientHoldTransaction, nil
|
||||
}
|
||||
|
||||
func (mln *MockLn) SettleHoldInvoice(ctx context.Context, preimage string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mln *MockLn) CancelHoldInvoice(ctx context.Context, paymentHash string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mln *MockLn) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
|
||||
if mln.MockTransaction != nil {
|
||||
return mln.MockTransaction, nil
|
||||
|
|
|
|||
|
|
@ -1,12 +1,28 @@
|
|||
// Code generated by mockery v2.53.2. DO NOT EDIT.
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
config "github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/config"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewMockConfig creates a new instance of MockConfig. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockConfig(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockConfig {
|
||||
mock := &MockConfig{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// MockConfig is an autogenerated mock type for the Config type
|
||||
type MockConfig struct {
|
||||
mock.Mock
|
||||
|
|
@ -20,21 +36,20 @@ func (_m *MockConfig) EXPECT() *MockConfig_Expecter {
|
|||
return &MockConfig_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// ChangeUnlockPassword provides a mock function with given fields: currentUnlockPassword, newUnlockPassword
|
||||
func (_m *MockConfig) ChangeUnlockPassword(currentUnlockPassword string, newUnlockPassword string) error {
|
||||
ret := _m.Called(currentUnlockPassword, newUnlockPassword)
|
||||
// ChangeUnlockPassword provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) ChangeUnlockPassword(currentUnlockPassword string, newUnlockPassword string) error {
|
||||
ret := _mock.Called(currentUnlockPassword, newUnlockPassword)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ChangeUnlockPassword")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(currentUnlockPassword, newUnlockPassword)
|
||||
if returnFunc, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = returnFunc(currentUnlockPassword, newUnlockPassword)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -44,8 +59,8 @@ 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)}
|
||||
}
|
||||
|
|
@ -57,31 +72,30 @@ func (_c *MockConfig_ChangeUnlockPassword_Call) Run(run func(currentUnlockPasswo
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_ChangeUnlockPassword_Call) Return(_a0 error) *MockConfig_ChangeUnlockPassword_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_ChangeUnlockPassword_Call) Return(err error) *MockConfig_ChangeUnlockPassword_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_ChangeUnlockPassword_Call) RunAndReturn(run func(string, string) error) *MockConfig_ChangeUnlockPassword_Call {
|
||||
func (_c *MockConfig_ChangeUnlockPassword_Call) RunAndReturn(run func(currentUnlockPassword string, newUnlockPassword string) error) *MockConfig_ChangeUnlockPassword_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CheckUnlockPassword provides a mock function with given fields: password
|
||||
func (_m *MockConfig) CheckUnlockPassword(password string) bool {
|
||||
ret := _m.Called(password)
|
||||
// CheckUnlockPassword provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) CheckUnlockPassword(password string) bool {
|
||||
ret := _mock.Called(password)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CheckUnlockPassword")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string) bool); ok {
|
||||
r0 = rf(password)
|
||||
if returnFunc, ok := ret.Get(0).(func(string) bool); ok {
|
||||
r0 = returnFunc(password)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +105,7 @@ 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)}
|
||||
}
|
||||
|
|
@ -103,19 +117,19 @@ func (_c *MockConfig_CheckUnlockPassword_Call) Run(run func(password string)) *M
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_CheckUnlockPassword_Call) Return(_a0 bool) *MockConfig_CheckUnlockPassword_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_CheckUnlockPassword_Call) Return(b bool) *MockConfig_CheckUnlockPassword_Call {
|
||||
_c.Call.Return(b)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_CheckUnlockPassword_Call) RunAndReturn(run func(string) bool) *MockConfig_CheckUnlockPassword_Call {
|
||||
func (_c *MockConfig_CheckUnlockPassword_Call) RunAndReturn(run func(password string) bool) *MockConfig_CheckUnlockPassword_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: key, encryptionKey
|
||||
func (_m *MockConfig) Get(key string, encryptionKey string) (string, error) {
|
||||
ret := _m.Called(key, encryptionKey)
|
||||
// Get provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) Get(key string, encryptionKey string) (string, error) {
|
||||
ret := _mock.Called(key, encryptionKey)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
|
|
@ -123,21 +137,19 @@ func (_m *MockConfig) Get(key string, encryptionKey string) (string, error) {
|
|||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) (string, error)); ok {
|
||||
return rf(key, encryptionKey)
|
||||
if returnFunc, ok := ret.Get(0).(func(string, string) (string, error)); ok {
|
||||
return returnFunc(key, encryptionKey)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) string); ok {
|
||||
r0 = rf(key, encryptionKey)
|
||||
if returnFunc, ok := ret.Get(0).(func(string, string) string); ok {
|
||||
r0 = returnFunc(key, encryptionKey)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(key, encryptionKey)
|
||||
if returnFunc, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = returnFunc(key, encryptionKey)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
|
|
@ -147,8 +159,8 @@ 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)}
|
||||
}
|
||||
|
|
@ -160,31 +172,30 @@ func (_c *MockConfig_Get_Call) Run(run func(key string, encryptionKey string)) *
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_Get_Call) Return(_a0 string, _a1 error) *MockConfig_Get_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
func (_c *MockConfig_Get_Call) Return(s string, err error) *MockConfig_Get_Call {
|
||||
_c.Call.Return(s, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_Get_Call) RunAndReturn(run func(string, string) (string, error)) *MockConfig_Get_Call {
|
||||
func (_c *MockConfig_Get_Call) RunAndReturn(run func(key string, encryptionKey string) (string, error)) *MockConfig_Get_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetCurrency provides a mock function with no fields
|
||||
func (_m *MockConfig) GetCurrency() string {
|
||||
ret := _m.Called()
|
||||
// GetCurrency provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) GetCurrency() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetCurrency")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -205,8 +216,8 @@ func (_c *MockConfig_GetCurrency_Call) Run(run func()) *MockConfig_GetCurrency_C
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetCurrency_Call) Return(_a0 string) *MockConfig_GetCurrency_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_GetCurrency_Call) Return(s string) *MockConfig_GetCurrency_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -215,23 +226,22 @@ func (_c *MockConfig_GetCurrency_Call) RunAndReturn(run func() string) *MockConf
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetEnv provides a mock function with no fields
|
||||
func (_m *MockConfig) GetEnv() *config.AppConfig {
|
||||
ret := _m.Called()
|
||||
// GetEnv provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) GetEnv() *config.AppConfig {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetEnv")
|
||||
}
|
||||
|
||||
var r0 *config.AppConfig
|
||||
if rf, ok := ret.Get(0).(func() *config.AppConfig); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() *config.AppConfig); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*config.AppConfig)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -252,8 +262,8 @@ func (_c *MockConfig_GetEnv_Call) Run(run func()) *MockConfig_GetEnv_Call {
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetEnv_Call) Return(_a0 *config.AppConfig) *MockConfig_GetEnv_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_GetEnv_Call) Return(appConfig *config.AppConfig) *MockConfig_GetEnv_Call {
|
||||
_c.Call.Return(appConfig)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -262,21 +272,20 @@ func (_c *MockConfig_GetEnv_Call) RunAndReturn(run func() *config.AppConfig) *Mo
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetJWTSecret provides a mock function with no fields
|
||||
func (_m *MockConfig) GetJWTSecret() string {
|
||||
ret := _m.Called()
|
||||
// GetJWTSecret provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) GetJWTSecret() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetJWTSecret")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -297,8 +306,8 @@ func (_c *MockConfig_GetJWTSecret_Call) Run(run func()) *MockConfig_GetJWTSecret
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetJWTSecret_Call) Return(_a0 string) *MockConfig_GetJWTSecret_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_GetJWTSecret_Call) Return(s string) *MockConfig_GetJWTSecret_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -307,21 +316,20 @@ func (_c *MockConfig_GetJWTSecret_Call) RunAndReturn(run func() string) *MockCon
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetNetwork provides a mock function with no fields
|
||||
func (_m *MockConfig) GetNetwork() string {
|
||||
ret := _m.Called()
|
||||
// GetNetwork provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) GetNetwork() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetNetwork")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -342,8 +350,8 @@ func (_c *MockConfig_GetNetwork_Call) Run(run func()) *MockConfig_GetNetwork_Cal
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetNetwork_Call) Return(_a0 string) *MockConfig_GetNetwork_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_GetNetwork_Call) Return(s string) *MockConfig_GetNetwork_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -352,21 +360,20 @@ func (_c *MockConfig_GetNetwork_Call) RunAndReturn(run func() string) *MockConfi
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetRelayUrl provides a mock function with no fields
|
||||
func (_m *MockConfig) GetRelayUrl() string {
|
||||
ret := _m.Called()
|
||||
// GetRelayUrl provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) GetRelayUrl() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRelayUrl")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -387,8 +394,8 @@ func (_c *MockConfig_GetRelayUrl_Call) Run(run func()) *MockConfig_GetRelayUrl_C
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetRelayUrl_Call) Return(_a0 string) *MockConfig_GetRelayUrl_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_GetRelayUrl_Call) Return(s string) *MockConfig_GetRelayUrl_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -397,21 +404,20 @@ func (_c *MockConfig_GetRelayUrl_Call) RunAndReturn(run func() string) *MockConf
|
|||
return _c
|
||||
}
|
||||
|
||||
// SaveUnlockPasswordCheck provides a mock function with given fields: encryptionKey
|
||||
func (_m *MockConfig) SaveUnlockPasswordCheck(encryptionKey string) error {
|
||||
ret := _m.Called(encryptionKey)
|
||||
// SaveUnlockPasswordCheck provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) SaveUnlockPasswordCheck(encryptionKey string) error {
|
||||
ret := _mock.Called(encryptionKey)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SaveUnlockPasswordCheck")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(encryptionKey)
|
||||
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = returnFunc(encryptionKey)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -421,7 +427,7 @@ 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)}
|
||||
}
|
||||
|
|
@ -433,31 +439,30 @@ func (_c *MockConfig_SaveUnlockPasswordCheck_Call) Run(run func(encryptionKey st
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SaveUnlockPasswordCheck_Call) Return(_a0 error) *MockConfig_SaveUnlockPasswordCheck_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_SaveUnlockPasswordCheck_Call) Return(err error) *MockConfig_SaveUnlockPasswordCheck_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SaveUnlockPasswordCheck_Call) RunAndReturn(run func(string) error) *MockConfig_SaveUnlockPasswordCheck_Call {
|
||||
func (_c *MockConfig_SaveUnlockPasswordCheck_Call) RunAndReturn(run func(encryptionKey string) error) *MockConfig_SaveUnlockPasswordCheck_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetAutoUnlockPassword provides a mock function with given fields: unlockPassword
|
||||
func (_m *MockConfig) SetAutoUnlockPassword(unlockPassword string) error {
|
||||
ret := _m.Called(unlockPassword)
|
||||
// SetAutoUnlockPassword provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) SetAutoUnlockPassword(unlockPassword string) error {
|
||||
ret := _mock.Called(unlockPassword)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetAutoUnlockPassword")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(unlockPassword)
|
||||
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = returnFunc(unlockPassword)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -467,7 +472,7 @@ 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)}
|
||||
}
|
||||
|
|
@ -479,31 +484,30 @@ func (_c *MockConfig_SetAutoUnlockPassword_Call) Run(run func(unlockPassword str
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetAutoUnlockPassword_Call) Return(_a0 error) *MockConfig_SetAutoUnlockPassword_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_SetAutoUnlockPassword_Call) Return(err error) *MockConfig_SetAutoUnlockPassword_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetAutoUnlockPassword_Call) RunAndReturn(run func(string) error) *MockConfig_SetAutoUnlockPassword_Call {
|
||||
func (_c *MockConfig_SetAutoUnlockPassword_Call) RunAndReturn(run func(unlockPassword string) error) *MockConfig_SetAutoUnlockPassword_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetCurrency provides a mock function with given fields: value
|
||||
func (_m *MockConfig) SetCurrency(value string) error {
|
||||
ret := _m.Called(value)
|
||||
// SetCurrency provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) SetCurrency(value string) error {
|
||||
ret := _mock.Called(value)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetCurrency")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(value)
|
||||
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = returnFunc(value)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -513,7 +517,7 @@ 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)}
|
||||
}
|
||||
|
|
@ -525,31 +529,30 @@ func (_c *MockConfig_SetCurrency_Call) Run(run func(value string)) *MockConfig_S
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetCurrency_Call) Return(_a0 error) *MockConfig_SetCurrency_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_SetCurrency_Call) Return(err error) *MockConfig_SetCurrency_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetCurrency_Call) RunAndReturn(run func(string) error) *MockConfig_SetCurrency_Call {
|
||||
func (_c *MockConfig_SetCurrency_Call) RunAndReturn(run func(value string) error) *MockConfig_SetCurrency_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetIgnore provides a mock function with given fields: key, value, encryptionKey
|
||||
func (_m *MockConfig) SetIgnore(key string, value string, encryptionKey string) error {
|
||||
ret := _m.Called(key, value, encryptionKey)
|
||||
// SetIgnore provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) SetIgnore(key string, value string, encryptionKey string) error {
|
||||
ret := _mock.Called(key, value, encryptionKey)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetIgnore")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(key, value, encryptionKey)
|
||||
if returnFunc, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = returnFunc(key, value, encryptionKey)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -559,9 +562,9 @@ 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)}
|
||||
}
|
||||
|
|
@ -573,31 +576,30 @@ func (_c *MockConfig_SetIgnore_Call) Run(run func(key string, value string, encr
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetIgnore_Call) Return(_a0 error) *MockConfig_SetIgnore_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_SetIgnore_Call) Return(err error) *MockConfig_SetIgnore_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetIgnore_Call) RunAndReturn(run func(string, string, string) error) *MockConfig_SetIgnore_Call {
|
||||
func (_c *MockConfig_SetIgnore_Call) RunAndReturn(run func(key string, value string, encryptionKey string) error) *MockConfig_SetIgnore_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUpdate provides a mock function with given fields: key, value, encryptionKey
|
||||
func (_m *MockConfig) SetUpdate(key string, value string, encryptionKey string) error {
|
||||
ret := _m.Called(key, value, encryptionKey)
|
||||
// SetUpdate provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) SetUpdate(key string, value string, encryptionKey string) error {
|
||||
ret := _mock.Called(key, value, encryptionKey)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetUpdate")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(key, value, encryptionKey)
|
||||
if returnFunc, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = returnFunc(key, value, encryptionKey)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -607,9 +609,9 @@ 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)}
|
||||
}
|
||||
|
|
@ -621,31 +623,30 @@ func (_c *MockConfig_SetUpdate_Call) Run(run func(key string, value string, encr
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetUpdate_Call) Return(_a0 error) *MockConfig_SetUpdate_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_SetUpdate_Call) Return(err error) *MockConfig_SetUpdate_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetUpdate_Call) RunAndReturn(run func(string, string, string) error) *MockConfig_SetUpdate_Call {
|
||||
func (_c *MockConfig_SetUpdate_Call) RunAndReturn(run func(key string, value string, encryptionKey string) error) *MockConfig_SetUpdate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetupCompleted provides a mock function with no fields
|
||||
func (_m *MockConfig) SetupCompleted() bool {
|
||||
ret := _m.Called()
|
||||
// SetupCompleted provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) SetupCompleted() bool {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetupCompleted")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -666,8 +667,8 @@ func (_c *MockConfig_SetupCompleted_Call) Run(run func()) *MockConfig_SetupCompl
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_SetupCompleted_Call) Return(_a0 bool) *MockConfig_SetupCompleted_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockConfig_SetupCompleted_Call) Return(b bool) *MockConfig_SetupCompleted_Call {
|
||||
_c.Call.Return(b)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -675,17 +676,3 @@ func (_c *MockConfig_SetupCompleted_Call) RunAndReturn(run func() bool) *MockCon
|
|||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockConfig creates a new instance of MockConfig. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockConfig(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockConfig {
|
||||
mock := &MockConfig{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,26 +1,35 @@
|
|||
// Code generated by mockery v2.53.2. DO NOT EDIT.
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
alby "github.com/getAlby/hub/alby"
|
||||
config "github.com/getAlby/hub/config"
|
||||
|
||||
events "github.com/getAlby/hub/events"
|
||||
|
||||
gorm "gorm.io/gorm"
|
||||
|
||||
keys "github.com/getAlby/hub/service/keys"
|
||||
|
||||
lnclient "github.com/getAlby/hub/lnclient"
|
||||
|
||||
"github.com/getAlby/hub/alby"
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/service/keys"
|
||||
"github.com/getAlby/hub/swaps"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
swaps "github.com/getAlby/hub/swaps"
|
||||
|
||||
transactions "github.com/getAlby/hub/transactions"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockService(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockService {
|
||||
mock := &MockService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// MockService is an autogenerated mock type for the Service type
|
||||
type MockService struct {
|
||||
mock.Mock
|
||||
|
|
@ -34,23 +43,22 @@ func (_m *MockService) EXPECT() *MockService_Expecter {
|
|||
return &MockService_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetAlbyOAuthSvc provides a mock function with no fields
|
||||
func (_m *MockService) GetAlbyOAuthSvc() alby.AlbyOAuthService {
|
||||
ret := _m.Called()
|
||||
// GetAlbyOAuthSvc provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetAlbyOAuthSvc() alby.AlbyOAuthService {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAlbyOAuthSvc")
|
||||
}
|
||||
|
||||
var r0 alby.AlbyOAuthService
|
||||
if rf, ok := ret.Get(0).(func() alby.AlbyOAuthService); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() alby.AlbyOAuthService); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(alby.AlbyOAuthService)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -71,8 +79,8 @@ func (_c *MockService_GetAlbyOAuthSvc_Call) Run(run func()) *MockService_GetAlby
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetAlbyOAuthSvc_Call) Return(_a0 alby.AlbyOAuthService) *MockService_GetAlbyOAuthSvc_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetAlbyOAuthSvc_Call) Return(albyOAuthService alby.AlbyOAuthService) *MockService_GetAlbyOAuthSvc_Call {
|
||||
_c.Call.Return(albyOAuthService)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -81,23 +89,22 @@ func (_c *MockService_GetAlbyOAuthSvc_Call) RunAndReturn(run func() alby.AlbyOAu
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetConfig provides a mock function with no fields
|
||||
func (_m *MockService) GetConfig() config.Config {
|
||||
ret := _m.Called()
|
||||
// GetConfig provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetConfig() config.Config {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetConfig")
|
||||
}
|
||||
|
||||
var r0 config.Config
|
||||
if rf, ok := ret.Get(0).(func() config.Config); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() config.Config); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(config.Config)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -118,8 +125,8 @@ func (_c *MockService_GetConfig_Call) Run(run func()) *MockService_GetConfig_Cal
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetConfig_Call) Return(_a0 config.Config) *MockService_GetConfig_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetConfig_Call) Return(config1 config.Config) *MockService_GetConfig_Call {
|
||||
_c.Call.Return(config1)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -128,23 +135,22 @@ func (_c *MockService_GetConfig_Call) RunAndReturn(run func() config.Config) *Mo
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetDB provides a mock function with no fields
|
||||
func (_m *MockService) GetDB() *gorm.DB {
|
||||
ret := _m.Called()
|
||||
// GetDB provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetDB() *gorm.DB {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetDB")
|
||||
}
|
||||
|
||||
var r0 *gorm.DB
|
||||
if rf, ok := ret.Get(0).(func() *gorm.DB); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() *gorm.DB); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*gorm.DB)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -165,8 +171,8 @@ func (_c *MockService_GetDB_Call) Run(run func()) *MockService_GetDB_Call {
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetDB_Call) Return(_a0 *gorm.DB) *MockService_GetDB_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetDB_Call) Return(dB *gorm.DB) *MockService_GetDB_Call {
|
||||
_c.Call.Return(dB)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -175,23 +181,22 @@ func (_c *MockService_GetDB_Call) RunAndReturn(run func() *gorm.DB) *MockService
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetEventPublisher provides a mock function with no fields
|
||||
func (_m *MockService) GetEventPublisher() events.EventPublisher {
|
||||
ret := _m.Called()
|
||||
// GetEventPublisher provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetEventPublisher() events.EventPublisher {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetEventPublisher")
|
||||
}
|
||||
|
||||
var r0 events.EventPublisher
|
||||
if rf, ok := ret.Get(0).(func() events.EventPublisher); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() events.EventPublisher); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(events.EventPublisher)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -212,8 +217,8 @@ func (_c *MockService_GetEventPublisher_Call) Run(run func()) *MockService_GetEv
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetEventPublisher_Call) Return(_a0 events.EventPublisher) *MockService_GetEventPublisher_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetEventPublisher_Call) Return(eventPublisher events.EventPublisher) *MockService_GetEventPublisher_Call {
|
||||
_c.Call.Return(eventPublisher)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -222,23 +227,22 @@ func (_c *MockService_GetEventPublisher_Call) RunAndReturn(run func() events.Eve
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetKeys provides a mock function with no fields
|
||||
func (_m *MockService) GetKeys() keys.Keys {
|
||||
ret := _m.Called()
|
||||
// GetKeys provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetKeys() keys.Keys {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetKeys")
|
||||
}
|
||||
|
||||
var r0 keys.Keys
|
||||
if rf, ok := ret.Get(0).(func() keys.Keys); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() keys.Keys); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(keys.Keys)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -259,8 +263,8 @@ func (_c *MockService_GetKeys_Call) Run(run func()) *MockService_GetKeys_Call {
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetKeys_Call) Return(_a0 keys.Keys) *MockService_GetKeys_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetKeys_Call) Return(keys1 keys.Keys) *MockService_GetKeys_Call {
|
||||
_c.Call.Return(keys1)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -269,23 +273,22 @@ func (_c *MockService_GetKeys_Call) RunAndReturn(run func() keys.Keys) *MockServ
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetLNClient provides a mock function with no fields
|
||||
func (_m *MockService) GetLNClient() lnclient.LNClient {
|
||||
ret := _m.Called()
|
||||
// GetLNClient provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetLNClient() lnclient.LNClient {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetLNClient")
|
||||
}
|
||||
|
||||
var r0 lnclient.LNClient
|
||||
if rf, ok := ret.Get(0).(func() lnclient.LNClient); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() lnclient.LNClient); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(lnclient.LNClient)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -306,8 +309,8 @@ func (_c *MockService_GetLNClient_Call) Run(run func()) *MockService_GetLNClient
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetLNClient_Call) Return(_a0 lnclient.LNClient) *MockService_GetLNClient_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetLNClient_Call) Return(lNClient lnclient.LNClient) *MockService_GetLNClient_Call {
|
||||
_c.Call.Return(lNClient)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -316,21 +319,20 @@ func (_c *MockService_GetLNClient_Call) RunAndReturn(run func() lnclient.LNClien
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetStartupState provides a mock function with no fields
|
||||
func (_m *MockService) GetStartupState() string {
|
||||
ret := _m.Called()
|
||||
// GetStartupState provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetStartupState() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetStartupState")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -351,8 +353,8 @@ func (_c *MockService_GetStartupState_Call) Run(run func()) *MockService_GetStar
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetStartupState_Call) Return(_a0 string) *MockService_GetStartupState_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetStartupState_Call) Return(s string) *MockService_GetStartupState_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -361,23 +363,22 @@ func (_c *MockService_GetStartupState_Call) RunAndReturn(run func() string) *Moc
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetSwapsService provides a mock function with no fields
|
||||
func (_m *MockService) GetSwapsService() swaps.SwapsService {
|
||||
ret := _m.Called()
|
||||
// GetSwapsService provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetSwapsService() swaps.SwapsService {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSwapsService")
|
||||
}
|
||||
|
||||
var r0 swaps.SwapsService
|
||||
if rf, ok := ret.Get(0).(func() swaps.SwapsService); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() swaps.SwapsService); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(swaps.SwapsService)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -398,8 +399,8 @@ func (_c *MockService_GetSwapsService_Call) Run(run func()) *MockService_GetSwap
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetSwapsService_Call) Return(_a0 swaps.SwapsService) *MockService_GetSwapsService_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetSwapsService_Call) Return(swapsService swaps.SwapsService) *MockService_GetSwapsService_Call {
|
||||
_c.Call.Return(swapsService)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -408,23 +409,22 @@ func (_c *MockService_GetSwapsService_Call) RunAndReturn(run func() swaps.SwapsS
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetTransactionsService provides a mock function with no fields
|
||||
func (_m *MockService) GetTransactionsService() transactions.TransactionsService {
|
||||
ret := _m.Called()
|
||||
// GetTransactionsService provides a mock function for the type MockService
|
||||
func (_mock *MockService) GetTransactionsService() transactions.TransactionsService {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetTransactionsService")
|
||||
}
|
||||
|
||||
var r0 transactions.TransactionsService
|
||||
if rf, ok := ret.Get(0).(func() transactions.TransactionsService); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() transactions.TransactionsService); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(transactions.TransactionsService)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -445,8 +445,8 @@ func (_c *MockService_GetTransactionsService_Call) Run(run func()) *MockService_
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_GetTransactionsService_Call) Return(_a0 transactions.TransactionsService) *MockService_GetTransactionsService_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_GetTransactionsService_Call) Return(transactionsService transactions.TransactionsService) *MockService_GetTransactionsService_Call {
|
||||
_c.Call.Return(transactionsService)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -455,21 +455,20 @@ func (_c *MockService_GetTransactionsService_Call) RunAndReturn(run func() trans
|
|||
return _c
|
||||
}
|
||||
|
||||
// IsRelayReady provides a mock function with no fields
|
||||
func (_m *MockService) IsRelayReady() bool {
|
||||
ret := _m.Called()
|
||||
// IsRelayReady provides a mock function for the type MockService
|
||||
func (_mock *MockService) IsRelayReady() bool {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsRelayReady")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -490,8 +489,8 @@ func (_c *MockService_IsRelayReady_Call) Run(run func()) *MockService_IsRelayRea
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_IsRelayReady_Call) Return(_a0 bool) *MockService_IsRelayReady_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_IsRelayReady_Call) Return(b bool) *MockService_IsRelayReady_Call {
|
||||
_c.Call.Return(b)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -500,9 +499,10 @@ func (_c *MockService_IsRelayReady_Call) RunAndReturn(run func() bool) *MockServ
|
|||
return _c
|
||||
}
|
||||
|
||||
// Shutdown provides a mock function with no fields
|
||||
func (_m *MockService) Shutdown() {
|
||||
_m.Called()
|
||||
// Shutdown provides a mock function for the type MockService
|
||||
func (_mock *MockService) Shutdown() {
|
||||
_mock.Called()
|
||||
return
|
||||
}
|
||||
|
||||
// MockService_Shutdown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Shutdown'
|
||||
|
|
@ -532,21 +532,20 @@ func (_c *MockService_Shutdown_Call) RunAndReturn(run func()) *MockService_Shutd
|
|||
return _c
|
||||
}
|
||||
|
||||
// StartApp provides a mock function with given fields: encryptionKey
|
||||
func (_m *MockService) StartApp(encryptionKey string) error {
|
||||
ret := _m.Called(encryptionKey)
|
||||
// StartApp provides a mock function for the type MockService
|
||||
func (_mock *MockService) StartApp(encryptionKey string) error {
|
||||
ret := _mock.Called(encryptionKey)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for StartApp")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(encryptionKey)
|
||||
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = returnFunc(encryptionKey)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -556,7 +555,7 @@ 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)}
|
||||
}
|
||||
|
|
@ -568,31 +567,30 @@ func (_c *MockService_StartApp_Call) Run(run func(encryptionKey string)) *MockSe
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_StartApp_Call) Return(_a0 error) *MockService_StartApp_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_StartApp_Call) Return(err error) *MockService_StartApp_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_StartApp_Call) RunAndReturn(run func(string) error) *MockService_StartApp_Call {
|
||||
func (_c *MockService_StartApp_Call) RunAndReturn(run func(encryptionKey string) error) *MockService_StartApp_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// StartAutoSwaps provides a mock function with no fields
|
||||
func (_m *MockService) StartAutoSwaps() error {
|
||||
ret := _m.Called()
|
||||
// StartAutoSwaps provides a mock function for the type MockService
|
||||
func (_mock *MockService) StartAutoSwaps() error {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for StartAutoSwaps")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
if returnFunc, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
|
|
@ -613,8 +611,8 @@ func (_c *MockService_StartAutoSwaps_Call) Run(run func()) *MockService_StartAut
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockService_StartAutoSwaps_Call) Return(_a0 error) *MockService_StartAutoSwaps_Call {
|
||||
_c.Call.Return(_a0)
|
||||
func (_c *MockService_StartAutoSwaps_Call) Return(err error) *MockService_StartAutoSwaps_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
|
@ -623,9 +621,10 @@ func (_c *MockService_StartAutoSwaps_Call) RunAndReturn(run func() error) *MockS
|
|||
return _c
|
||||
}
|
||||
|
||||
// StopApp provides a mock function with no fields
|
||||
func (_m *MockService) StopApp() {
|
||||
_m.Called()
|
||||
// StopApp provides a mock function for the type MockService
|
||||
func (_mock *MockService) StopApp() {
|
||||
_mock.Called()
|
||||
return
|
||||
}
|
||||
|
||||
// MockService_StopApp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StopApp'
|
||||
|
|
@ -654,17 +653,3 @@ func (_c *MockService_StopApp_Call) RunAndReturn(run func()) *MockService_StopAp
|
|||
_c.Run(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockService(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockService {
|
||||
mock := &MockService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
|
|
|||
31
transactions/hold_invoice_self_payment_consumer.go
Normal file
31
transactions/hold_invoice_self_payment_consumer.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package transactions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/events"
|
||||
)
|
||||
|
||||
type holdInvoiceUpdatedConsumer struct {
|
||||
paymentHash string
|
||||
settledChannel chan<- *db.Transaction
|
||||
canceledChannel chan<- *db.Transaction
|
||||
}
|
||||
|
||||
func newHoldInvoiceUpdatedConsumer(paymentHash string, settledChannel chan<- *db.Transaction, canceledChannel chan<- *db.Transaction) *holdInvoiceUpdatedConsumer {
|
||||
return &holdInvoiceUpdatedConsumer{
|
||||
paymentHash: paymentHash,
|
||||
settledChannel: settledChannel,
|
||||
canceledChannel: canceledChannel,
|
||||
}
|
||||
}
|
||||
|
||||
func (consumer *holdInvoiceUpdatedConsumer) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) {
|
||||
if event.Event == "nwc_payment_received" && event.Properties.(*db.Transaction).PaymentHash == consumer.paymentHash {
|
||||
consumer.settledChannel <- event.Properties.(*db.Transaction)
|
||||
}
|
||||
if event.Event == "nwc_hold_invoice_canceled" && event.Properties.(*db.Transaction).PaymentHash == consumer.paymentHash {
|
||||
consumer.canceledChannel <- event.Properties.(*db.Transaction)
|
||||
}
|
||||
}
|
||||
93
transactions/self_hold_payments_test.go
Normal file
93
transactions/self_hold_payments_test.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package transactions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/tests"
|
||||
)
|
||||
|
||||
func TestSelfHoldPaymentSettled(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
defer svc.Remove()
|
||||
|
||||
preimage := tests.MockLNClientHoldTransaction.Preimage
|
||||
paymentHash := tests.MockLNClientHoldTransaction.PaymentHash
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.MakeHoldInvoice(ctx, 1000, "Hold payment test", "", 0, paymentHash, nil, svc.LNClient, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, transaction.Hold)
|
||||
// use the pubkey from the decoded tests.MockLNClientHoldTransaction invoice
|
||||
svc.LNClient.(*tests.MockLn).Pubkey = "038a73de75fdc3c7ec951a5e0b0fa95c5cd353bd7ca72df2086aa228c1f92819a5"
|
||||
|
||||
go func() {
|
||||
result, err := transactionsService.SendPaymentSync(ctx, transaction.PaymentRequest, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, result.State)
|
||||
assert.Equal(t, true, result.SelfPayment)
|
||||
assert.Equal(t, false, result.Hold)
|
||||
}()
|
||||
|
||||
// wait for payment to be accepted
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
settledTransaction, err := transactionsService.SettleHoldInvoice(ctx, preimage, svc.LNClient)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, settledTransaction)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, settledTransaction.State)
|
||||
assert.Equal(t, true, settledTransaction.SelfPayment)
|
||||
assert.Equal(t, true, settledTransaction.Hold)
|
||||
}
|
||||
func TestSelfHoldPaymentCanceled(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
defer svc.Remove()
|
||||
|
||||
paymentHash := tests.MockLNClientHoldTransaction.PaymentHash
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
transaction, err := transactionsService.MakeHoldInvoice(ctx, 1000, "Hold payment test", "", 0, paymentHash, nil, svc.LNClient, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, transaction.Hold)
|
||||
// use the pubkey from the decoded tests.MockLNClientHoldTransaction invoice
|
||||
svc.LNClient.(*tests.MockLn).Pubkey = "038a73de75fdc3c7ec951a5e0b0fa95c5cd353bd7ca72df2086aa228c1f92819a5"
|
||||
|
||||
go func() {
|
||||
result, err := transactionsService.SendPaymentSync(ctx, transaction.PaymentRequest, nil, nil, svc.LNClient, nil, nil, nil)
|
||||
assert.ErrorIs(t, err, lnclient.NewHoldInvoiceCanceledError())
|
||||
assert.Nil(t, result)
|
||||
|
||||
outgoingTransactionType := constants.TRANSACTION_TYPE_OUTGOING
|
||||
failedOutgoingTransaction, err := transactionsService.LookupTransaction(ctx, paymentHash, &outgoingTransactionType, svc.LNClient, nil)
|
||||
assert.Nil(t, err)
|
||||
require.NotNil(t, failedOutgoingTransaction)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_FAILED, failedOutgoingTransaction.State)
|
||||
assert.Equal(t, true, failedOutgoingTransaction.SelfPayment)
|
||||
assert.Equal(t, false, failedOutgoingTransaction.Hold)
|
||||
}()
|
||||
|
||||
// wait for payment to be accepted
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
err = transactionsService.CancelHoldInvoice(ctx, paymentHash, svc.LNClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
incomingTransactionType := constants.TRANSACTION_TYPE_INCOMING
|
||||
updatedHoldTransaction, err := transactionsService.LookupTransaction(ctx, paymentHash, &incomingTransactionType, svc.LNClient, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_FAILED, updatedHoldTransaction.State)
|
||||
assert.Equal(t, true, updatedHoldTransaction.SelfPayment)
|
||||
assert.Equal(t, true, updatedHoldTransaction.Hold)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
|
@ -40,6 +40,9 @@ type TransactionsService interface {
|
|||
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool) (transactions []Transaction, totalCount uint64, err error)
|
||||
SendPaymentSync(ctx context.Context, payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint, timeoutSeconds *int64) (*Transaction, error)
|
||||
SendKeysend(ctx context.Context, amount uint64, destination string, customRecords []lnclient.TLVRecord, preimage string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
MakeHoldInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, paymentHash string, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
SettleHoldInvoice(ctx context.Context, preimage string, lnClient lnclient.LNClient) (*Transaction, error)
|
||||
CancelHoldInvoice(ctx context.Context, paymentHash string, lnClient lnclient.LNClient) error
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -188,6 +191,60 @@ func (svc *transactionsService) MakeInvoice(ctx context.Context, amount uint64,
|
|||
return &dbTransaction, nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) MakeHoldInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, paymentHash string, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) {
|
||||
var err error
|
||||
var metadataBytes []byte
|
||||
if metadata != nil {
|
||||
metadataBytes, err = json.Marshal(metadata)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to serialize metadata")
|
||||
return nil, err
|
||||
}
|
||||
if len(metadataBytes) > constants.INVOICE_METADATA_MAX_LENGTH {
|
||||
return nil, fmt.Errorf("encoded invoice metadata provided is too large. Limit: %d Received: %d", constants.INVOICE_METADATA_MAX_LENGTH, len(metadataBytes))
|
||||
}
|
||||
}
|
||||
|
||||
lnClientTransaction, err := lnClient.MakeHoldInvoice(ctx, int64(amount), description, descriptionHash, int64(expiry), paymentHash)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to create hold invoice via LN client")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preimage *string
|
||||
if lnClientTransaction.Preimage != "" {
|
||||
preimage = &lnClientTransaction.Preimage
|
||||
}
|
||||
|
||||
var expiresAt *time.Time
|
||||
if lnClientTransaction.ExpiresAt != nil {
|
||||
expiresAtValue := time.Unix(*lnClientTransaction.ExpiresAt, 0)
|
||||
expiresAt = &expiresAtValue
|
||||
}
|
||||
|
||||
dbTransaction := db.Transaction{
|
||||
AppId: appId,
|
||||
RequestEventId: requestEventId,
|
||||
Type: constants.TRANSACTION_TYPE_INCOMING,
|
||||
State: constants.TRANSACTION_STATE_PENDING,
|
||||
AmountMsat: uint64(lnClientTransaction.Amount),
|
||||
Description: description,
|
||||
DescriptionHash: descriptionHash,
|
||||
PaymentRequest: lnClientTransaction.Invoice,
|
||||
PaymentHash: lnClientTransaction.PaymentHash,
|
||||
ExpiresAt: expiresAt,
|
||||
Preimage: preimage,
|
||||
Metadata: datatypes.JSON(metadataBytes),
|
||||
Hold: true,
|
||||
}
|
||||
err = svc.db.Create(&dbTransaction).Error
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to create hold invoice DB transaction")
|
||||
return nil, err
|
||||
}
|
||||
return &dbTransaction, nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint, timeoutSeconds *int64) (*Transaction, error) {
|
||||
var metadataBytes []byte
|
||||
if metadata != nil {
|
||||
|
|
@ -291,7 +348,7 @@ func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq stri
|
|||
|
||||
var response *lnclient.PayInvoiceResponse
|
||||
if selfPayment {
|
||||
response, err = svc.interceptSelfPayment(paymentRequest.PaymentHash)
|
||||
response, err = svc.interceptSelfPayment(ctx, paymentRequest.PaymentHash, lnClient)
|
||||
} else {
|
||||
response, err = lnClient.SendPaymentSync(ctx, payReq, amountMsat, timeoutSeconds)
|
||||
}
|
||||
|
|
@ -430,7 +487,7 @@ func (svc *transactionsService) SendKeysend(ctx context.Context, amount uint64,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
_, err = svc.interceptSelfPayment(paymentHash)
|
||||
_, err = svc.interceptSelfPayment(ctx, paymentHash, lnClient)
|
||||
if err == nil {
|
||||
payKeysendResponse = &lnclient.PayKeysendResponse{
|
||||
Fee: 0,
|
||||
|
|
@ -740,6 +797,19 @@ func (svc *transactionsService) ConsumeEvent(ctx context.Context, event *events.
|
|||
}).WithError(err).Error("Failed to execute DB transaction")
|
||||
return
|
||||
}
|
||||
|
||||
case "nwc_lnclient_hold_invoice_accepted":
|
||||
lnClientTransaction, ok := event.Properties.(*lnclient.Transaction)
|
||||
if !ok {
|
||||
logger.Logger.WithField("event", event).Error("Failed to cast event properties for hold invoice accepted")
|
||||
return
|
||||
}
|
||||
if lnClientTransaction.SettleDeadline == nil {
|
||||
logger.Logger.WithField("event", event).Error("Transaction has no settle deadline")
|
||||
return
|
||||
}
|
||||
svc.markHoldInvoiceAccepted(lnClientTransaction.PaymentHash, *lnClientTransaction.SettleDeadline, false)
|
||||
|
||||
case "nwc_lnclient_payment_sent":
|
||||
lnClientTransaction, ok := event.Properties.(*lnclient.Transaction)
|
||||
if !ok {
|
||||
|
|
@ -802,7 +872,60 @@ func (svc *transactionsService) ConsumeEvent(ctx context.Context, event *events.
|
|||
}
|
||||
}
|
||||
|
||||
func (svc *transactionsService) interceptSelfPayment(paymentHash string) (*lnclient.PayInvoiceResponse, error) {
|
||||
func (svc *transactionsService) markHoldInvoiceAccepted(paymentHash string, settleDeadline uint32, selfPayment bool) {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
"self_payment": selfPayment,
|
||||
}).Info("Processing hold invoice accepted event")
|
||||
|
||||
var dbTransaction db.Transaction
|
||||
err := svc.db.Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Where("payment_hash = ? AND type = ? AND state = ?", paymentHash, constants.TRANSACTION_TYPE_INCOMING, constants.TRANSACTION_STATE_PENDING).First(&dbTransaction)
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
}).Warn("No corresponding pending incoming transaction found in DB for accepted hold invoice")
|
||||
}
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
}).WithError(result.Error).Error("Failed to query DB for accepted hold invoice")
|
||||
return result.Error
|
||||
}
|
||||
|
||||
err := tx.Model(&dbTransaction).UpdateColumns(map[string]interface{}{
|
||||
"state": constants.TRANSACTION_STATE_ACCEPTED,
|
||||
"self_payment": selfPayment,
|
||||
"settle_deadline": settleDeadline,
|
||||
}).Error
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
"dbTxID": dbTransaction.ID,
|
||||
}).WithError(err).Error("Failed to update hold invoice state to accepted in DB")
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
"dbTxID": dbTransaction.ID,
|
||||
}).Info("Updated hold invoice state to accepted in DB")
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
}).WithError(err).Error("Failed DB transaction for hold invoice accepted event")
|
||||
} else {
|
||||
svc.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_hold_invoice_accepted",
|
||||
Properties: &dbTransaction,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *transactionsService) interceptSelfPayment(ctx context.Context, paymentHash string, lnClient lnclient.LNClient) (*lnclient.PayInvoiceResponse, error) {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Debug("Intercepting self payment")
|
||||
incomingTransaction := db.Transaction{}
|
||||
result := svc.db.Limit(1).Find(&incomingTransaction, &db.Transaction{
|
||||
|
|
@ -817,6 +940,11 @@ func (svc *transactionsService) interceptSelfPayment(paymentHash string) (*lncli
|
|||
if result.RowsAffected == 0 {
|
||||
return nil, NewNotFoundError()
|
||||
}
|
||||
|
||||
if incomingTransaction.Hold {
|
||||
return svc.interceptSelfHoldPayment(ctx, paymentHash, lnClient)
|
||||
}
|
||||
|
||||
if incomingTransaction.Preimage == nil {
|
||||
return nil, errors.New("preimage is not set on transaction. Self payments not supported")
|
||||
}
|
||||
|
|
@ -836,6 +964,51 @@ func (svc *transactionsService) interceptSelfPayment(paymentHash string) (*lncli
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) interceptSelfHoldPayment(ctx context.Context, paymentHash string, lnClient lnclient.LNClient) (*lnclient.PayInvoiceResponse, error) {
|
||||
settledChannel := make(chan *db.Transaction)
|
||||
canceledChannel := make(chan *db.Transaction)
|
||||
|
||||
holdInvoiceUpdatedConsumer := newHoldInvoiceUpdatedConsumer(paymentHash, settledChannel, canceledChannel)
|
||||
|
||||
svc.eventPublisher.RegisterSubscriber(holdInvoiceUpdatedConsumer)
|
||||
|
||||
clientInfo, err := lnClient.GetInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to get client info")
|
||||
}
|
||||
if clientInfo.BlockHeight == 0 {
|
||||
return nil, errors.New("invalid client block height")
|
||||
}
|
||||
|
||||
fakeSettleDeadline := clientInfo.BlockHeight + 24
|
||||
|
||||
svc.markHoldInvoiceAccepted(paymentHash, fakeSettleDeadline, true)
|
||||
|
||||
select {
|
||||
case settledTransaction := <-settledChannel:
|
||||
logger.Logger.WithField("settled_transaction", settledTransaction).Info("self hold payment was settled")
|
||||
if settledTransaction.Preimage == nil {
|
||||
return nil, errors.New("preimage is not set on self hold payment")
|
||||
}
|
||||
|
||||
return &lnclient.PayInvoiceResponse{
|
||||
Preimage: *settledTransaction.Preimage,
|
||||
Fee: 0,
|
||||
}, nil
|
||||
case canceledTransaction := <-canceledChannel:
|
||||
logger.Logger.WithField("canceled_transaction", canceledTransaction).Info("self hold payment was canceled")
|
||||
return nil, lnclient.NewHoldInvoiceCanceledError()
|
||||
case <-time.After(50 * time.Second):
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
}).Error("Timeout executing self payment for hold invoice")
|
||||
}
|
||||
|
||||
svc.eventPublisher.RemoveSubscriber(holdInvoiceUpdatedConsumer)
|
||||
|
||||
return nil, lnclient.NewTimeoutError()
|
||||
}
|
||||
|
||||
func (svc *transactionsService) validateCanPay(tx *gorm.DB, appId *uint, amount uint64, description string) error {
|
||||
amountWithFeeReserve := amount + CalculateFeeReserveMsat(amount)
|
||||
|
||||
|
|
@ -985,6 +1158,128 @@ func (svc *transactionsService) getAppIdFromCustomRecords(customRecords []lnclie
|
|||
return nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) SettleHoldInvoice(ctx context.Context, preimage string, lnClient lnclient.LNClient) (*Transaction, error) {
|
||||
if len(preimage) != 64 {
|
||||
return nil, errors.New("invalid preimage format")
|
||||
}
|
||||
preimageBytes, err := hex.DecodeString(preimage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid preimage hex: %w", err)
|
||||
}
|
||||
|
||||
paymentHashBytes := sha256.Sum256(preimageBytes)
|
||||
paymentHash := hex.EncodeToString(paymentHashBytes[:])
|
||||
|
||||
var dbTransaction db.Transaction
|
||||
result := svc.db.Limit(1).Find(&dbTransaction, &db.Transaction{
|
||||
Type: constants.TRANSACTION_TYPE_INCOMING,
|
||||
State: constants.TRANSACTION_STATE_ACCEPTED,
|
||||
PaymentHash: paymentHash,
|
||||
})
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("Failed to find accepted hold invoice")
|
||||
return nil, errors.New("failed to find accepted hold invoice")
|
||||
}
|
||||
|
||||
if !dbTransaction.SelfPayment {
|
||||
err = lnClient.SettleHoldInvoice(ctx, preimage)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"preimage": preimage,
|
||||
}).WithError(err).Error("Failed to settle hold invoice via LN client")
|
||||
// Don't mark DB as failed here, as the settle might succeed later or might have already succeeded.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var settledTransaction *db.Transaction
|
||||
err = svc.db.Transaction(func(tx *gorm.DB) error {
|
||||
var err error
|
||||
settledTransaction, err = svc.markTransactionSettled(tx, &dbTransaction, preimage, 0, dbTransaction.SelfPayment)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
"preimage": preimage,
|
||||
}).WithError(err).Error("Failed DB transaction while settling hold invoice")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return settledTransaction, nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) CancelHoldInvoice(ctx context.Context, paymentHash string, lnClient lnclient.LNClient) error {
|
||||
|
||||
var dbTransaction db.Transaction
|
||||
result := svc.db.Limit(1).Find(&dbTransaction, &db.Transaction{
|
||||
Type: constants.TRANSACTION_TYPE_INCOMING,
|
||||
State: constants.TRANSACTION_STATE_ACCEPTED,
|
||||
PaymentHash: paymentHash,
|
||||
})
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
logger.Logger.WithField("payment_hash", paymentHash).Error("Failed to find accepted hold invoice")
|
||||
return NewNotFoundError()
|
||||
}
|
||||
|
||||
if !dbTransaction.SelfPayment {
|
||||
err := lnClient.CancelHoldInvoice(ctx, paymentHash)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
}).WithError(err).Error("Failed to cancel hold invoice via LN client")
|
||||
// Don't mark DB as failed here, cancellation might have already happened or might succeed later.
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := svc.db.Transaction(func(tx *gorm.DB) error {
|
||||
var dbTransaction db.Transaction
|
||||
result := tx.Limit(1).Find(&dbTransaction, &db.Transaction{
|
||||
Type: constants.TRANSACTION_TYPE_INCOMING,
|
||||
State: constants.TRANSACTION_STATE_ACCEPTED,
|
||||
PaymentHash: paymentHash,
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
}).WithError(result.Error).Error("Failed to find accepted hold invoice in DB for cancellation")
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
}).Warn("No accepted hold invoice found in DB to mark as failed due to cancellation")
|
||||
return NewNotFoundError()
|
||||
}
|
||||
|
||||
return svc.markPaymentFailed(tx, &dbTransaction, "Hold invoice was cancelled")
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
}).WithError(err).Error("Failed DB transaction while canceling hold invoice")
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": paymentHash,
|
||||
}).Info("Marked hold invoice as failed in DB due to cancellation")
|
||||
|
||||
svc.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_hold_invoice_canceled",
|
||||
Properties: &dbTransaction,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) markTransactionSettled(tx *gorm.DB, dbTransaction *db.Transaction, preimage string, fee uint64, selfPayment bool) (*db.Transaction, error) {
|
||||
// TODO: it would be better to have a database constraint so we cannot have two pending payments
|
||||
var existingSettledTransaction db.Transaction
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue