diff --git a/config/config-local.yml b/config/config-local.yml
index 8e95bd5..4aab66e 100644
--- a/config/config-local.yml
+++ b/config/config-local.yml
@@ -1,7 +1,7 @@
 db:
   username: root
   password: example
-  address: "mongodb://localhost:27017"
+  address: "mongodb://localhost:27019/?replicaSet=RS&directConnection=true"
   db-name: babylon-staking-indexer
 btc:
   endpoint: localhost:18332
diff --git a/contrib/images/babylon-staking-indexer/Dockerfile b/contrib/images/babylon-staking-indexer/Dockerfile
index 19a3309..991416d 100644
--- a/contrib/images/babylon-staking-indexer/Dockerfile
+++ b/contrib/images/babylon-staking-indexer/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.21-alpine AS builder
+FROM golang:1.22.3-alpine AS builder
 
 ARG VERSION="HEAD"
 
diff --git a/docker-compose.yml b/docker-compose.yml
index b300dfd..a41174e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,21 +1,21 @@
 version: "3.9"
 services:
   babylon-staking-indexer:
-    image: babylonlabs/babylon-staking-indexer:latest
+    image: babylonlabs-io/babylon-staking-indexer:latest
     container_name: babylon-staking-indexer
     environment:
       - CONFIG=/home/babylon-staking-indexer/config.yml
     depends_on:
-      - mongodb
+      - indexer-mongodb
       - rabbitmq
     volumes:
       - ./config/config-docker.yml:/home/babylon-staking-indexer/config.yml:Z
-  mongodb:
+  indexer-mongodb:
     image: mongo:latest
     container_name: indexer-mongodb
     hostname: indexer-mongodb
     ports:
-      - "27017:27017"
+      - "27019:27017"
     environment:
       MONGO_INITDB_ROOT_USERNAME: root
       MONGO_INITDB_ROOT_PASSWORD: example
diff --git a/go.mod b/go.mod
index 5ac1aad..680d157 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,14 @@
 module github.com/babylonlabs-io/babylon-staking-indexer
 
-go 1.23.2
+go 1.22.3
 
 require (
 	github.com/babylonlabs-io/babylon v0.12.1
 	github.com/babylonlabs-io/staking-queue-client v0.4.1
 	github.com/btcsuite/btcd v0.24.2
 	github.com/cometbft/cometbft v0.38.7
+	github.com/cosmos/cosmos-sdk v0.50.6
+	github.com/cosmos/gogoproto v1.7.0
 	github.com/go-chi/chi/v5 v5.1.0
 	github.com/spf13/viper v1.19.0
 )
