Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ nostr:
third_party:
coinmarketcap_api_key: "your-cmc-api-key-here" # optional — get free key at coinmarketcap.com/api

# DCA bot integration
dca:
wallet_username: "" # when sats are received by this Telegram username, reset DCA retry counts
Comment thread
helloscoopa marked this conversation as resolved.
api_url: "https://bitcoindeepa-dca-be-production.up.railway.app"

# API Configuration
api:
# Analytics API - HMAC authenticated endpoints for data export
Expand Down
6 changes: 6 additions & 0 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ var Configuration = struct {
Nostr NostrConfiguration `yaml:"nostr"`
API APIConfiguration `yaml:"api"`
ThirdParty ThirdPartyConfiguration `yaml:"third_party"`
DCA DCAConfiguration `yaml:"dca"`
}{}

type DCAConfiguration struct {
WalletUsername string `yaml:"wallet_username"`
ApiUrl string `yaml:"api_url"`
}

type NostrConfiguration struct {
PrivateKey string `yaml:"private_key"`
}
Expand Down
42 changes: 42 additions & 0 deletions internal/dca/dca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dca

import (
"net/http"
"strings"
"time"

"github.com/LightningTipBot/LightningTipBot/internal"
log "github.com/sirupsen/logrus"
)

// NotifyDeposit checks whether the given recipient username matches the configured
// DCA wallet username and, if so, asks the DCA backend to reset its retry counts.
func NotifyDeposit(toUsername string) {
wallet := strings.TrimPrefix(internal.Configuration.DCA.WalletUsername, "@")
if wallet == "" || strings.TrimPrefix(toUsername, "@") != wallet {
return
}

apiUrl := strings.TrimSuffix(internal.Configuration.DCA.ApiUrl, "/")
if apiUrl == "" {
log.Errorln("[DCA] dca.api_url is not configured, skipping reset-retry-counts call")
return
}

url := apiUrl + "/transaction/reset-retry-counts"
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
log.Errorf("[DCA] Error creating reset-retry-counts request: %s", err.Error())
return
}
req.Header.Set("Content-Type", "application/json")
Comment on lines +27 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove Content-Type header when sending no body.

The request sets Content-Type: application/json (line 24) but the request body is nil (line 19). This is inconsistent. If the DCA API endpoint does not require a request body, the Content-Type header should be omitted.

🧹 Proposed fix
 	req, err := http.NewRequest(http.MethodPost, url, nil)
 	if err != nil {
 		log.Errorf("[DCA] Error creating reset-retry-counts request: %s", err.Error())
 		return
 	}
-	req.Header.Set("Content-Type", "application/json")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
log.Errorf("[DCA] Error creating reset-retry-counts request: %s", err.Error())
return
}
req.Header.Set("Content-Type", "application/json")
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
log.Errorf("[DCA] Error creating reset-retry-counts request: %s", err.Error())
return
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/dca/dca.go` around lines 19 - 24, The request is setting
req.Header.Set("Content-Type", "application/json") while creating the POST with
a nil body; remove the incorrect header or set it only when a non-nil body is
provided. Update the code around the http.NewRequest(...) call in
internal/dca/dca.go so that the Content-Type header is not added when body is
nil (e.g., only call req.Header.Set("Content-Type", "application/json") when the
body variable is non-nil), keeping the rest of the error handling and logging
for req creation intact.


client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
log.Errorf("[DCA] Error calling reset-retry-counts: %s", err.Error())
return
}
defer resp.Body.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check error return from resp.Body.Close().

The deferred resp.Body.Close() call does not check for errors. While rare, Close() can fail and may indicate issues with connection cleanup or incomplete response reads.

🔧 Proposed fix
-	defer resp.Body.Close()
+	defer func() {
+		if err := resp.Body.Close(); err != nil {
+			log.Errorf("[DCA] Error closing response body: %s", err.Error())
+		}
+	}()

Based on learnings from static analysis tool errcheck.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
log.Errorf("[DCA] Error closing response body: %s", err.Error())
}
}()
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 32-32: Error return value of resp.Body.Close is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/dca/dca.go` at line 32, The deferred call defer resp.Body.Close()
ignores the error return; update the function in internal/dca/dca.go to capture
and handle the error from resp.Body.Close() (e.g., assign err :=
resp.Body.Close() or check the returned error inside the deferred closure) so
any Close() failure is logged or returned; locate the resp variable and replace
the bare defer resp.Body.Close() with a deferred closure that checks the error
and uses the function's logger or error return to report it.

Source: Linters/SAST tools

log.Infof("[DCA] reset-retry-counts called for %s, status: %s", toUsername, resp.Status)
}
3 changes: 3 additions & 0 deletions internal/lnbits/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/LightningTipBot/LightningTipBot/internal"
"github.com/LightningTipBot/LightningTipBot/internal/dca"
"github.com/LightningTipBot/LightningTipBot/internal/lnbits"
"github.com/LightningTipBot/LightningTipBot/internal/telegram"
"github.com/LightningTipBot/LightningTipBot/internal/utils"
Expand Down Expand Up @@ -100,6 +101,8 @@ func (w *Server) receive(writer http.ResponseWriter, request *http.Request) {
}
log.Infoln(fmt.Sprintf("[⚡️ WebHook] User %s (%d) received invoice of %d sat.", telegram.GetUserStr(user.Telegram), user.Telegram.ID, webhookEvent.Amount/1000))

go dca.NotifyDeposit(telegram.GetUserStr(user.Telegram))

writer.WriteHeader(200)

// trigger invoice events
Expand Down
2 changes: 2 additions & 0 deletions internal/telegram/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

log "github.com/sirupsen/logrus"

"github.com/LightningTipBot/LightningTipBot/internal/dca"
"github.com/LightningTipBot/LightningTipBot/internal/lnbits"
tb "gopkg.in/lightningtipbot/telebot.v3"
)
Expand Down Expand Up @@ -73,6 +74,7 @@ func (t *Transaction) Send() (success bool, err error) {
success, err = t.SendTransaction(t.Bot, t.From, t.To, t.Amount, t.Memo)
if success {
t.Success = success
go dca.NotifyDeposit(t.ToUser)
}

// save transaction to db
Expand Down
Loading