diff --git a/lnclient/cashu/cashu.go b/lnclient/cashu/cashu.go index 814ca0256..40fc38368 100644 --- a/lnclient/cashu/cashu.go +++ b/lnclient/cashu/cashu.go @@ -119,7 +119,8 @@ 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) { +func (cs *CashuService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (transaction *lnclient.Transaction, err error) { + _ = minCltvExpiryDelta return nil, errors.New("not implemented") } diff --git a/lnclient/ldk/ldk.go b/lnclient/ldk/ldk.go index 7efbcdf81..3593e7927 100644 --- a/lnclient/ldk/ldk.go +++ b/lnclient/ldk/ldk.go @@ -2282,7 +2282,8 @@ func (ls *LDKService) ExecuteCustomNodeCommand(ctx context.Context, command *lnc return nil, lnclient.ErrUnknownCustomNodeCommand } -func (ls *LDKService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (*lnclient.Transaction, error) { +func (ls *LDKService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (*lnclient.Transaction, error) { + // TODO: Support minCltvExpiryDelta if time.Duration(expiry)*time.Second > maxInvoiceExpiry { return nil, errors.New("expiry is too long") } diff --git a/lnclient/lnd/lnd.go b/lnclient/lnd/lnd.go index 3f26d5acb..1e4fcead4 100644 --- a/lnclient/lnd/lnd.go +++ b/lnclient/lnd/lnd.go @@ -727,7 +727,7 @@ 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) { +func (svc *LNDService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (transaction *lnclient.Transaction, err error) { var descriptionHashBytes []byte var paymentHashBytes []byte @@ -779,6 +779,9 @@ func (svc *LNDService) MakeHoldInvoice(ctx context.Context, amount int64, descri Private: !hasPublicChannels, Hash: paymentHashBytes, } + if minCltvExpiryDelta != nil { + addInvoiceRequest.CltvExpiry = *minCltvExpiryDelta + } _, err = svc.client.AddHoldInvoice(ctx, addInvoiceRequest) if err != nil { diff --git a/lnclient/models.go b/lnclient/models.go index f8f07d638..56092b954 100644 --- a/lnclient/models.go +++ b/lnclient/models.go @@ -62,7 +62,7 @@ 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, throughNodePubkey *string) (transaction *Transaction, err error) - MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (transaction *Transaction, err error) + MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (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) diff --git a/lnclient/phoenixd/phoenixd.go b/lnclient/phoenixd/phoenixd.go index 812cbf323..0b8c211a9 100644 --- a/lnclient/phoenixd/phoenixd.go +++ b/lnclient/phoenixd/phoenixd.go @@ -213,7 +213,7 @@ 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) { +func (svc *PhoenixService) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (transaction *lnclient.Transaction, err error) { return nil, errors.New("not implemented") } diff --git a/nip47/controllers/make_hold_invoice_controller.go b/nip47/controllers/make_hold_invoice_controller.go index 6c5fb4556..01493e6b9 100644 --- a/nip47/controllers/make_hold_invoice_controller.go +++ b/nip47/controllers/make_hold_invoice_controller.go @@ -11,12 +11,13 @@ import ( ) 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"` + Amount uint64 `json:"amount"` + PaymentHash string `json:"payment_hash"` + Description string `json:"description"` + DescriptionHash string `json:"description_hash"` + Expiry uint64 `json:"expiry"` + MinCltvExpiryDelta *uint64 `json:"min_cltv_expiry_delta"` + Metadata map[string]interface{} `json:"metadata,omitempty"` } type makeHoldInvoiceResponse struct { models.Transaction @@ -46,14 +47,15 @@ func (controller *nip47Controller) HandleMakeHoldInvoiceEvent(ctx context.Contex } 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, + "requestEventId": requestEventId, + "appId": appId, + "amount": makeHoldInvoiceParams.Amount, + "description": makeHoldInvoiceParams.Description, + "descriptionHash": makeHoldInvoiceParams.DescriptionHash, + "expiry": makeHoldInvoiceParams.Expiry, + "minCltvExpiryDelta": makeHoldInvoiceParams.MinCltvExpiryDelta, + "paymentHash": makeHoldInvoiceParams.PaymentHash, + "metadata": makeHoldInvoiceParams.Metadata, }).Info("Making hold invoice") requestEventIdUint := uint(requestEventId) @@ -64,6 +66,7 @@ func (controller *nip47Controller) HandleMakeHoldInvoiceEvent(ctx context.Contex makeHoldInvoiceParams.DescriptionHash, makeHoldInvoiceParams.Expiry, makeHoldInvoiceParams.PaymentHash, + makeHoldInvoiceParams.MinCltvExpiryDelta, makeHoldInvoiceParams.Metadata, controller.lnClient, &appId, @@ -72,13 +75,14 @@ func (controller *nip47Controller) HandleMakeHoldInvoiceEvent(ctx context.Contex 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, + "request_event_id": requestEventId, + "appId": appId, + "amount": makeHoldInvoiceParams.Amount, + "description": makeHoldInvoiceParams.Description, + "descriptionHash": makeHoldInvoiceParams.DescriptionHash, + "expiry": makeHoldInvoiceParams.Expiry, + "minCltvExpiryDelta": makeHoldInvoiceParams.MinCltvExpiryDelta, + "paymentHash": makeHoldInvoiceParams.PaymentHash, }).WithError(err).Error("Failed to make invoice") publishResponse(&models.Response{ diff --git a/nip47/controllers/make_hold_invoice_controller_test.go b/nip47/controllers/make_hold_invoice_controller_test.go index 1a1aea838..bac492d39 100644 --- a/nip47/controllers/make_hold_invoice_controller_test.go +++ b/nip47/controllers/make_hold_invoice_controller_test.go @@ -23,6 +23,7 @@ const nip47MakeHoldInvoiceJson = ` "description": "Hello, world", "payment_hash": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "expiry": 3600, + "min_cltv_expiry_delta": 144, "metadata": { "a": 1, "b": "2", @@ -67,6 +68,11 @@ func TestHandleMakeHoldInvoiceEvent(t *testing.T) { NewTestNip47Controller(svc). HandleMakeHoldInvoiceEvent(ctx, nip47Request, dbRequestEvent.ID, *dbRequestEvent.AppId, publishResponse) + mockLn, ok := svc.LNClient.(*tests.MockLn) + require.True(t, ok) + require.NotNil(t, mockLn.LastMinCltvExpiryDelta) + assert.EqualValues(t, 144, *mockLn.LastMinCltvExpiryDelta) + expectedMetadata := map[string]interface{}{ "a": float64(1), "b": "2", diff --git a/tests/mock_ln_client.go b/tests/mock_ln_client.go index 1a4d8a388..a2155b16d 100644 --- a/tests/mock_ln_client.go +++ b/tests/mock_ln_client.go @@ -84,6 +84,7 @@ type MockLn struct { PaymentDelay *time.Duration Pubkey string MockTransaction *lnclient.Transaction + LastMinCltvExpiryDelta *uint64 SupportedNotificationTypes *[]string } @@ -129,7 +130,14 @@ 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) { +func (mln *MockLn) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (transaction *lnclient.Transaction, err error) { + if minCltvExpiryDelta == nil { + mln.LastMinCltvExpiryDelta = nil + } else { + value := *minCltvExpiryDelta + mln.LastMinCltvExpiryDelta = &value + } + if len(mln.MakeInvoiceResponses) > 0 { response := mln.MakeInvoiceResponses[0] err := mln.MakeInvoiceErrors[0] diff --git a/tests/mocks/LNClient.go b/tests/mocks/LNClient.go index 75e5028b5..4fb9be952 100644 --- a/tests/mocks/LNClient.go +++ b/tests/mocks/LNClient.go @@ -1347,8 +1347,8 @@ func (_c *MockLNClient_LookupInvoice_Call) RunAndReturn(run func(ctx context.Con } // MakeHoldInvoice provides a mock function for the type MockLNClient -func (_mock *MockLNClient) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (*lnclient.Transaction, error) { - ret := _mock.Called(ctx, amount, description, descriptionHash, expiry, paymentHash) +func (_mock *MockLNClient) MakeHoldInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (*lnclient.Transaction, error) { + ret := _mock.Called(ctx, amount, description, descriptionHash, expiry, paymentHash, minCltvExpiryDelta) if len(ret) == 0 { panic("no return value specified for MakeHoldInvoice") @@ -1356,18 +1356,18 @@ func (_mock *MockLNClient) MakeHoldInvoice(ctx context.Context, amount int64, de var r0 *lnclient.Transaction var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int64, string, string, int64, string) (*lnclient.Transaction, error)); ok { - return returnFunc(ctx, amount, description, descriptionHash, expiry, paymentHash) + if returnFunc, ok := ret.Get(0).(func(context.Context, int64, string, string, int64, string, *uint64) (*lnclient.Transaction, error)); ok { + return returnFunc(ctx, amount, description, descriptionHash, expiry, paymentHash, minCltvExpiryDelta) } - if returnFunc, ok := ret.Get(0).(func(context.Context, int64, string, string, int64, string) *lnclient.Transaction); ok { - r0 = returnFunc(ctx, amount, description, descriptionHash, expiry, paymentHash) + if returnFunc, ok := ret.Get(0).(func(context.Context, int64, string, string, int64, string, *uint64) *lnclient.Transaction); ok { + r0 = returnFunc(ctx, amount, description, descriptionHash, expiry, paymentHash, minCltvExpiryDelta) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*lnclient.Transaction) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, int64, string, string, int64, string) error); ok { - r1 = returnFunc(ctx, amount, description, descriptionHash, expiry, paymentHash) + if returnFunc, ok := ret.Get(1).(func(context.Context, int64, string, string, int64, string, *uint64) error); ok { + r1 = returnFunc(ctx, amount, description, descriptionHash, expiry, paymentHash, minCltvExpiryDelta) } else { r1 = ret.Error(1) } @@ -1386,11 +1386,12 @@ type MockLNClient_MakeHoldInvoice_Call struct { // - descriptionHash string // - expiry int64 // - paymentHash string -func (_e *MockLNClient_Expecter) MakeHoldInvoice(ctx interface{}, amount interface{}, description interface{}, descriptionHash interface{}, expiry interface{}, paymentHash interface{}) *MockLNClient_MakeHoldInvoice_Call { - return &MockLNClient_MakeHoldInvoice_Call{Call: _e.mock.On("MakeHoldInvoice", ctx, amount, description, descriptionHash, expiry, paymentHash)} +// - minCltvExpiryDelta *uint64 +func (_e *MockLNClient_Expecter) MakeHoldInvoice(ctx interface{}, amount interface{}, description interface{}, descriptionHash interface{}, expiry interface{}, paymentHash interface{}, minCltvExpiryDelta interface{}) *MockLNClient_MakeHoldInvoice_Call { + return &MockLNClient_MakeHoldInvoice_Call{Call: _e.mock.On("MakeHoldInvoice", ctx, amount, description, descriptionHash, expiry, paymentHash, minCltvExpiryDelta)} } -func (_c *MockLNClient_MakeHoldInvoice_Call) Run(run func(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string)) *MockLNClient_MakeHoldInvoice_Call { +func (_c *MockLNClient_MakeHoldInvoice_Call) Run(run func(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64)) *MockLNClient_MakeHoldInvoice_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -1416,6 +1417,10 @@ func (_c *MockLNClient_MakeHoldInvoice_Call) Run(run func(ctx context.Context, a if args[5] != nil { arg5 = args[5].(string) } + var arg6 *uint64 + if args[6] != nil { + arg6 = args[6].(*uint64) + } run( arg0, arg1, @@ -1423,6 +1428,7 @@ func (_c *MockLNClient_MakeHoldInvoice_Call) Run(run func(ctx context.Context, a arg3, arg4, arg5, + arg6, ) }) return _c @@ -1433,7 +1439,7 @@ func (_c *MockLNClient_MakeHoldInvoice_Call) Return(transaction *lnclient.Transa return _c } -func (_c *MockLNClient_MakeHoldInvoice_Call) RunAndReturn(run func(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string) (*lnclient.Transaction, error)) *MockLNClient_MakeHoldInvoice_Call { +func (_c *MockLNClient_MakeHoldInvoice_Call) RunAndReturn(run func(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (*lnclient.Transaction, error)) *MockLNClient_MakeHoldInvoice_Call { _c.Call.Return(run) return _c } diff --git a/transactions/self_hold_payments_test.go b/transactions/self_hold_payments_test.go index 4936fb3a0..394b91449 100644 --- a/transactions/self_hold_payments_test.go +++ b/transactions/self_hold_payments_test.go @@ -27,7 +27,7 @@ func TestSelfHoldPaymentSettled(t *testing.T) { 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) + transaction, err := transactionsService.MakeHoldInvoice(ctx, 1000, "Hold payment test", "", 0, paymentHash, nil, nil, svc.LNClient, nil, nil) require.NoError(t, err) assert.True(t, transaction.Hold) // use the pubkey from the decoded tests.MockLNClientHoldTransaction invoice @@ -65,7 +65,7 @@ func TestSelfHoldPaymentCanceled(t *testing.T) { 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) + transaction, err := transactionsService.MakeHoldInvoice(ctx, 1000, "Hold payment test", "", 0, paymentHash, nil, nil, svc.LNClient, nil, nil) require.NoError(t, err) assert.True(t, transaction.Hold) // use the pubkey from the decoded tests.MockLNClientHoldTransaction invoice @@ -207,7 +207,7 @@ func TestWrappedInvoice(t *testing.T) { // Bob creates a wrapped invoice with the same payment hash but higher amount (1100 sats) // Bob acts as an intermediary, adding a fee of 100 sats - bobWrappedInvoice, err := transactionsService.MakeHoldInvoice(ctx, 1100, "Bob wrapped invoice", "", 0, charlieInvoice.PaymentHash, nil, svc.LNClient, &bobApp.ID, nil) + bobWrappedInvoice, err := transactionsService.MakeHoldInvoice(ctx, 1100, "Bob wrapped invoice", "", 0, charlieInvoice.PaymentHash, nil, nil, svc.LNClient, &bobApp.ID, nil) require.NoError(t, err) require.True(t, bobWrappedInvoice.Hold) require.Equal(t, mockBobHoldInvoice.Invoice, bobWrappedInvoice.PaymentRequest) diff --git a/transactions/transactions_service.go b/transactions/transactions_service.go index ae14fdaea..f7520119f 100644 --- a/transactions/transactions_service.go +++ b/transactions/transactions_service.go @@ -41,7 +41,7 @@ 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(payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) SendKeysend(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) + MakeHoldInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, paymentHash string, minCltvExpiryDelta *uint64, 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 SetTransactionMetadata(ctx context.Context, id uint, metadata map[string]interface{}) error @@ -213,7 +213,7 @@ 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) { +func (svc *transactionsService) MakeHoldInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, paymentHash string, minCltvExpiryDelta *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) { var err error var metadataBytes []byte if metadata != nil { @@ -227,7 +227,7 @@ func (svc *transactionsService) MakeHoldInvoice(ctx context.Context, amount uint } } - lnClientTransaction, err := lnClient.MakeHoldInvoice(ctx, int64(amount), description, descriptionHash, int64(expiry), paymentHash) + lnClientTransaction, err := lnClient.MakeHoldInvoice(ctx, int64(amount), description, descriptionHash, int64(expiry), paymentHash, minCltvExpiryDelta) if err != nil { logger.Logger.WithError(err).Error("Failed to create hold invoice via LN client") return nil, err