@@ -62,10 +64,8 @@ require (
 	github.com/cosmos/btcutil v1.0.5 // indirect
 	github.com/cosmos/cosmos-db v1.0.2 // indirect
 	github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
-	github.com/cosmos/cosmos-sdk v0.50.6 // indirect
 	github.com/cosmos/go-bip39 v1.0.0 // indirect
 	github.com/cosmos/gogogateway v1.2.0 // indirect
-	github.com/cosmos/gogoproto v1.7.0 // indirect
 	github.com/cosmos/iavl v1.1.2 // indirect
 	github.com/cosmos/ibc-go/modules/capability v1.0.0 // indirect
 	github.com/cosmos/ibc-go/v8 v8.3.0 // indirect
diff --git a/internal/db/delegation.go b/internal/db/delegation.go
new file mode 100644
index 0000000..d5e5fbe
--- /dev/null
+++ b/internal/db/delegation.go
@@ -0,0 +1,79 @@
+package db
+
+import (
+	"context"
+	"errors"
+
+	"github.com/babylonlabs-io/babylon-staking-indexer/internal/db/model"
+	"github.com/babylonlabs-io/babylon-staking-indexer/internal/types"
+	"go.mongodb.org/mongo-driver/mongo"
+)
+
+func (db *Database) SaveNewBTCDelegation(
+	ctx context.Context, delegationDoc *model.BTCDelegationDetails,
+) error {
+	_, err := db.client.Database(db.dbName).
+		Collection(model.BTCDelegationDetailsCollection).
+		InsertOne(ctx, delegationDoc)
+	if err != nil {
+		var writeErr mongo.WriteException
+		if errors.As(err, &writeErr) {
+			for _, e := range writeErr.WriteErrors {
+				if mongo.IsDuplicateKeyError(e) {
+					return &DuplicateKeyError{
+						Key:     delegationDoc.StakingTxHashHex,
+						Message: "delegation already exists",
+					}
+				}
+			}
+		}
+		return err
+	}
+	return nil
+}
+
+func (db *Database) UpdateBTCDelegationState(
+	ctx context.Context, stakingTxHash string, newState types.DelegationState,
+) error {
+	filter := map[string]interface{}{"_id": stakingTxHash}
+	update := map[string]interface{}{"$set": map[string]string{"state": newState.String()}}
+
+	res := db.client.Database(db.dbName).
+		Collection(model.BTCDelegationDetailsCollection).
+		FindOneAndUpdate(ctx, filter, update)
+
+	if res.Err() != nil {
+		if errors.Is(res.Err(), mongo.ErrNoDocuments) {
+			return &NotFoundError{
+				Key:     stakingTxHash,
+				Message: "BTC delegation not found when updating state",
+			}
+		}
+		return res.Err()
+	}
+
+	return nil
+}
+
+func (db *Database) GetBTCDelegationByStakingTxHash(
+	ctx context.Context, stakingTxHash string,
+) (*model.BTCDelegationDetails, error) {
+	filter := map[string]interface{}{"_id": stakingTxHash}
+	res := db.client.Database(db.dbName).
+		Collection(model.BTCDelegationDetailsCollection).
+		FindOne(ctx, filter)
+
+	var delegationDoc model.BTCDelegationDetails
+	err := res.Decode(&delegationDoc)
+	if err != nil {
+		if errors.Is(err, mongo.ErrNoDocuments) {
+			return nil, &NotFoundError{
+				Key:     stakingTxHash,
+				Message: "BTC delegation not found when getting by staking tx hash",
+			}
+		}
+		return nil, err
+	}
+
+	return &delegationDoc, nil
+}
diff --git a/internal/db/interface.go b/internal/db/interface.go
index d96cc62..4781ea7 100644
--- a/internal/db/interface.go
+++ b/internal/db/interface.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/babylonlabs-io/babylon-staking-indexer/internal/clients/bbnclient"
 	"github.com/babylonlabs-io/babylon-staking-indexer/internal/db/model"
+	"github.com/babylonlabs-io/babylon-staking-indexer/internal/types"
 )
 
 type DbInterface interface {
@@ -73,4 +74,33 @@ type DbInterface interface {
 	SaveCheckpointParams(
 		ctx context.Context, params *bbnclient.CheckpointParams,
 	) error
+	/**
+	 * SaveNewBTCDelegation saves a new BTC delegation to the database.
+	 * If the BTC delegation already exists, DuplicateKeyError will be returned.
+	 * @param ctx The context
+	 * @param delegationDoc The BTC delegation details
+	 * @return An error if the operation failed
+	 */
+	SaveNewBTCDelegation(
+		ctx context.Context, delegationDoc *model.BTCDelegationDetails,
+	) error
+	/**
+	 * SaveBTCDelegationStateUpdate saves a BTC delegation state update to the database.
+	 * @param ctx The context
+	 * @param delegationDoc The BTC delegation details
+	 * @return An error if the operation failed
+	 */
+	UpdateBTCDelegationState(
+		ctx context.Context, stakingTxHash string, newState types.DelegationState,
+	) error
+	/**
+	 * GetBTCDelegationByStakingTxHash retrieves the BTC delegation details by the staking tx hash.
+	 * If the BTC delegation does not exist, a NotFoundError will be returned.
+	 * @param ctx The context
+	 * @param stakingTxHash The staking tx hash
+	 * @return The BTC delegation details or an error
+	 */
+	GetBTCDelegationByStakingTxHash(
+		ctx context.Context, stakingTxHash string,
+	) (*model.BTCDelegationDetails, error)
 }
diff --git a/internal/db/model/delegation.go b/internal/db/model/delegation.go
index 901e93f..026e76a 100644
--- a/internal/db/model/delegation.go
+++ b/internal/db/model/delegation.go
@@ -2,10 +2,33 @@ package model
 
 import (
 	"github.com/babylonlabs-io/babylon-staking-indexer/internal/types"
+	bbntypes "github.com/babylonlabs-io/babylon/x/btcstaking/types"
 )
 
-type DelegationDocument struct {
-	StakingTxHashHex string                `bson:"_id"` // Primary key
-	State            types.DelegationState `bson:"state"`
-	// TODO: Placeholder for more fields
+type BTCDelegationDetails struct {
+	StakingTxHashHex          string                `bson:"_id"` // Primary key
+	ParamsVersion             string                `bson:"params_version"`
+	FinalityProviderBtcPksHex []string              `bson:"finality_provider_btc_pks_hex"`
+	StakerBtcPkHex            string                `bson:"staker_btc_pk_hex"`
+	StakingTime               string                `bson:"staking_time"`
+	StakingAmount             string                `bson:"staking_amount"`
+	UnbondingTime             string                `bson:"unbonding_time"`
+	UnbondingTx               string                `bson:"unbonding_tx"`
+	State                     types.DelegationState `bson:"state"`
+}
+
+func FromEventBTCDelegationCreated(
+	event *bbntypes.EventBTCDelegationCreated,
+) *BTCDelegationDetails {
+	return &BTCDelegationDetails{
+		StakingTxHashHex:          event.StakingTxHash, // babylon returns a hex string
+		ParamsVersion:             event.ParamsVersion,
+		FinalityProviderBtcPksHex: event.FinalityProviderBtcPksHex,
+		StakerBtcPkHex:            event.StakerBtcPkHex,
+		StakingTime:               event.StakingTime,
+		StakingAmount:             event.StakingAmount,
+		UnbondingTime:             event.UnbondingTime,
+		UnbondingTx:               event.UnbondingTx,
+		State:                     types.DelegationState(event.NewState),
+	}
 }
diff --git a/internal/db/model/setup.go b/internal/db/model/setup.go
index a4edde5..0a96ecf 100644
--- a/internal/db/model/setup.go
+++ b/internal/db/model/setup.go
@@ -15,7 +15,7 @@ import (
 
 const (
 	FinalityProviderDetailsCollection = "finality_provider_details"
-	DelegationCollection              = "delegation"
+	BTCDelegationDetailsCollection    = "btc_delegation_details"
 	GlobalParamsCollection            = "global_params"
 )
 
@@ -26,7 +26,7 @@ type index struct {
 
 var collections = map[string][]index{
 	FinalityProviderDetailsCollection: {{Indexes: map[string]int{}}},
-	DelegationCollection:              {{Indexes: map[string]int{}}},
+	BTCDelegationDetailsCollection:    {{Indexes: map[string]int{}}},
 	GlobalParamsCollection:            {{Indexes: map[string]int{}}},
 }
 
diff --git a/internal/db/params.go b/internal/db/params.go
index c089c5f..c66ac98 100644
--- a/internal/db/params.go
+++ b/internal/db/params.go
@@ -69,5 +69,6 @@ func (db *Database) SaveCheckpointParams(
 	if err != nil {
 		return fmt.Errorf("failed to save checkpoint params: %w", err)
 	}
+
 	return nil
 }
diff --git a/internal/services/delegation.go b/internal/services/delegation.go
index d16a5c5..0234f23 100644
--- a/internal/services/delegation.go
+++ b/internal/services/delegation.go
@@ -1,13 +1,266 @@
 package services
 
-import "context"
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/babylonlabs-io/babylon-staking-indexer/internal/db"
+	"github.com/babylonlabs-io/babylon-staking-indexer/internal/db/model"
+	"github.com/babylonlabs-io/babylon-staking-indexer/internal/types"
+	bbntypes "github.com/babylonlabs-io/babylon/x/btcstaking/types"
+	abcitypes "github.com/cometbft/cometbft/abci/types"
+)
 
 const (
+	EventBTCDelegationStateUpdate            EventTypes = "babylon.btcstaking.v1.EventBTCDelegationStateUpdate"
 	EventBTCDelegationCreated                EventTypes = "babylon.btcstaking.v1.EventBTCDelegationCreated"
+	EventCovenantQuorumReached               EventTypes = "babylon.btcstaking.v1.EventCovenantQuorumReached"
 	EventBTCDelegationInclusionProofReceived EventTypes = "babylon.btcstaking.v1.EventBTCDelegationInclusionProofReceived"
 	EventBTCDelgationUnbondedEarly           EventTypes = "babylon.btcstaking.v1.EventBTCDelgationUnbondedEarly"
 	EventBTCDelegationExpired                EventTypes = "babylon.btcstaking.v1.EventBTCDelegationExpired"
 )
 
-func (s *Service) processBTCDelegationStateUpdateEvent(ctx context.Context) {
+func (s *Service) processBTCDelegationStateUpdateEvent(ctx context.Context, event abcitypes.Event) *types.Error {
+	stateUpdate, err := parseEvent[*bbntypes.EventBTCDelegationStateUpdate](
+		EventBTCDelegationStateUpdate, event,
+	)
+	if err != nil {
+		return err
+	}
+
+	if err := validateBTCDelegationStateUpdateEvent(stateUpdate); err != nil {
+		return err
+	}
+
+	// Check if BTC delegation exists
+	_, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, stateUpdate.StakingTxHash)
+	if dbErr != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr),
+		)
+	}
+
+	if err := s.db.UpdateBTCDelegationState(
+		ctx, stateUpdate.StakingTxHash, types.DelegationState(stateUpdate.NewState),
+	); err != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to update BTC delegation state: %w", err),
+		)
+	}
+
+	return nil
+}
+
+func (s *Service) processNewBTCDelegationEvent(
+	ctx context.Context, event abcitypes.Event,
+) *types.Error {
+	newDelegation, err := parseEvent[*bbntypes.EventBTCDelegationCreated](
+		EventBTCDelegationCreated, event,
+	)
+	if err != nil {
+		return err
+	}
+
+	if err := validateBTCDelegationCreatedEvent(newDelegation); err != nil {
+		return err
+	}
+	if err := s.db.SaveNewBTCDelegation(
+		ctx, model.FromEventBTCDelegationCreated(newDelegation),
+	); err != nil {
+		if db.IsDuplicateKeyError(err) {
+			// BTC delegation already exists, ignore the event
+			return nil
+		}
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to save new BTC delegation: %w", err),
+		)
+	}
+
+	return nil
+}
+
+func (s *Service) processCovenantQuorumReachedEvent(
+	ctx context.Context, event abcitypes.Event,
+) *types.Error {
+	covenantQuorumReachedEvent, err := parseEvent[*bbntypes.EventCovenantQuorumReached](
+		EventCovenantQuorumReached, event,
+	)
+	if err != nil {
+		return err
+	}
+
+	if err := validateCovenantQuorumReachedEvent(covenantQuorumReachedEvent); err != nil {
+		return err
+	}
+
+	// Check if BTC delegation exists
+	_, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, covenantQuorumReachedEvent.StakingTxHash)
+	if dbErr != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr),
+		)
+	}
+
+	if err := s.db.UpdateBTCDelegationState(
+		ctx, covenantQuorumReachedEvent.StakingTxHash, types.DelegationState(covenantQuorumReachedEvent.NewState),
+	); err != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to update BTC delegation state: %w", err),
+		)
+	}
+
+	return nil
+}
+
+func (s *Service) processBTCDelegationInclusionProofReceivedEvent(
+	ctx context.Context, event abcitypes.Event,
+) *types.Error {
+	inclusionProofEvent, err := parseEvent[*bbntypes.EventBTCDelegationInclusionProofReceived](
+		EventBTCDelegationInclusionProofReceived, event,
+	)
+	if err != nil {
+		return err
+	}
+
+	if err := validateBTCDelegationInclusionProofReceivedEvent(inclusionProofEvent); err != nil {
+		return err
+	}
+
+	// Check if BTC delegation exists
+	_, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, inclusionProofEvent.StakingTxHash)
+	if dbErr != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr),
+		)
+	}
+
+	if err := s.db.UpdateBTCDelegationState(
+		ctx, inclusionProofEvent.StakingTxHash, types.DelegationState(inclusionProofEvent.NewState),
+	); err != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to update BTC delegation state: %w", err),
+		)
+	}
+
+	return nil
+}
+
+func (s *Service) processBTCDelegationUnbondedEarlyEvent(
+	ctx context.Context, event abcitypes.Event,
+) *types.Error {
+	unbondedEarlyEvent, err := parseEvent[*bbntypes.EventBTCDelgationUnbondedEarly](
+		EventBTCDelgationUnbondedEarly, event,
+	)
+	if err != nil {
+		return err
+	}
+
+	if err := validateBTCDelegationUnbondedEarlyEvent(unbondedEarlyEvent); err != nil {
+		return err
+	}
+
+	// Check if BTC delegation exists
+	_, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, unbondedEarlyEvent.StakingTxHash)
+	if dbErr != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr),
+		)
+	}
+
+	if err := s.db.UpdateBTCDelegationState(
+		ctx, unbondedEarlyEvent.StakingTxHash, types.DelegationState(unbondedEarlyEvent.NewState),
+	); err != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to update BTC delegation state: %w", err),
+		)
+	}
+
+	return nil
+}
+
+func (s *Service) processBTCDelegationExpiredEvent(
+	ctx context.Context, event abcitypes.Event,
+) *types.Error {
+	expiredEvent, err := parseEvent[*bbntypes.EventBTCDelegationExpired](
+		EventBTCDelegationExpired, event,
+	)
+	if err != nil {
+		return err
+	}
+
+	if err := validateBTCDelegationExpiredEvent(expiredEvent); err != nil {
+		return err
+	}
+
+	// Check if BTC delegation exists
+	_, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, expiredEvent.StakingTxHash)
+	if dbErr != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr),
+		)
+	}
+
+	if err := s.db.UpdateBTCDelegationState(
+		ctx, expiredEvent.StakingTxHash, types.DelegationState(expiredEvent.NewState),
+	); err != nil {
+		return types.NewError(
+			http.StatusInternalServerError,
+			types.InternalServiceError,
+			fmt.Errorf("failed to update BTC delegation state: %w", err),
+		)
+	}
+
+	return nil
+}
+
+// You'll need to implement these functions:
+func validateBTCDelegationCreatedEvent(event *bbntypes.EventBTCDelegationCreated) *types.Error {
+	// Implement validation logic here
+	return nil
+}
+
+func validateBTCDelegationStateUpdateEvent(event *bbntypes.EventBTCDelegationStateUpdate) *types.Error {
+	// Implement validation logic here
+	return nil
+}
+
+func validateCovenantQuorumReachedEvent(event *bbntypes.EventCovenantQuorumReached) *types.Error {
+	// Implement validation logic here
+	return nil
+}
+
+func validateBTCDelegationInclusionProofReceivedEvent(event *bbntypes.EventBTCDelegationInclusionProofReceived) *types.Error {
+	// Implement validation logic here
+	return nil
+}
+
+func validateBTCDelegationUnbondedEarlyEvent(event *bbntypes.EventBTCDelgationUnbondedEarly) *types.Error {
+	// Implement validation logic here
+	return nil
+}
+
+func validateBTCDelegationExpiredEvent(event *bbntypes.EventBTCDelegationExpired) *types.Error {
+	// Implement validation logic here
+	return nil
 }
