diff --git a/README.md b/README.md index facf762..729cb96 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,6 @@ Live deployment at [ln.getalby.com](https://ln.getalby.com). ### Status: alpha -## Known Issues - -* Fee reserves are not checked prior to making the payment. This can cause a user's balance to go below 0. - ## Configuration All required configuration is done with environment variables and a `.env` file can be used. @@ -50,6 +46,7 @@ vim .env # edit your config + `ENABLE_PROMETHEUS`: (default: false) Enable Prometheus metrics to be exposed + `PROMETHEUS_PORT`: (default: 9092) Prometheus port (path: `/metrics`) + `WEBHOOK_URL`: Optional. Callback URL for incoming and outgoing payment events, see below. ++ `FEE_RESERVE`: (default: false) Keep fee reserve for each user ## Developing diff --git a/controllers/keysend.ctrl.go b/controllers/keysend.ctrl.go index f0d80fa..c136078 100644 --- a/controllers/keysend.ctrl.go +++ b/controllers/keysend.ctrl.go @@ -85,7 +85,11 @@ func (controller *KeySendController) KeySend(c echo.Context) error { return err } - if currentBalance < invoice.Amount { + minimumBalance := invoice.Amount + if controller.svc.Config.FeeReserve { + minimumBalance += invoice.CalcFeeLimit() + } + if currentBalance < minimumBalance { c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount) return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) } diff --git a/controllers/payinvoice.ctrl.go b/controllers/payinvoice.ctrl.go index beacdff..614b1ca 100644 --- a/controllers/payinvoice.ctrl.go +++ b/controllers/payinvoice.ctrl.go @@ -95,9 +95,12 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { return err } - if currentBalance < invoice.Amount { + minimumBalance := invoice.Amount + if controller.svc.Config.FeeReserve { + minimumBalance += invoice.CalcFeeLimit() + } + if currentBalance < minimumBalance { c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount) - return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) } diff --git a/db/models/invoice.go b/db/models/invoice.go index ba03e5f..98d2d0b 100644 --- a/db/models/invoice.go +++ b/db/models/invoice.go @@ -2,6 +2,7 @@ package models import ( "context" + "math" "time" "github.com/uptrace/bun" @@ -41,4 +42,12 @@ func (i *Invoice) BeforeAppendModel(ctx context.Context, query bun.Query) error return nil } +func (i *Invoice) CalcFeeLimit() int64 { + limit := int64(10) + if i.Amount > 1000 { + limit = int64(math.Ceil(float64(i.Amount)*float64(0.01)) + 1) + } + return limit +} + var _ bun.BeforeAppendModelHook = (*Invoice)(nil) diff --git a/lib/service/config.go b/lib/service/config.go index 2d0e04b..17f2994 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -19,4 +19,5 @@ type Config struct { EnablePrometheus bool `envconfig:"ENABLE_PROMETHEUS" default:"false"` PrometheusPort int `envconfig:"PROMETHEUS_PORT" default:"9092"` WebhookUrl string `envconfig:"WEBHOOK_URL"` + FeeReserve bool `envconfig:"FEE_RESERVE" default:"false"` } diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 39179e6..601fd23 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "errors" "fmt" - "math" "strconv" "time" @@ -143,7 +142,11 @@ func (svc *LndhubService) SendPaymentSync(ctx context.Context, invoice *models.I } func createLnRpcSendRequest(invoice *models.Invoice) (*lnrpc.SendRequest, error) { - feeLimit := calcFeeLimit(invoice) + feeLimit := lnrpc.FeeLimit{ + Limit: &lnrpc.FeeLimit_Fixed{ + Fixed: invoice.CalcFeeLimit(), + }, + } if !invoice.Keysend { return &lnrpc.SendRequest{ @@ -176,31 +179,18 @@ func createLnRpcSendRequest(invoice *models.Invoice) (*lnrpc.SendRequest, error) }, nil } -func calcFeeLimit(invoice *models.Invoice) lnrpc.FeeLimit { - limit := int64(10) - if invoice.Amount > 1000 { - limit = int64(math.Ceil(float64(invoice.Amount)*float64(0.01)) + 1) - } - - return lnrpc.FeeLimit{ - Limit: &lnrpc.FeeLimit_Fixed{ - Fixed: limit, - }, - } -} - func (svc *LndhubService) PayInvoice(ctx context.Context, invoice *models.Invoice) (*SendPaymentResponse, error) { userId := invoice.UserID // Get the user's current and outgoing account for the transaction entry debitAccount, err := svc.AccountFor(ctx, common.AccountTypeCurrent, userId) if err != nil { - svc.Logger.Errorf("Could not find current account user_id:%v", invoice.UserID) + svc.Logger.Errorf("Could not find current account user_id:%v", userId) return nil, err } creditAccount, err := svc.AccountFor(ctx, common.AccountTypeOutgoing, userId) if err != nil { - svc.Logger.Errorf("Could not find outgoing account user_id:%v", invoice.UserID) + svc.Logger.Errorf("Could not find outgoing account user_id:%v", userId) return nil, err } @@ -216,7 +206,7 @@ func (svc *LndhubService) PayInvoice(ctx context.Context, invoice *models.Invoic // If the user does not have enough balance this call fails _, err = svc.DB.NewInsert().Model(&entry).Exec(ctx) if err != nil { - svc.Logger.Errorf("Could not insert transaction entry user_id:%v invoice_id:%v", invoice.UserID, invoice.ID) + svc.Logger.Errorf("Could not insert transaction entry user_id:%v invoice_id:%v", userId, invoice.ID) return nil, err } diff --git a/lib/service/invoices_test.go b/lib/service/invoices_test.go index 3a9ba77..999c3ce 100644 --- a/lib/service/invoices_test.go +++ b/lib/service/invoices_test.go @@ -12,9 +12,9 @@ func TestCalcFeeWithInvoiceLessThan1000(t *testing.T) { Amount: 500, } - feeLimit := calcFeeLimit(invoice) + feeLimit := invoice.CalcFeeLimit() expectedFee := int64(10) - assert.Equal(t, expectedFee, feeLimit.GetFixed()) + assert.Equal(t, expectedFee, feeLimit) } func TestCalcFeeWithInvoiceEqualTo1000(t *testing.T) { @@ -22,9 +22,9 @@ func TestCalcFeeWithInvoiceEqualTo1000(t *testing.T) { Amount: 500, } - feeLimit := calcFeeLimit(invoice) + feeLimit := invoice.CalcFeeLimit() expectedFee := int64(10) - assert.Equal(t, expectedFee, feeLimit.GetFixed()) + assert.Equal(t, expectedFee, feeLimit) } func TestCalcFeeWithInvoiceMoreThan1000(t *testing.T) { @@ -32,8 +32,8 @@ func TestCalcFeeWithInvoiceMoreThan1000(t *testing.T) { Amount: 1500, } - feeLimit := calcFeeLimit(invoice) + feeLimit := invoice.CalcFeeLimit() // 1500 * 0.01 + 1 expectedFee := int64(16) - assert.Equal(t, expectedFee, feeLimit.GetFixed()) + assert.Equal(t, expectedFee, feeLimit) }