diff --git a/internal/shared/api/routes.go b/internal/shared/api/routes.go index 94f5fc64..ab22d9c0 100644 --- a/internal/shared/api/routes.go +++ b/internal/shared/api/routes.go @@ -37,6 +37,7 @@ func (a *Server) SetupRoutes(r *chi.Mux) { r.Get("/v2/delegations", registerHandler(handlers.V2Handler.GetDelegations)) r.Get("/v2/stats", registerHandler(handlers.V2Handler.GetOverallStats)) r.Get("/v2/staker/stats", registerHandler(handlers.V2Handler.GetStakerStats)) + r.Get("/v2/staker/delegation/check", registerHandler(handlers.V2Handler.CheckStakerDelegationExist)) // Common routes r.Get("/swagger/*", httpSwagger.WrapHandler) diff --git a/internal/v2/api/handlers/stats.go b/internal/v2/api/handlers/stats.go index f99ed403..0be5d195 100644 --- a/internal/v2/api/handlers/stats.go +++ b/internal/v2/api/handlers/stats.go @@ -5,6 +5,7 @@ import ( "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" ) // GetStakerStats gets staker stats for babylon staking @@ -44,3 +45,70 @@ func (h *V2Handler) GetOverallStats(request *http.Request) (*handler.Result, *ty } return handler.NewResult(stats), nil } + +// CheckStakerDelegationExist @Summary Check if a staker has an active delegation +// @Description Check if a staker has an active delegation by the staker BTC address (Taproot or Native Segwit) +// @Description Optionally, you can provide a timeframe to check if the delegation is active within the provided timeframe +// @Description The available timeframe is "today" which checks after UTC 12AM of the current day +// @Produce json +// @Tags v1 +// @Param address query string true "Staker BTC address in Taproot/Native Segwit format" +// @Param timeframe query string false "Check if the delegation is active within the provided timeframe" Enums(today) +// @Success 200 {object} DelegationCheckPublicResponse "Delegation check result" +// @Failure 400 {object} types.Error "Error: Bad Request" +// @Router /v1/staker/delegation/check [get] +func (h *V2Handler) CheckStakerDelegationExist(request *http.Request) (*handler.Result, *types.Error) { + address, err := handler.ParseBtcAddressQuery(request, "address", h.Handler.Config.Server.BTCNetParam) + if err != nil { + return nil, err + } + + afterTimestamp, err := parseTimeframeToAfterTimestamp(request.URL.Query().Get("timeframe")) + if err != nil { + return nil, err + } + + addressToPkMapping, err := h.Service.GetStakerPublicKeysByAddresses(request.Context(), []string{address}) + if err != nil { + return nil, err + } + if _, exist := addressToPkMapping[address]; !exist { + return buildDelegationCheckResponse(false), nil + } + + exist, err := h.Service.CheckStakerHasActiveDelegationByPk( + request.Context(), addressToPkMapping[address], afterTimestamp, + ) + if err != nil { + return nil, err + } + + return buildDelegationCheckResponse(exist), nil +} + +func parseTimeframeToAfterTimestamp(timeframe string) (int64, *types.Error) { + switch timeframe { + case "": // We ignore and return 0 if no timeframe is provided + return 0, nil + case "today": + return utils.GetTodayStartTimestampInSeconds(), nil + default: + return 0, types.NewErrorWithMsg( + http.StatusBadRequest, types.BadRequest, "invalid timeframe value", + ) + } +} + +type DelegationCheckPublicResponse struct { + Data bool `json:"data"` + Code int `json:"code"` +} + +func buildDelegationCheckResponse(exist bool) *handler.Result { + return &handler.Result{ + Data: &DelegationCheckPublicResponse{ + Data: exist, Code: 0, + }, + Status: http.StatusOK, + } +} diff --git a/internal/v2/service/interface.go b/internal/v2/service/interface.go index 5ecdee65..1491f71a 100644 --- a/internal/v2/service/interface.go +++ b/internal/v2/service/interface.go @@ -22,4 +22,5 @@ type V2ServiceProvider interface { ProcessUnbondingDelegationStats(ctx context.Context, stakingTxHashHex, stakerPkHex string, fpBtcPkHexes []string, amount uint64, stateHistory []string) *types.Error ProcessWithdrawableDelegationStats(ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, stateHistory []string) *types.Error ProcessWithdrawnDelegationStats(ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, stateHistory []string) *types.Error + GetStakerPublicKeysByAddresses(ctx context.Context, addresses []string) (map[string]string, *types.Error) }