diff --git a/internal/services/events.go b/internal/services/events.go
index 0c1ad81..ff69b1a 100644
--- a/internal/services/events.go
+++ b/internal/services/events.go
@@ -2,14 +2,14 @@ package services
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"net/http"
 	"time"
 
 	"github.com/babylonlabs-io/babylon-staking-indexer/internal/types"
-	"github.com/babylonlabs-io/babylon-staking-indexer/internal/utils"
 	abcitypes "github.com/cometbft/cometbft/abci/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	proto "github.com/cosmos/gogoproto/proto"
 	"github.com/rs/zerolog/log"
 )
 
@@ -55,6 +55,7 @@ func (s *Service) processEvent(ctx context.Context, event BbnEvent) {
 	// Note: We no longer need to check for the event category here. We can directly
 	// process the event based on its type.
 	bbnEvent := event.Event
+	// log.Debug().Str("event_type", bbnEvent.Type).Msg("Processing event")
 	switch EventTypes(bbnEvent.Type) {
 	case EventFinalityProviderCreatedType:
 		log.Debug().Msg("Processing new finality provider event")
@@ -65,15 +66,36 @@ func (s *Service) processEvent(ctx context.Context, event BbnEvent) {
 	case EventFinalityProviderStatusChange:
 		log.Debug().Msg("Processing finality provider status change event")
 		s.processFinalityProviderStateChangeEvent(ctx, bbnEvent)
+	case EventBTCDelegationCreated:
+		log.Debug().Msg("Processing new BTC delegation event")
+		s.processNewBTCDelegationEvent(ctx, bbnEvent)
+	case EventBTCDelegationStateUpdate:
+		log.Debug().Msg("Processing BTC delegation state update event")
+		s.processBTCDelegationStateUpdateEvent(ctx, bbnEvent)
+	case EventCovenantQuorumReached:
+		log.Debug().Msg("Processing covenant quorum reached event")
+		s.processCovenantQuorumReachedEvent(ctx, bbnEvent)
+	case EventBTCDelegationInclusionProofReceived:
+		log.Debug().Msg("Processing BTC delegation inclusion proof received event")
+		s.processBTCDelegationInclusionProofReceivedEvent(ctx, bbnEvent)
+	case EventBTCDelgationUnbondedEarly:
+		log.Debug().Msg("Processing BTC delegation unbonded early event")
+		s.processBTCDelegationUnbondedEarlyEvent(ctx, bbnEvent)
+	case EventBTCDelegationExpired:
+		log.Debug().Msg("Processing BTC delegation expired event")
+		s.processBTCDelegationExpiredEvent(ctx, bbnEvent)
 	}
 }
 
-func parseEvent[T any](
+func parseEvent[T proto.Message](
 	expectedType EventTypes,
 	event abcitypes.Event,
-) (*T, *types.Error) {
+) (T, *types.Error) {
+	var result T
+
+	// Check if the event type matches the expected type
 	if EventTypes(event.Type) != expectedType {
-		return nil, types.NewErrorWithMsg(
+		return result, types.NewErrorWithMsg(
 			http.StatusInternalServerError,
 			types.InternalServiceError,
 			fmt.Sprintf(
@@ -83,8 +105,10 @@ func parseEvent[T any](
 			),
 		)
 	}
+
+	// Check if the event has attributes
 	if len(event.Attributes) == 0 {
-		return nil, types.NewErrorWithMsg(
+		return result, types.NewErrorWithMsg(
 			http.StatusInternalServerError,
 			types.InternalServiceError,
 			fmt.Sprintf(
@@ -94,35 +118,25 @@ func parseEvent[T any](
 		)
 	}
 
-	// Create a map to store the attributes
-	attributeMap := make(map[string]string)
-
-	// Populate the attribute map from the event's attributes
-	for _, attr := range event.Attributes {
-		// Unescape the attribute value
-		attributeMap[attr.Key] = utils.SafeUnescape(attr.Value)
-	}
-
-	// Marshal the attributeMap into JSON
-	attrJSON, err := json.Marshal(attributeMap)
+	// Use the SDK's ParseTypedEvent function
+	protoMsg, err := sdk.ParseTypedEvent(event)
 	if err != nil {
-		return nil, types.NewError(
+		return result, types.NewError(
 			http.StatusInternalServerError,
 			types.InternalServiceError,
-			fmt.Errorf("failed to marshal attributes into JSON: %w", err),
+			fmt.Errorf("failed to parse typed event: %w", err),
 		)
 	}
 
-	// Unmarshal the JSON into the T struct
-	var evt T
-	err = json.Unmarshal(attrJSON, &evt)
-	if err != nil {
-		return nil, types.NewError(
+	// Type assertion to ensure we have the correct concrete type
+	concreteMsg, ok := protoMsg.(T)
+	if !ok {
+		return result, types.NewError(
 			http.StatusInternalServerError,
 			types.InternalServiceError,
-			fmt.Errorf("failed to unmarshal attributes into %T: %w", evt, err),
+			fmt.Errorf("parsed event type %T does not match expected type %T", protoMsg, result),
 		)
 	}
 
-	return &evt, nil
+	return concreteMsg, nil
 }
diff --git a/internal/services/finality-provider.go b/internal/services/finality-provider.go
index 979148a..474ef15 100644
--- a/internal/services/finality-provider.go
+++ b/internal/services/finality-provider.go
@@ -21,7 +21,7 @@ const (
 func (s *Service) processNewFinalityProviderEvent(
 	ctx context.Context, event abcitypes.Event,
 ) *types.Error {
-	newFinalityProvider, err := parseEvent[bbntypes.EventFinalityProviderCreated](
+	newFinalityProvider, err := parseEvent[*bbntypes.EventFinalityProviderCreated](
 		EventFinalityProviderCreatedType, event,
 	)
 	if err != nil {
@@ -50,7 +50,7 @@ func (s *Service) processNewFinalityProviderEvent(
 func (s *Service) processFinalityProviderEditedEvent(
 	ctx context.Context, event abcitypes.Event,
 ) *types.Error {
-	finalityProviderEdited, err := parseEvent[bbntypes.EventFinalityProviderEdited](
+	finalityProviderEdited, err := parseEvent[*bbntypes.EventFinalityProviderEdited](
 		EventFinalityProviderEditedType, event,
 	)
 	if err != nil {
@@ -75,7 +75,7 @@ func (s *Service) processFinalityProviderEditedEvent(
 func (s *Service) processFinalityProviderStateChangeEvent(
 	ctx context.Context, event abcitypes.Event,
 ) *types.Error {
-	finalityProviderStateChange, err := parseEvent[bbntypes.EventFinalityProviderStatusChange](
+	finalityProviderStateChange, err := parseEvent[*bbntypes.EventFinalityProviderStatusChange](
 		EventFinalityProviderStatusChange, event,
 	)
 	if err != nil {
diff --git a/internal/services/global-params.go b/internal/services/global-params.go
index 22bc0c3..717c7d6 100644
--- a/internal/services/global-params.go
+++ b/internal/services/global-params.go
@@ -37,6 +37,7 @@ func (s *Service) fetchAndSaveParams(ctx context.Context) *types.Error {
 			fmt.Errorf("failed to get staking params: %w", err),
 		)
 	}
+
 	for version, params := range allStakingParams {
 		if params == nil {
 			return types.NewInternalServiceError(
@@ -49,5 +50,6 @@ func (s *Service) fetchAndSaveParams(ctx context.Context) *types.Error {
 			)
 		}
 	}
+
 	return nil
 }
diff --git a/internal/types/state.go b/internal/types/state.go
index f9c756d..cf5b645 100644
--- a/internal/types/state.go
+++ b/internal/types/state.go
@@ -5,9 +5,14 @@ type DelegationState string
 
 const (
 	StatePending   DelegationState = "PENDING"
+	StateVerified  DelegationState = "VERIFIED"
 	StateActive    DelegationState = "ACTIVE"
 	StateUnbonding DelegationState = "UNBONDING"
 	StateWithdrawn DelegationState = "WITHDRAWN"
 	StateSlashed   DelegationState = "SLASHED"
 	StateUnbonded  DelegationState = "UNBONDED"
 )
+
+func (s DelegationState) String() string {
+	return string(s)
+}
diff --git a/tests/mocks/mock_bbn_client.go b/tests/mocks/mock_bbn_client.go
index 92fdd8b..d2591c9 100644
--- a/tests/mocks/mock_bbn_client.go
+++ b/tests/mocks/mock_bbn_client.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.41.0. DO NOT EDIT.
+// Code generated by mockery v2.42.1. DO NOT EDIT.
 
 package mocks
 
diff --git a/tests/mocks/mock_btc_client.go b/tests/mocks/mock_btc_client.go
index 21a8239..83c0ef2 100644
--- a/tests/mocks/mock_btc_client.go
+++ b/tests/mocks/mock_btc_client.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.41.0. DO NOT EDIT.
+// Code generated by mockery v2.42.1. DO NOT EDIT.
 
 package mocks
 
diff --git a/tests/mocks/mock_db_client.go b/tests/mocks/mock_db_client.go
index 1efd07f..8b54bf7 100644
--- a/tests/mocks/mock_db_client.go
+++ b/tests/mocks/mock_db_client.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.41.0. DO NOT EDIT.
+// Code generated by mockery v2.42.1. DO NOT EDIT.
 
 package mocks
 
@@ -7,6 +7,8 @@ import (
 
 	bbnclient "github.com/babylonlabs-io/babylon-staking-indexer/internal/clients/bbnclient"
 
+	internaltypes "github.com/babylonlabs-io/babylon-staking-indexer/internal/types"
+
 	mock "github.com/stretchr/testify/mock"
 
 	model "github.com/babylonlabs-io/babylon-staking-indexer/internal/db/model"
@@ -19,6 +21,36 @@ type DbInterface struct {
 	mock.Mock
 }
 
+// GetBTCDelegationByStakingTxHash provides a mock function with given fields: ctx, stakingTxHash
+func (_m *DbInterface) GetBTCDelegationByStakingTxHash(ctx context.Context, stakingTxHash string) (*model.BTCDelegationDetails, error) {
+	ret := _m.Called(ctx, stakingTxHash)
+
+	if len(ret) == 0 {
+		panic("no return value specified for GetBTCDelegationByStakingTxHash")
+	}
+
+	var r0 *model.BTCDelegationDetails
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) (*model.BTCDelegationDetails, error)); ok {
+		return rf(ctx, stakingTxHash)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, string) *model.BTCDelegationDetails); ok {
+		r0 = rf(ctx, stakingTxHash)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*model.BTCDelegationDetails)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, stakingTxHash)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // GetFinalityProviderByBtcPk provides a mock function with given fields: ctx, btcPk
 func (_m *DbInterface) GetFinalityProviderByBtcPk(ctx context.Context, btcPk string) (*model.FinalityProviderDetails, error) {
 	ret := _m.Called(ctx, btcPk)
@@ -85,6 +117,24 @@ func (_m *DbInterface) SaveCheckpointParams(ctx context.Context, params *types.P
 	return r0
 }
 
+// SaveNewBTCDelegation provides a mock function with given fields: ctx, delegationDoc
+func (_m *DbInterface) SaveNewBTCDelegation(ctx context.Context, delegationDoc *model.BTCDelegationDetails) error {
+	ret := _m.Called(ctx, delegationDoc)
+
+	if len(ret) == 0 {
+		panic("no return value specified for SaveNewBTCDelegation")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *model.BTCDelegationDetails) error); ok {
+		r0 = rf(ctx, delegationDoc)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
 // SaveNewFinalityProvider provides a mock function with given fields: ctx, fpDoc
 func (_m *DbInterface) SaveNewFinalityProvider(ctx context.Context, fpDoc *model.FinalityProviderDetails) error {
 	ret := _m.Called(ctx, fpDoc)
@@ -121,6 +171,24 @@ func (_m *DbInterface) SaveStakingParams(ctx context.Context, version uint32, pa
 	return r0
 }
 
+// UpdateBTCDelegationState provides a mock function with given fields: ctx, stakingTxHash, newState
+func (_m *DbInterface) UpdateBTCDelegationState(ctx context.Context, stakingTxHash string, newState internaltypes.DelegationState) error {
+	ret := _m.Called(ctx, stakingTxHash, newState)
+
+	if len(ret) == 0 {
+		panic("no return value specified for UpdateBTCDelegationState")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, internaltypes.DelegationState) error); ok {
+		r0 = rf(ctx, stakingTxHash, newState)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
 // UpdateFinalityProviderDetailsFromEvent provides a mock function with given fields: ctx, detailsToUpdate
 func (_m *DbInterface) UpdateFinalityProviderDetailsFromEvent(ctx context.Context, detailsToUpdate *model.FinalityProviderDetails) error {
 	ret := _m.Called(ctx, detailsToUpdate)