@@ -3,10 +3,12 @@ package liquidity_provider
33import (
44 "context"
55 "errors"
6+ "fmt"
67 "sync"
78 "time"
89
910 "github.com/rsksmart/liquidity-provider-server/internal/entities"
11+ "github.com/rsksmart/liquidity-provider-server/internal/entities/alerts"
1012 "github.com/rsksmart/liquidity-provider-server/internal/entities/blockchain"
1113 "github.com/rsksmart/liquidity-provider-server/internal/entities/cold_wallet"
1214 "github.com/rsksmart/liquidity-provider-server/internal/entities/liquidity_provider"
@@ -86,6 +88,8 @@ type TransferExcessToColdWalletUseCase struct {
8688 eventBus entities.EventBus
8789 signer entities.Signer
8890 hashFunc entities.HashFunction
91+ alertSender alerts.AlertSender
92+ alertRecipient string
8993}
9094
9195// NewTransferExcessToColdWalletUseCase creates a new use case for transferring excess liquidity to the cold wallet.
@@ -106,6 +110,8 @@ func NewTransferExcessToColdWalletUseCase(
106110 eventBus entities.EventBus ,
107111 signer entities.Signer ,
108112 hashFunc entities.HashFunction ,
113+ alertSender alerts.AlertSender ,
114+ alertRecipient string ,
109115) * TransferExcessToColdWalletUseCase {
110116 return & TransferExcessToColdWalletUseCase {
111117 peginProvider : peginProvider ,
@@ -124,6 +130,8 @@ func NewTransferExcessToColdWalletUseCase(
124130 eventBus : eventBus ,
125131 signer : signer ,
126132 hashFunc : hashFunc ,
133+ alertSender : alertSender ,
134+ alertRecipient : alertRecipient ,
127135 }
128136}
129137
@@ -155,7 +163,7 @@ func (useCase *TransferExcessToColdWalletUseCase) Run(ctx context.Context) (*Tra
155163 return nil , usecases .WrapUseCaseError (usecases .TransferExcessToColdWalletId , err )
156164 }
157165
158- btcResult := useCase .handleBtcTransfer (excessResult .BtcExcess , excessResult .BtcIsTimeForced )
166+ btcResult := useCase .handleBtcTransfer (ctx , excessResult .BtcExcess , excessResult .BtcIsTimeForced )
159167 if btcResult .Status == TransferStatusFailed {
160168 result := NewTransferToColdWalletResult (btcResult , NetworkTransferResult {})
161169 return result , usecases .WrapUseCaseError (usecases .TransferExcessToColdWalletId , btcResult .Error )
@@ -260,7 +268,7 @@ func (useCase *TransferExcessToColdWalletUseCase) calculateExcessForBothNetworks
260268
261269// publishBtcTransferEvent emits a BTC cold wallet transfer event to the event bus, distinguishing
262270// between threshold-triggered and time-forced transfers.
263- func (useCase * TransferExcessToColdWalletUseCase ) publishBtcTransferEvent (txResult internalTxResult , isTimeForcingTransfer bool ) {
271+ func (useCase * TransferExcessToColdWalletUseCase ) publishBtcTransferEvent (ctx context. Context , txResult internalTxResult , isTimeForcingTransfer bool ) {
264272 if isTimeForcingTransfer {
265273 useCase .eventBus .Publish (cold_wallet.BtcTransferredDueToTimeForcingEvent {
266274 Event : entities .NewBaseEvent (cold_wallet .BtcTransferredDueToTimeForcingEventId ),
@@ -276,11 +284,14 @@ func (useCase *TransferExcessToColdWalletUseCase) publishBtcTransferEvent(txResu
276284 Fee : txResult .Fee ,
277285 })
278286 }
287+ if err := useCase .sendTransferAlert (ctx , "BTC" , txResult .Amount , txResult .TxHash , txResult .Fee , isTimeForcingTransfer ); err != nil {
288+ log .Error ("Error sending hot to cold transfer alert: " , err )
289+ }
279290}
280291
281292// handleBtcTransfer orchestrates a single BTC transfer: skips if no excess, executes the transfer,
282293// checks if the amount is economical, and publishes the corresponding event on success.
283- func (useCase * TransferExcessToColdWalletUseCase ) handleBtcTransfer (excess * entities.Wei , isTimeForcingTransfer bool ) NetworkTransferResult {
294+ func (useCase * TransferExcessToColdWalletUseCase ) handleBtcTransfer (ctx context. Context , excess * entities.Wei , isTimeForcingTransfer bool ) NetworkTransferResult {
284295 if excess .Cmp (entities .NewWei (0 )) <= 0 {
285296 return NetworkTransferResult {
286297 Status : TransferStatusSkippedNoExcess ,
@@ -290,7 +301,7 @@ func (useCase *TransferExcessToColdWalletUseCase) handleBtcTransfer(excess *enti
290301
291302 txResult , err := useCase .executeBtcTransfer (excess )
292303 if err == nil {
293- useCase .publishBtcTransferEvent (txResult , isTimeForcingTransfer )
304+ useCase .publishBtcTransferEvent (ctx , txResult , isTimeForcingTransfer )
294305
295306 return NetworkTransferResult {
296307 Status : TransferStatusSuccess ,
@@ -317,7 +328,7 @@ func (useCase *TransferExcessToColdWalletUseCase) handleBtcTransfer(excess *enti
317328
318329// publishRskTransferEvent emits an RBTC cold wallet transfer event to the event bus, distinguishing
319330// between threshold-triggered and time-forced transfers.
320- func (useCase * TransferExcessToColdWalletUseCase ) publishRskTransferEvent (txResult internalTxResult , isTimeForcingTransfer bool ) {
331+ func (useCase * TransferExcessToColdWalletUseCase ) publishRskTransferEvent (ctx context. Context , txResult internalTxResult , isTimeForcingTransfer bool ) {
321332 if isTimeForcingTransfer {
322333 useCase .eventBus .Publish (cold_wallet.RbtcTransferredDueToTimeForcingEvent {
323334 Event : entities .NewBaseEvent (cold_wallet .RbtcTransferredDueToTimeForcingEventId ),
@@ -333,6 +344,26 @@ func (useCase *TransferExcessToColdWalletUseCase) publishRskTransferEvent(txResu
333344 Fee : txResult .Fee ,
334345 })
335346 }
347+ if err := useCase .sendTransferAlert (ctx , "RBTC" , txResult .Amount , txResult .TxHash , txResult .Fee , isTimeForcingTransfer ); err != nil {
348+ log .Error ("Error sending hot to cold transfer alert: " , err )
349+ }
350+ }
351+
352+ func (useCase * TransferExcessToColdWalletUseCase ) sendTransferAlert (ctx context.Context , asset string , amount * entities.Wei , txHash string , fee * entities.Wei , isTimeForcing bool ) error {
353+ reason := "threshold"
354+ if isTimeForcing {
355+ reason = "time forcing"
356+ }
357+ amountStr := "0"
358+ if amount != nil {
359+ amountStr = amount .ToRbtc ().String ()
360+ }
361+ feeStr := "0"
362+ if fee != nil {
363+ feeStr = fee .ToRbtc ().String ()
364+ }
365+ body := fmt .Sprintf ("| Asset: %s | Amount: %s | TxHash: %s | Fee: %s | Reason: %s" , asset , amountStr , txHash , feeStr , reason )
366+ return useCase .alertSender .SendAlert (ctx , alerts .AlertSubjectHotToColdTransfer , body , []string {useCase .alertRecipient })
336367}
337368
338369// handleRskTransfer orchestrates a single RBTC transfer: skips if no excess, executes the transfer,
@@ -347,7 +378,7 @@ func (useCase *TransferExcessToColdWalletUseCase) handleRskTransfer(ctx context.
347378
348379 txResult , err := useCase .executeRskTransfer (ctx , excess )
349380 if err == nil {
350- useCase .publishRskTransferEvent (txResult , isTimeForcingTransfer )
381+ useCase .publishRskTransferEvent (ctx , txResult , isTimeForcingTransfer )
351382
352383 return NetworkTransferResult {
353384 Status : TransferStatusSuccess ,
0 commit comments