Skip to content

Commit 723cbfc

Browse files
Merge pull request #921 from rsksmart/feat_hot_to_cold_tranfer_alert
Feat/Fly2227/ Add alert for hot to cold wallet transfer
2 parents 23edd77 + 3844c89 commit 723cbfc

File tree

4 files changed

+182
-6
lines changed

4 files changed

+182
-6
lines changed

internal/configuration/registry/usecase.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ func NewUseCaseRegistry(
389389
messaging.EventBus,
390390
rskRegistry.Wallet,
391391
signingHashFunction,
392+
messaging.AlertSender,
393+
env.Provider.AlertRecipientEmail,
392394
),
393395
checkColdWalletAddressChangeUseCase: liquidity_provider.NewCheckColdWalletAddressChangeUseCase(
394396
databaseRegistry.LiquidityProviderRepository,

internal/entities/alerts/alerts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const (
1111
AlertSubjectPegoutOutOfLiquidity = "PegOut: Out of liquidity"
1212
AlertSubjectEclipseAttack = "Node Eclipse Detected"
1313
AlertSubjectColdWalletChange = "Cold wallet address changed"
14+
AlertSubjectHotToColdTransfer = "Hot to cold wallet transfer executed"
1415
)
1516

1617
type AlertSender interface {

internal/usecases/liquidity_provider/transfer_excess_to_cold_wallet.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package liquidity_provider
33
import (
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

Comments
 (0)