Repository: ObolNetwork/charon

Issue:
Replace go-eth2-client with in-house solution
# 🎯 Problem to be solved
`go-eth2-client` has been doing good job in the past years for `charon`'s needs. However, it was not built for the purpose of charon and the fact `charon` accommodates multiple clients. We've been experiencing issues with `go-eth2-client` with its abrupt code deletions, which removed support for some clients.

Some of the benefits that we will experience by having our in-house solution instead of `go-eth2-client`:
- Currently we manage a wrapper around `go-eth2-client` to add endpoints that aren't supported. We won't need that anymore.
- Deprecate endpoints, instead of completely removing support for them.
- Work in a timely manner on updates for future forks, instead of waiting on attestant to accommodate the changes.
- Incorporate different strategies for BNs - instead of calling all at once, potentially have some sequencing, incorporated fallback BNs, etc.
- Accommodate different chains (like gnosis) in an easier manner.

# 🛠️ Proposed solution 1
Use complete new repository. We can make use of a code generator, and the Beacon API.
TODO: Add more details if this is the chosen solution.

# 🛠️ Proposed solution 2
Fork `go-eth2-client` and maintain a much more customised fork of it.
TODO: Add more details if this is the chosen solution.

# 🧪 Tests
- [ ] Tested by new automated unit/integration/smoke tests
- [ ] Manually tested on local Kurtosis simnet


Discussion:

Solution patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 1c0d57c8..a2d53e4f 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -20,16 +20,3 @@ jobs:
         uses: golangci/golangci-lint-action@v8
         with:
           version: v2.2.1
-      - name: notify failure
-        if: failure() && github.ref == 'refs/heads/main'
-        env:
-          DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
-          DISCORD_USERNAME: GitHub
-          DISCORD_AVATAR: https://avatars.githubusercontent.com/u/583231
-          DISCORD_EMBEDS: |
-            [{
-              "title": "🚨  Main branch workflow failed: ${{ github.workflow }}",
-              "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
-              "color": 10038562
-            }]
-        uses: Ilshidur/action-discord@0.3.2
diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml
index 8e39ba1c..540cdedb 100644
--- a/.github/workflows/nightly-tests.yml
+++ b/.github/workflows/nightly-tests.yml
@@ -2,7 +2,7 @@
 name: nightly tests
 on:
   schedule:
-    - cron: '00 12 * * *'
+    - cron: "00 12 * * *"
 
 jobs:
   nightly_test:
@@ -49,22 +49,3 @@ jobs:
           name: fuzz-test-logs
           path: testutil/compose/fuzz/*.log
           retention-days: 3
-
-  notify_failure:
-    runs-on: ubuntu-latest
-    needs: [ nightly_test ]
-    # Syntax ref: https://github.com/actions/runner/issues/1251
-    if: always() && github.ref == 'refs/heads/main' && contains(join(needs.*.result, ','), 'failure')
-    steps:
-      - name: notify failure
-        uses: Ilshidur/action-discord@0.3.2
-        env:
-          DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
-          DISCORD_USERNAME: GitHub
-          DISCORD_AVATAR: https://avatars.githubusercontent.com/u/583231
-          DISCORD_EMBEDS: |
-            [{
-              "title": "🚨  Nightly tests failed: ${{ github.workflow }}",
-              "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
-              "color": 10038562
-            }]
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index a078d1c4..7774419c 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -16,17 +16,3 @@ jobs:
       - uses: actions/setup-python@v5
       - uses: ./.github/actions/setup-go
       - uses: pre-commit/action@v3.0.1
-
-      - name: notify failure
-        if: failure() && github.ref == 'refs/heads/main'
-        env:
-          DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
-          DISCORD_USERNAME: GitHub
-          DISCORD_AVATAR: https://avatars.githubusercontent.com/u/583231
-          DISCORD_EMBEDS: |
-            [{
-              "title": "🚨  Main branch workflow failed: ${{ github.workflow }}",
-              "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
-              "color": 10038562
-            }]
-        uses: Ilshidur/action-discord@0.3.2
diff --git a/app/eth1wrap/mocks/erc1271.go b/app/eth1wrap/mocks/erc1271.go
index 9587b7be..e57cca03 100644
--- a/app/eth1wrap/mocks/erc1271.go
+++ b/app/eth1wrap/mocks/erc1271.go
@@ -1,11 +1,11 @@
 // Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
 
-// Code generated by mockery v2.42.1. DO NOT EDIT.
+// Code generated by mockery v2.53.3. DO NOT EDIT.
 
 package mocks
 
 import (
-	bind "github.com/ethereum/go-ethereum/accounts/abi/bind"
+	bind "github.com/ethereum/go-ethereum/accounts/abi/bind/v2"
 
 	mock "github.com/stretchr/testify/mock"
 )
diff --git a/app/eth1wrap/mocks/eth_client.go b/app/eth1wrap/mocks/eth_client.go
index 46efdd00..e575b62f 100644
--- a/app/eth1wrap/mocks/eth_client.go
+++ b/app/eth1wrap/mocks/eth_client.go
@@ -1,6 +1,6 @@
 // Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
 
-// Code generated by mockery v2.42.1. DO NOT EDIT.
+// Code generated by mockery v2.53.3. DO NOT EDIT.
 
 package mocks
 
@@ -292,7 +292,7 @@ func (_m *EthClient) ChainID(ctx context.Context) (*big.Int, error) {
 	return r0, r1
 }
 
-// Client provides a mock function with given fields:
+// Client provides a mock function with no fields
 func (_m *EthClient) Client() *rpc.Client {
 	ret := _m.Called()
 
@@ -312,7 +312,7 @@ func (_m *EthClient) Client() *rpc.Client {
 	return r0
 }
 
-// Close provides a mock function with given fields:
+// Close provides a mock function with no fields
 func (_m *EthClient) Close() {
 	_m.Called()
 }
diff --git a/app/eth1wrap/mocks/eth_client_runner.go b/app/eth1wrap/mocks/eth_client_runner.go
index f87d15fc..31de82ba 100644
--- a/app/eth1wrap/mocks/eth_client_runner.go
+++ b/app/eth1wrap/mocks/eth_client_runner.go
@@ -1,6 +1,6 @@
 // Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
 
-// Code generated by mockery v2.42.1. DO NOT EDIT.
+// Code generated by mockery v2.53.3. DO NOT EDIT.
 
 package mocks
 
diff --git a/app/eth2wrap/eth2wrap_gen.go b/app/eth2wrap/eth2wrap_gen.go
index a6fb97b1..09ebd85a 100644
--- a/app/eth2wrap/eth2wrap_gen.go
+++ b/app/eth2wrap/eth2wrap_gen.go
@@ -14,19 +14,11 @@ import (
 	"github.com/attestantio/go-eth2-client/spec"
 	"github.com/attestantio/go-eth2-client/spec/altair"
 	"github.com/attestantio/go-eth2-client/spec/phase0"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 )
 
 // Client defines all go-eth2-client interfaces used in charon.
 type Client interface {
 	eth2client.Service
-	eth2exp.BeaconCommitteeSelectionAggregator
-	eth2exp.SyncCommitteeSelectionAggregator
-	eth2exp.ProposerConfigProvider
-	BlockAttestationsProvider
-	BlockProvider
-	BeaconStateCommitteesProvider
-	NodePeerCountProvider
 
 	CachedValidatorsProvider
 	SetValidatorCache(func(context.Context) (ActiveValidators, CompleteValidators, error))
@@ -38,14 +30,18 @@ type Client interface {
 	eth2client.AttestationDataProvider
 	eth2client.AttestationsSubmitter
 	eth2client.AttesterDutiesProvider
+	eth2client.BeaconBlockAttestationsProvider
 	eth2client.BeaconBlockRootProvider
+	eth2client.BeaconCommitteeSelectionsProvider
 	eth2client.BeaconCommitteeSubscriptionsSubmitter
+	eth2client.BeaconCommitteesProvider
 	eth2client.BlindedProposalSubmitter
 	eth2client.DepositContractProvider
 	eth2client.DomainProvider
 	eth2client.ForkProvider
 	eth2client.ForkScheduleProvider
 	eth2client.GenesisProvider
+	eth2client.NodePeerCountProvider
 	eth2client.NodeSyncingProvider
 	eth2client.NodeVersionProvider
 	eth2client.ProposalPreparationsSubmitter
@@ -60,6 +56,7 @@ type Client interface {
 	eth2client.SyncCommitteeContributionsSubmitter
 	eth2client.SyncCommitteeDutiesProvider
 	eth2client.SyncCommitteeMessagesSubmitter
+	eth2client.SyncCommitteeSelectionsProvider
 	eth2client.SyncCommitteeSubscriptionsSubmitter
 	eth2client.ValidatorRegistrationsSubmitter
 	eth2client.ValidatorsProvider
@@ -135,6 +132,27 @@ func (m multi) SignedBeaconBlock(ctx context.Context, opts *api.SignedBeaconBloc
 	return res0, err
 }
 
+// BeaconCommittees fetches all beacon committees for the given options.
+func (m multi) BeaconCommittees(ctx context.Context, opts *api.BeaconCommitteesOpts) (*api.Response[[]*apiv1.BeaconCommittee], error) {
+	const label = "beacon_committees"
+	defer latency(ctx, label, false)()
+	defer incRequest(label)
+
+	res0, err := provide(ctx, m.clients, m.fallbacks,
+		func(ctx context.Context, args provideArgs) (*api.Response[[]*apiv1.BeaconCommittee], error) {
+			return args.client.BeaconCommittees(ctx, opts)
+		},
+		nil, m.selector,
+	)
+
+	if err != nil {
+		incError(label)
+		err = wrapError(ctx, err, label)
+	}
+
+	return res0, err
+}
+
 // AggregateAttestation fetches the aggregate attestation for the given options.
 func (m multi) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (*api.Response[*spec.VersionedAttestation], error) {
 	const label = "aggregate_attestation"
@@ -368,6 +386,27 @@ func (m multi) SubmitSyncCommitteeContributions(ctx context.Context, contributio
 	return err
 }
 
+// SyncCommitteeSelections submits sync committee selections.
+func (m multi) SyncCommitteeSelections(ctx context.Context, opts *api.SyncCommitteeSelectionsOpts) (*api.Response[[]*apiv1.SyncCommitteeSelection], error) {
+	const label = "sync_committee_selections"
+	defer latency(ctx, label, false)()
+	defer incRequest(label)
+
+	res0, err := provide(ctx, m.clients, m.fallbacks,
+		func(ctx context.Context, args provideArgs) (*api.Response[[]*apiv1.SyncCommitteeSelection], error) {
+			return args.client.SyncCommitteeSelections(ctx, opts)
+		},
+		nil, m.selector,
+	)
+
+	if err != nil {
+		incError(label)
+		err = wrapError(ctx, err, label)
+	}
+
+	return res0, err
+}
+
 // Proposal fetches a proposal for signing.
 func (m multi) Proposal(ctx context.Context, opts *api.ProposalOpts) (*api.Response[*api.VersionedProposal], error) {
 	const label = "proposal"
@@ -411,6 +450,27 @@ func (m multi) BeaconBlockRoot(ctx context.Context, opts *api.BeaconBlockRootOpt
 	return res0, err
 }
 
+// BeaconBlockAttestations fetches a block's attestations given a set of options.
+func (m multi) BeaconBlockAttestations(ctx context.Context, opts *api.BeaconBlockAttestationsOpts) (*api.Response[[]*spec.VersionedAttestation], error) {
+	const label = "beacon_block_attestations"
+	defer latency(ctx, label, false)()
+	defer incRequest(label)
+
+	res0, err := provide(ctx, m.clients, m.fallbacks,
+		func(ctx context.Context, args provideArgs) (*api.Response[[]*spec.VersionedAttestation], error) {
+			return args.client.BeaconBlockAttestations(ctx, opts)
+		},
+		nil, m.selector,
+	)
+
+	if err != nil {
+		incError(label)
+		err = wrapError(ctx, err, label)
+	}
+
+	return res0, err
+}
+
 // SubmitProposal submits a proposal.
 func (m multi) SubmitProposal(ctx context.Context, opts *api.SubmitProposalOpts) error {
 	const label = "submit_proposal"
@@ -453,6 +513,27 @@ func (m multi) SubmitBeaconCommitteeSubscriptions(ctx context.Context, subscript
 	return err
 }
 
+// BeaconCommitteeSelections submits beacon committee selections.
+func (m multi) BeaconCommitteeSelections(ctx context.Context, opts *api.BeaconCommitteeSelectionsOpts) (*api.Response[[]*apiv1.BeaconCommitteeSelection], error) {
+	const label = "beacon_committee_selections"
+	defer latency(ctx, label, false)()
+	defer incRequest(label)
+
+	res0, err := provide(ctx, m.clients, m.fallbacks,
+		func(ctx context.Context, args provideArgs) (*api.Response[[]*apiv1.BeaconCommitteeSelection], error) {
+			return args.client.BeaconCommitteeSelections(ctx, opts)
+		},
+		nil, m.selector,
+	)
+
+	if err != nil {
+		incError(label)
+		err = wrapError(ctx, err, label)
+	}
+
+	return res0, err
+}
+
 // SubmitBlindedProposal submits a beacon block.
 func (m multi) SubmitBlindedProposal(ctx context.Context, opts *api.SubmitBlindedProposalOpts) error {
 	const label = "submit_blinded_proposal"
@@ -559,6 +640,28 @@ func (m multi) Genesis(ctx context.Context, opts *api.GenesisOpts) (*api.Respons
 	return res0, err
 }
 
+// NodePeerCount provides the peer count of the node.
+// Note this endpoint is cached in go-eth2-client.
+func (m multi) NodePeerCount(ctx context.Context, opts *api.NodePeerCountOpts) (*api.Response[*apiv1.PeerCount], error) {
+	const label = "node_peer_count"
+
+	defer incRequest(label)
+
+	res0, err := provide(ctx, m.clients, m.fallbacks,
+		func(ctx context.Context, args provideArgs) (*api.Response[*apiv1.PeerCount], error) {
+			return args.client.NodePeerCount(ctx, opts)
+		},
+		nil, m.selector,
+	)
+
+	if err != nil {
+		incError(label)
+		err = wrapError(ctx, err, label)
+	}
+
+	return res0, err
+}
+
 // NodeSyncing provides the state of the node's synchronization with the chain.
 func (m multi) NodeSyncing(ctx context.Context, opts *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error) {
 	const label = "node_syncing"
@@ -790,6 +893,16 @@ func (l *lazy) SignedBeaconBlock(ctx context.Context, opts *api.SignedBeaconBloc
 	return cl.SignedBeaconBlock(ctx, opts)
 }
 
+// BeaconCommittees fetches all beacon committees for the given options.
+func (l *lazy) BeaconCommittees(ctx context.Context, opts *api.BeaconCommitteesOpts) (res0 *api.Response[[]*apiv1.BeaconCommittee], err error) {
+	cl, err := l.getOrCreateClient(ctx)
+	if err != nil {
+		return res0, err
+	}
+
+	return cl.BeaconCommittees(ctx, opts)
+}
+
 // AggregateAttestation fetches the aggregate attestation for the given options.
 func (l *lazy) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (res0 *api.Response[*spec.VersionedAttestation], err error) {
 	cl, err := l.getOrCreateClient(ctx)
@@ -901,6 +1014,16 @@ func (l *lazy) SubmitSyncCommitteeContributions(ctx context.Context, contributio
 	return cl.SubmitSyncCommitteeContributions(ctx, contributionAndProofs)
 }
 
+// SyncCommitteeSelections submits sync committee selections.
+func (l *lazy) SyncCommitteeSelections(ctx context.Context, opts *api.SyncCommitteeSelectionsOpts) (res0 *api.Response[[]*apiv1.SyncCommitteeSelection], err error) {
+	cl, err := l.getOrCreateClient(ctx)
+	if err != nil {
+		return res0, err
+	}
+
+	return cl.SyncCommitteeSelections(ctx, opts)
+}
+
 // Proposal fetches a proposal for signing.
 func (l *lazy) Proposal(ctx context.Context, opts *api.ProposalOpts) (res0 *api.Response[*api.VersionedProposal], err error) {
 	cl, err := l.getOrCreateClient(ctx)
@@ -921,6 +1044,16 @@ func (l *lazy) BeaconBlockRoot(ctx context.Context, opts *api.BeaconBlockRootOpt
 	return cl.BeaconBlockRoot(ctx, opts)
 }
 
+// BeaconBlockAttestations fetches a block's attestations given a set of options.
+func (l *lazy) BeaconBlockAttestations(ctx context.Context, opts *api.BeaconBlockAttestationsOpts) (res0 *api.Response[[]*spec.VersionedAttestation], err error) {
+	cl, err := l.getOrCreateClient(ctx)
+	if err != nil {
+		return res0, err
+	}
+
+	return cl.BeaconBlockAttestations(ctx, opts)
+}
+
 // SubmitProposal submits a proposal.
 func (l *lazy) SubmitProposal(ctx context.Context, opts *api.SubmitProposalOpts) (err error) {
 	cl, err := l.getOrCreateClient(ctx)
@@ -941,6 +1074,16 @@ func (l *lazy) SubmitBeaconCommitteeSubscriptions(ctx context.Context, subscript
 	return cl.SubmitBeaconCommitteeSubscriptions(ctx, subscriptions)
 }
 
+// BeaconCommitteeSelections submits beacon committee selections.
+func (l *lazy) BeaconCommitteeSelections(ctx context.Context, opts *api.BeaconCommitteeSelectionsOpts) (res0 *api.Response[[]*apiv1.BeaconCommitteeSelection], err error) {
+	cl, err := l.getOrCreateClient(ctx)
+	if err != nil {
+		return res0, err
+	}
+
+	return cl.BeaconCommitteeSelections(ctx, opts)
+}
+
 // SubmitBlindedProposal submits a beacon block.
 func (l *lazy) SubmitBlindedProposal(ctx context.Context, opts *api.SubmitBlindedProposalOpts) (err error) {
 	cl, err := l.getOrCreateClient(ctx)
@@ -991,6 +1134,16 @@ func (l *lazy) Genesis(ctx context.Context, opts *api.GenesisOpts) (res0 *api.Re
 	return cl.Genesis(ctx, opts)
 }
 
+// NodePeerCount provides the peer count of the node.
+func (l *lazy) NodePeerCount(ctx context.Context, opts *api.NodePeerCountOpts) (res0 *api.Response[*apiv1.PeerCount], err error) {
+	cl, err := l.getOrCreateClient(ctx)
+	if err != nil {
+		return res0, err
+	}
+
+	return cl.NodePeerCount(ctx, opts)
+}
+
 // NodeSyncing provides the state of the node's synchronization with the chain.
 func (l *lazy) NodeSyncing(ctx context.Context, opts *api.NodeSyncingOpts) (res0 *api.Response[*apiv1.SyncState], err error) {
 	cl, err := l.getOrCreateClient(ctx)
diff --git a/app/eth2wrap/genwrap/genwrap.go b/app/eth2wrap/genwrap/genwrap.go
index cf8025ea..53f03376 100644
--- a/app/eth2wrap/genwrap/genwrap.go
+++ b/app/eth2wrap/genwrap/genwrap.go
@@ -47,13 +47,6 @@ import (
 // Client defines all go-eth2-client interfaces used in charon.
 type Client interface {
     eth2client.Service
-    eth2exp.BeaconCommitteeSelectionAggregator
-    eth2exp.SyncCommitteeSelectionAggregator
-    eth2exp.ProposerConfigProvider
-    BlockAttestationsProvider
-		BlockProvider
-    BeaconStateCommitteesProvider
-    NodePeerCountProvider
 
     CachedValidatorsProvider
     SetValidatorCache(func(context.Context) (ActiveValidators, CompleteValidators, error))
@@ -113,6 +106,9 @@ type Client interface {
 		"BeaconBlockRootProvider":               {Latency: false, Log: false},
 		"ProposalSubmitter":                     {Latency: true, Log: false},
 		"BeaconCommitteeSubscriptionsSubmitter": {Latency: true, Log: false},
+		"BeaconBlockAttestationsProvider":       {Latency: true, Log: false},
+		"BeaconCommitteeSelectionsProvider":     {Latency: true, Log: false},
+		"BeaconCommitteesProvider":              {Latency: true, Log: false},
 		"BlindedProposalProvider":               {Latency: true, Log: false},
 		"BlindedProposalSubmitter":              {Latency: true, Log: false},
 		"DepositContractProvider":               {Latency: false, Log: false},
@@ -120,6 +116,7 @@ type Client interface {
 		"ForkProvider":                          {Latency: true, Log: false},
 		"ForkScheduleProvider":                  {Latency: true, Log: false},
 		"GenesisProvider":                       {Latency: false, Log: false},
+		"NodePeerCountProvider":                 {Latency: false, Log: false},
 		"NodeSyncingProvider":                   {Latency: true, Log: false},
 		"NodeVersionProvider":                   {Latency: false, Log: false},
 		"ProposerDutiesProvider":                {Latency: true, Log: false},
@@ -133,6 +130,7 @@ type Client interface {
 		"SyncCommitteeContributionsSubmitter":   {Latency: true, Log: false},
 		"SyncCommitteeMessagesSubmitter":        {Latency: true, Log: false},
 		"SyncCommitteeSubscriptionsSubmitter":   {Latency: true, Log: false},
+		"SyncCommitteeSelectionsProvider":       {Latency: true, Log: false},
 		"ValidatorsProvider":                    {Latency: true, Log: true},
 		"ValidatorRegistrationsSubmitter":       {Latency: true, Log: false},
 		"VoluntaryExitSubmitter":                {Latency: true, Log: false},
diff --git a/app/eth2wrap/httpwrap.go b/app/eth2wrap/httpwrap.go
index 938d1b64..f6be47eb 100644
--- a/app/eth2wrap/httpwrap.go
+++ b/app/eth2wrap/httpwrap.go
@@ -3,15 +3,8 @@
 package eth2wrap
 
 import (
-	"bytes"
 	"context"
 	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"strconv"
 	"sync"
 	"testing"
 	"time"
@@ -19,46 +12,12 @@ import (
 	"github.com/attestantio/go-eth2-client/api"
 	apiv1 "github.com/attestantio/go-eth2-client/api/v1"
 	eth2http "github.com/attestantio/go-eth2-client/http"
-	"github.com/attestantio/go-eth2-client/spec"
-	eth2e "github.com/attestantio/go-eth2-client/spec/electra"
 	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
 
 	"github.com/obolnetwork/charon/app/errors"
-	"github.com/obolnetwork/charon/app/z"
 	"github.com/obolnetwork/charon/eth2util"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
-	"github.com/obolnetwork/charon/eth2util/statecomm"
 )
 
-// BlockAttestationsProvider is the interface for providing attestations included in blocks.
-// It is a standard beacon API endpoint not implemented by eth2client.
-// See https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockAttestations.
-type BlockAttestationsProvider interface {
-	BlockAttestations(ctx context.Context, stateID string) ([]*spec.VersionedAttestation, error)
-}
-
-// BlockProvider is the interface for providing block details.
-// It is a standard beacon API endpoint not implemented by eth2client.
-// See https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2.
-type BlockProvider interface {
-	Block(ctx context.Context, stateID string) (*spec.VersionedSignedBeaconBlock, error)
-}
-
-// BeaconStateCommitteesProvider is the interface for providing committees for given slot.
-// It is a standard beacon API endpoint not implemented by eth2client.
-// See https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees.
-type BeaconStateCommitteesProvider interface {
-	BeaconStateCommittees(ctx context.Context, slot uint64) ([]*statecomm.StateCommittee, error)
-}
-
-// NodePeerCountProvider is the interface for providing node peer count.
-// It is a standard beacon API endpoint not implemented by eth2client.
-// See https://ethereum.github.io/beacon-APIs/#/Node/getPeerCount.
-type NodePeerCountProvider interface {
-	// NodePeerCount provides peer count of the beacon node.
-	NodePeerCount(ctx context.Context) (int, error)
-}
-
 // NewHTTPAdapterForT returns a http adapter for testing non-eth2service methods as it is nil.
 func NewHTTPAdapterForT(_ *testing.T, address string, headers map[string]string, timeout time.Duration) Client {
 	return newHTTPAdapter(nil, address, headers, timeout)
@@ -157,237 +116,6 @@ func (h *httpAdapter) Validators(ctx context.Context, opts *api.ValidatorsOpts)
 	return h.Service.Validators(reqCtx, opts)
 }
 
-// AggregateBeaconCommitteeSelections implements eth2exp.BeaconCommitteeSelectionAggregator.
-func (h *httpAdapter) AggregateBeaconCommitteeSelections(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	reqBody, err := json.Marshal(selections)
-	if err != nil {
-		return nil, errors.Wrap(err, "marshal submit beacon committee selections")
-	}
-
-	respBody, err := httpPost(ctx, h.address, "/eth/v1/validator/beacon_committee_selections", bytes.NewReader(reqBody), h.headers, h.timeout)
-	if err != nil {
-		return nil, errors.Wrap(err, "submit beacon committee selections")
-	}
-
-	var resp submitBeaconCommitteeSelectionsJSON
-	if err := json.Unmarshal(respBody, &resp); err != nil {
-		return nil, errors.Wrap(err, "failed to parse beacon committee selections response")
-	}
-
-	return resp.Data, nil
-}
-
-// AggregateSyncCommitteeSelections implements eth2exp.SyncCommitteeSelectionAggregator.
-func (h *httpAdapter) AggregateSyncCommitteeSelections(ctx context.Context, selections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-	reqBody, err := json.Marshal(selections)
-	if err != nil {
-		return nil, errors.Wrap(err, "marshal sync committee selections")
-	}
-
-	respBody, err := httpPost(ctx, h.address, "/eth/v1/validator/sync_committee_selections", bytes.NewReader(reqBody), h.headers, h.timeout)
-	if err != nil {
-		return nil, errors.Wrap(err, "submit sync committee selections")
-	}
-
-	var resp submitSyncCommitteeSelectionsJSON
-	if err := json.Unmarshal(respBody, &resp); err != nil {
-		return nil, errors.Wrap(err, "failed to parse sync committee selections response")
-	}
-
-	return resp.Data, nil
-}
-
-// BlockAttestations returns the attestations included in the requested block.
-// See https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockAttestationsV2.
-func (h *httpAdapter) BlockAttestations(ctx context.Context, stateID string) ([]*spec.VersionedAttestation, error) {
-	path := fmt.Sprintf("/eth/v2/beacon/blocks/%s/attestations", stateID)
-
-	ctx, cancel := context.WithTimeout(ctx, h.timeout)
-	defer cancel()
-
-	resp, err := httpGetRaw(ctx, h.address, path, h.headers, nil)
-	if err != nil {
-		return nil, errors.Wrap(err, "request block attestations")
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode == http.StatusNotFound {
-		return nil, nil // No block for slot, so no attestations.
-	} else if resp.StatusCode != http.StatusOK {
-		return nil, errors.New("request block attestations failed", z.Int("status", resp.StatusCode))
-	}
-
-	respBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, errors.Wrap(err, "request block attestations body")
-	}
-
-	version, err := fetchConsensusVersion(resp)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to get consensus version")
-	}
-
-	res := []*spec.VersionedAttestation{}
-	switch version {
-	case spec.DataVersionPhase0:
-		var respAttestation p0AttestationsJSON
-		if err := json.Unmarshal(respBody, &respAttestation); err != nil {
-			return nil, errors.Wrap(err, "failed to parse block attestations response")
-		}
-
-		for _, a := range respAttestation.Data {
-			res = append(res, &spec.VersionedAttestation{Version: version, Phase0: a})
-		}
-	case spec.DataVersionAltair:
-		var respAttestation p0AttestationsJSON
-		if err := json.Unmarshal(respBody, &respAttestation); err != nil {
-			return nil, errors.Wrap(err, "failed to parse block attestations response")
-		}
-
-		for _, a := range respAttestation.Data {
-			res = append(res, &spec.VersionedAttestation{Version: version, Altair: a})
-		}
-	case spec.DataVersionBellatrix:
-		var respAttestation p0AttestationsJSON
-		if err := json.Unmarshal(respBody, &respAttestation); err != nil {
-			return nil, errors.Wrap(err, "failed to parse block attestations response")
-		}
-
-		for _, a := range respAttestation.Data {
-			res = append(res, &spec.VersionedAttestation{Version: version, Bellatrix: a})
-		}
-	case spec.DataVersionCapella:
-		var respAttestation p0AttestationsJSON
-		if err := json.Unmarshal(respBody, &respAttestation); err != nil {
-			return nil, errors.Wrap(err, "failed to parse block attestations response")
-		}
-
-		for _, a := range respAttestation.Data {
-			res = append(res, &spec.VersionedAttestation{Version: version, Capella: a})
-		}
-	case spec.DataVersionDeneb:
-		var respAttestation p0AttestationsJSON
-		if err := json.Unmarshal(respBody, &respAttestation); err != nil {
-			return nil, errors.Wrap(err, "failed to parse block attestations response")
-		}
-
-		for _, a := range respAttestation.Data {
-			res = append(res, &spec.VersionedAttestation{Version: version, Deneb: a})
-		}
-	case spec.DataVersionElectra:
-		var respAttestation electraAttestationsJSON
-		if err := json.Unmarshal(respBody, &respAttestation); err != nil {
-			return nil, errors.Wrap(err, "failed to parse block attestations response")
-		}
-
-		for _, a := range respAttestation.Data {
-			res = append(res, &spec.VersionedAttestation{Version: version, Electra: a})
-		}
-	case spec.DataVersionUnknown:
-		return nil, errors.New("attestations data version unknown")
-	}
-
-	return res, nil
-}
-
-// Block returns the block details.
-// See https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2.
-func (h *httpAdapter) Block(ctx context.Context, stateID string) (*spec.VersionedSignedBeaconBlock, error) {
-	path := "/eth/v2/beacon/blocks/" + stateID
-
-	ctx, cancel := context.WithTimeout(ctx, h.timeout)
-	defer cancel()
-
-	resp, err := httpGetRaw(ctx, h.address, path, h.headers, nil)
-	if err != nil {
-		return nil, errors.Wrap(err, "request block")
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode == http.StatusNotFound {
-		return nil, nil //nolint:nilnil // No block for slot.
-	} else if resp.StatusCode != http.StatusOK {
-		return nil, errors.New("request block failed", z.Int("status", resp.StatusCode))
-	}
-
-	respBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, errors.Wrap(err, "request block attestations body")
-	}
-
-	res := spec.VersionedSignedBeaconBlock{}
-	if err := json.Unmarshal(respBody, &res); err != nil {
-		return nil, errors.Wrap(err, "failed to parse block response")
-	}
-
-	return &res, nil
-}
-
-// BeaconStateCommittees returns the attestations included in the requested block.
-// See https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidators.
-func (h *httpAdapter) BeaconStateCommittees(ctx context.Context, slot uint64) ([]*statecomm.StateCommittee, error) {
-	r := strconv.FormatUint(slot, 10)
-	path := fmt.Sprintf("/eth/v1/beacon/states/%v/committees", r)
-	queryParams := map[string]string{
-		"slot": strconv.FormatUint(slot, 10),
-	}
-
-	respBody, statusCode, err := httpGet(ctx, h.address, path, h.headers, queryParams, h.timeout)
-	if err != nil {
-		return nil, errors.Wrap(err, "request state committees for slot", z.Int("status", statusCode), z.U64("slot", slot))
-	}
-
-	if statusCode != http.StatusOK {
-		return nil, errors.New("request state committees for slot failed", z.Int("status", statusCode), z.U64("slot", slot))
-	}
-
-	var res statecomm.StateCommitteesResponse
-
-	err = json.Unmarshal(respBody, &res)
-	if err != nil {
-		return nil, errors.Wrap(err, "unmarshal state committees", z.Int("status", statusCode), z.U64("slot", slot))
-	}
-
-	return res.Data, nil
-}
-
-// ProposerConfig implements eth2exp.ProposerConfigProvider.
-func (h *httpAdapter) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
-	respBody, statusCode, err := httpGet(ctx, h.address, "/proposer_config", h.headers, nil, h.timeout)
-	if err != nil {
-		return nil, errors.Wrap(err, "submit sync committee selections")
-	} else if statusCode != http.StatusOK {
-		return nil, errors.New("request proposer config failed", z.Int("status", statusCode), z.Str("body", string(respBody)))
-	}
-
-	var resp eth2exp.ProposerConfigResponse
-	if err := json.Unmarshal(respBody, &resp); err != nil {
-		return nil, errors.Wrap(err, "failed to parse sync committee selections response")
-	}
-
-	return &resp, nil
-}
-
-// NodePeerCount provides the peer count of the beacon node.
-// See https://ethereum.github.io/beacon-APIs/#/Node/getPeerCount.
-func (h *httpAdapter) NodePeerCount(ctx context.Context) (int, error) {
-	const path = "/eth/v1/node/peer_count"
-
-	respBody, statusCode, err := httpGet(ctx, h.address, path, h.headers, nil, h.timeout)
-	if err != nil {
-		return 0, errors.Wrap(err, "request beacon node peer count")
-	} else if statusCode != http.StatusOK {
-		return 0, errors.New("request beacon node peer count failed", z.Int("status", statusCode), z.Str("body", string(respBody)))
-	}
-
-	var resp peerCountJSON
-	if err := json.Unmarshal(respBody, &resp); err != nil {
-		return 0, errors.Wrap(err, "failed to parse beacon node peer count response")
-	}
-
-	return resp.Data.Connected, nil
-}
-
 // Domain returns the signing domain for a given domain type.
 // After EIP-7044, the VOLUNTARY_EXIT domain must always return a domain relative to the Capella hardfork.
 // This method returns just that for that domain type, otherwise follows the standard go-eth2-client flow.
@@ -403,158 +131,3 @@ func (h *httpAdapter) Domain(ctx context.Context, domainType eth2p0.DomainType,
 
 	return h.Service.Domain(ctx, domainType, epoch)
 }
-
-type submitBeaconCommitteeSelectionsJSON struct {
-	Data []*eth2exp.BeaconCommitteeSelection `json:"data"`
-}
-
-type submitSyncCommitteeSelectionsJSON struct {
-	Data []*eth2exp.SyncCommitteeSelection `json:"data"`
-}
-
-type p0AttestationsJSON struct {
-	Data []*eth2p0.Attestation `json:"data"`
-}
-
-type electraAttestationsJSON struct {
-	Data []*eth2e.Attestation `json:"data"`
-}
-
-type peerCountJSON struct {
-	Data struct {
-		Connected int `json:"connected,string"`
-	} `json:"data"`
-}
-
-type responseMetadata struct {
-	Version spec.DataVersion `json:"version"`
-}
-
-func httpPost(ctx context.Context, base string, endpoint string, body io.Reader, headers map[string]string, timeout time.Duration) ([]byte, error) {
-	ctx, cancel := context.WithTimeout(ctx, timeout)
-	defer cancel()
-
-	addr, err := url.JoinPath(base, endpoint)
-	if err != nil {
-		return nil, errors.Wrap(err, "invalid address")
-	}
-
-	url, err := url.ParseRequestURI(addr)
-	if err != nil {
-		return nil, errors.Wrap(err, "invalid endpoint")
-	}
-
-	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url.String(), body)
-	if err != nil {
-		return nil, errors.Wrap(err, "new POST request with ctx")
-	}
-
-	for k, v := range headers {
-		req.Header.Add(k, v)
-	}
-
-	res, err := new(http.Client).Do(req)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to call POST endpoint")
-	}
-	defer res.Body.Close()
-
-	data, err := io.ReadAll(res.Body)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to read POST response")
-	}
-
-	if res.StatusCode/100 != 2 {
-		return nil, errors.New("post failed", z.Int("status", res.StatusCode), z.Str("body", string(data)))
-	}
-
-	return data, nil
-}
-
-// httpGetRaw performs a GET request and returns the raw http response or an error.
-func httpGetRaw(ctx context.Context, base string, endpoint string, headers map[string]string, queryParams map[string]string) (*http.Response, error) {
-	addr, err := url.JoinPath(base, endpoint)
-	if err != nil {
-		return nil, errors.Wrap(err, "invalid address")
-	}
-
-	u, err := url.ParseRequestURI(addr)
-	if err != nil {
-		return nil, errors.Wrap(err, "invalid endpoint")
-	}
-
-	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
-	if err != nil {
-		return nil, errors.Wrap(err, "new GET request with ctx")
-	}
-
-	for k, v := range headers {
-		req.Header.Add(k, v)
-	}
-
-	q := req.URL.Query()
-	for key, val := range queryParams {
-		q.Add(key, val)
-	}
-
-	req.URL.RawQuery = q.Encode()
-
-	res, err := new(http.Client).Do(req)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to call GET endpoint")
-	}
-
-	return res, nil
-}
-
-// httpGet performs a GET request and returns the body and status code or an error.
-func httpGet(ctx context.Context, base string, endpoint string, headers map[string]string, queryParams map[string]string, timeout time.Duration) ([]byte, int, error) {
-	ctx, cancel := context.WithTimeout(ctx, timeout)
-	defer cancel()
-
-	res, err := httpGetRaw(ctx, base, endpoint, headers, queryParams)
-	if err != nil {
-		return nil, 0, errors.Wrap(err, "failed to read GET response")
-	}
-	defer res.Body.Close()
-
-	data, err := io.ReadAll(res.Body)
-	if err != nil {
-		return nil, res.StatusCode, errors.Wrap(err, "failed to read GET response body")
-	}
-
-	return data, res.StatusCode, nil
-}
-
-// fetchConsensusVersion attempts to extract the consensus version from the beacon node http response.
-func fetchConsensusVersion(resp *http.Response) (spec.DataVersion, error) {
-	respConsensusVersions, exists := resp.Header["Eth-Consensus-Version"]
-	if !exists {
-		// No consensus version supplied in response; obtain it from the body if possible.
-		var metadata responseMetadata
-
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return spec.DataVersionUnknown, errors.Wrap(err, "read resp body")
-		}
-
-		if err := json.Unmarshal(body, &metadata); err != nil {
-			return spec.DataVersionUnknown, errors.Wrap(err, "no consensus version header and failed to parse response")
-		}
-
-		return metadata.Version, nil
-	}
-
-	if len(respConsensusVersions) != 1 {
-		return spec.DataVersionUnknown, errors.New("malformed consensus version", z.Int("entries", len(respConsensusVersions)))
-	}
-
-	var dataVersion spec.DataVersion
-
-	err := dataVersion.UnmarshalJSON([]byte(fmt.Sprintf("%q", respConsensusVersions[0])))
-	if err != nil {
-		return spec.DataVersionUnknown, errors.Wrap(err, "unmarshal consensus version header to data version")
-	}
-
-	return dataVersion, nil
-}
diff --git a/app/eth2wrap/lazy.go b/app/eth2wrap/lazy.go
index 51a436d5..08ea2633 100644
--- a/app/eth2wrap/lazy.go
+++ b/app/eth2wrap/lazy.go
@@ -6,11 +6,6 @@ import (
 	"context"
 	"sync"
 	"time"
-
-	"github.com/attestantio/go-eth2-client/spec"
-
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
-	"github.com/obolnetwork/charon/eth2util/statecomm"
 )
 
 //go:generate mockery --name=Client --output=mocks --outpkg=mocks --case=underscore
@@ -166,66 +161,3 @@ func (l *lazy) SetValidatorCache(valCache func(context.Context) (ActiveValidator
 		cl.SetValidatorCache(valCache)
 	}
 }
-
-func (l *lazy) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
-	cl, err := l.getOrCreateClient(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return cl.ProposerConfig(ctx)
-}
-
-func (l *lazy) AggregateBeaconCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	cl, err := l.getOrCreateClient(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return cl.AggregateBeaconCommitteeSelections(ctx, partialSelections)
-}
-
-func (l *lazy) AggregateSyncCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-	cl, err := l.getOrCreateClient(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return cl.AggregateSyncCommitteeSelections(ctx, partialSelections)
-}
-
-func (l *lazy) BlockAttestations(ctx context.Context, stateID string) ([]*spec.VersionedAttestation, error) {
-	cl, err := l.getOrCreateClient(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return cl.BlockAttestations(ctx, stateID)
-}
-
-func (l *lazy) Block(ctx context.Context, stateID string) (*spec.VersionedSignedBeaconBlock, error) {
-	cl, err := l.getOrCreateClient(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return cl.Block(ctx, stateID)
-}
-
-func (l *lazy) BeaconStateCommittees(ctx context.Context, slot uint64) ([]*statecomm.StateCommittee, error) {
-	cl, err := l.getOrCreateClient(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return cl.BeaconStateCommittees(ctx, slot)
-}
-
-func (l *lazy) NodePeerCount(ctx context.Context) (int, error) {
-	cl, err := l.getOrCreateClient(ctx)
-	if err != nil {
-		return 0, err
-	}
-
-	return cl.NodePeerCount(ctx)
-}
diff --git a/app/eth2wrap/mocks/client.go b/app/eth2wrap/mocks/client.go
index 839f509a..f7ab2197 100644
--- a/app/eth2wrap/mocks/client.go
+++ b/app/eth2wrap/mocks/client.go
@@ -10,8 +10,6 @@ import (
 
 	context "context"
 
-	eth2exp "github.com/obolnetwork/charon/eth2util/eth2exp"
-
 	eth2wrap "github.com/obolnetwork/charon/app/eth2wrap"
 
 	mock "github.com/stretchr/testify/mock"
@@ -20,8 +18,6 @@ import (
 
 	spec "github.com/attestantio/go-eth2-client/spec"
 
-	statecomm "github.com/obolnetwork/charon/eth2util/statecomm"
-
 	time "time"
 
 	v1 "github.com/attestantio/go-eth2-client/api/v1"
@@ -110,66 +106,6 @@ func (_m *Client) AggregateAttestation(ctx context.Context, opts *api.AggregateA
 	return r0, r1
 }
 
-// AggregateBeaconCommitteeSelections provides a mock function with given fields: ctx, partialSelections
-func (_m *Client) AggregateBeaconCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	ret := _m.Called(ctx, partialSelections)
-
-	if len(ret) == 0 {
-		panic("no return value specified for AggregateBeaconCommitteeSelections")
-	}
-
-	var r0 []*eth2exp.BeaconCommitteeSelection
-	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error)); ok {
-		return rf(ctx, partialSelections)
-	}
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.BeaconCommitteeSelection) []*eth2exp.BeaconCommitteeSelection); ok {
-		r0 = rf(ctx, partialSelections)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*eth2exp.BeaconCommitteeSelection)
-		}
-	}
-
-	if rf, ok := ret.Get(1).(func(context.Context, []*eth2exp.BeaconCommitteeSelection) error); ok {
-		r1 = rf(ctx, partialSelections)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
-// AggregateSyncCommitteeSelections provides a mock function with given fields: ctx, partialSelections
-func (_m *Client) AggregateSyncCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-	ret := _m.Called(ctx, partialSelections)
-
-	if len(ret) == 0 {
-		panic("no return value specified for AggregateSyncCommitteeSelections")
-	}
-
-	var r0 []*eth2exp.SyncCommitteeSelection
-	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error)); ok {
-		return rf(ctx, partialSelections)
-	}
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.SyncCommitteeSelection) []*eth2exp.SyncCommitteeSelection); ok {
-		r0 = rf(ctx, partialSelections)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*eth2exp.SyncCommitteeSelection)
-		}
-	}
-
-	if rf, ok := ret.Get(1).(func(context.Context, []*eth2exp.SyncCommitteeSelection) error); ok {
-		r1 = rf(ctx, partialSelections)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
 // AttestationData provides a mock function with given fields: ctx, opts
 func (_m *Client) AttestationData(ctx context.Context, opts *api.AttestationDataOpts) (*api.Response[*phase0.AttestationData], error) {
 	ret := _m.Called(ctx, opts)
@@ -230,28 +166,28 @@ func (_m *Client) AttesterDuties(ctx context.Context, opts *api.AttesterDutiesOp
 	return r0, r1
 }
 
-// BeaconBlockRoot provides a mock function with given fields: ctx, opts
-func (_m *Client) BeaconBlockRoot(ctx context.Context, opts *api.BeaconBlockRootOpts) (*api.Response[*phase0.Root], error) {
+// BeaconBlockAttestations provides a mock function with given fields: ctx, opts
+func (_m *Client) BeaconBlockAttestations(ctx context.Context, opts *api.BeaconBlockAttestationsOpts) (*api.Response[[]*spec.VersionedAttestation], error) {
 	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
-		panic("no return value specified for BeaconBlockRoot")
+		panic("no return value specified for BeaconBlockAttestations")
 	}
 
-	var r0 *api.Response[*phase0.Root]
+	var r0 *api.Response[[]*spec.VersionedAttestation]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconBlockRootOpts) (*api.Response[*phase0.Root], error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconBlockAttestationsOpts) (*api.Response[[]*spec.VersionedAttestation], error)); ok {
 		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconBlockRootOpts) *api.Response[*phase0.Root]); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconBlockAttestationsOpts) *api.Response[[]*spec.VersionedAttestation]); ok {
 		r0 = rf(ctx, opts)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*api.Response[*phase0.Root])
+			r0 = ret.Get(0).(*api.Response[[]*spec.VersionedAttestation])
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *api.BeaconBlockRootOpts) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *api.BeaconBlockAttestationsOpts) error); ok {
 		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
@@ -260,29 +196,29 @@ func (_m *Client) BeaconBlockRoot(ctx context.Context, opts *api.BeaconBlockRoot
 	return r0, r1
 }
 
-// BeaconStateCommittees provides a mock function with given fields: ctx, slot
-func (_m *Client) BeaconStateCommittees(ctx context.Context, slot uint64) ([]*statecomm.StateCommittee, error) {
-	ret := _m.Called(ctx, slot)
+// BeaconBlockRoot provides a mock function with given fields: ctx, opts
+func (_m *Client) BeaconBlockRoot(ctx context.Context, opts *api.BeaconBlockRootOpts) (*api.Response[*phase0.Root], error) {
+	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
-		panic("no return value specified for BeaconStateCommittees")
+		panic("no return value specified for BeaconBlockRoot")
 	}
 
-	var r0 []*statecomm.StateCommittee
+	var r0 *api.Response[*phase0.Root]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, uint64) ([]*statecomm.StateCommittee, error)); ok {
-		return rf(ctx, slot)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconBlockRootOpts) (*api.Response[*phase0.Root], error)); ok {
+		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, uint64) []*statecomm.StateCommittee); ok {
-		r0 = rf(ctx, slot)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconBlockRootOpts) *api.Response[*phase0.Root]); ok {
+		r0 = rf(ctx, opts)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*statecomm.StateCommittee)
+			r0 = ret.Get(0).(*api.Response[*phase0.Root])
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok {
-		r1 = rf(ctx, slot)
+	if rf, ok := ret.Get(1).(func(context.Context, *api.BeaconBlockRootOpts) error); ok {
+		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -290,29 +226,29 @@ func (_m *Client) BeaconStateCommittees(ctx context.Context, slot uint64) ([]*st
 	return r0, r1
 }
 
-// Block provides a mock function with given fields: ctx, stateID
-func (_m *Client) Block(ctx context.Context, stateID string) (*spec.VersionedSignedBeaconBlock, error) {
-	ret := _m.Called(ctx, stateID)
+// BeaconCommitteeSelections provides a mock function with given fields: ctx, opts
+func (_m *Client) BeaconCommitteeSelections(ctx context.Context, opts *api.BeaconCommitteeSelectionsOpts) (*api.Response[[]*v1.BeaconCommitteeSelection], error) {
+	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
-		panic("no return value specified for Block")
+		panic("no return value specified for BeaconCommitteeSelections")
 	}
 
-	var r0 *spec.VersionedSignedBeaconBlock
+	var r0 *api.Response[[]*v1.BeaconCommitteeSelection]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, string) (*spec.VersionedSignedBeaconBlock, error)); ok {
-		return rf(ctx, stateID)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconCommitteeSelectionsOpts) (*api.Response[[]*v1.BeaconCommitteeSelection], error)); ok {
+		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, string) *spec.VersionedSignedBeaconBlock); ok {
-		r0 = rf(ctx, stateID)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconCommitteeSelectionsOpts) *api.Response[[]*v1.BeaconCommitteeSelection]); ok {
+		r0 = rf(ctx, opts)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*spec.VersionedSignedBeaconBlock)
+			r0 = ret.Get(0).(*api.Response[[]*v1.BeaconCommitteeSelection])
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
-		r1 = rf(ctx, stateID)
+	if rf, ok := ret.Get(1).(func(context.Context, *api.BeaconCommitteeSelectionsOpts) error); ok {
+		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -320,29 +256,29 @@ func (_m *Client) Block(ctx context.Context, stateID string) (*spec.VersionedSig
 	return r0, r1
 }
 
-// BlockAttestations provides a mock function with given fields: ctx, stateID
-func (_m *Client) BlockAttestations(ctx context.Context, stateID string) ([]*spec.VersionedAttestation, error) {
-	ret := _m.Called(ctx, stateID)
+// BeaconCommittees provides a mock function with given fields: ctx, opts
+func (_m *Client) BeaconCommittees(ctx context.Context, opts *api.BeaconCommitteesOpts) (*api.Response[[]*v1.BeaconCommittee], error) {
+	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
-		panic("no return value specified for BlockAttestations")
+		panic("no return value specified for BeaconCommittees")
 	}
 
-	var r0 []*spec.VersionedAttestation
+	var r0 *api.Response[[]*v1.BeaconCommittee]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, string) ([]*spec.VersionedAttestation, error)); ok {
-		return rf(ctx, stateID)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconCommitteesOpts) (*api.Response[[]*v1.BeaconCommittee], error)); ok {
+		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, string) []*spec.VersionedAttestation); ok {
-		r0 = rf(ctx, stateID)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconCommitteesOpts) *api.Response[[]*v1.BeaconCommittee]); ok {
+		r0 = rf(ctx, opts)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*spec.VersionedAttestation)
+			r0 = ret.Get(0).(*api.Response[[]*v1.BeaconCommittee])
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
-		r1 = rf(ctx, stateID)
+	if rf, ok := ret.Get(1).(func(context.Context, *api.BeaconCommitteesOpts) error); ok {
+		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -614,27 +550,29 @@ func (_m *Client) Name() string {
 	return r0
 }
 
-// NodePeerCount provides a mock function with given fields: ctx
-func (_m *Client) NodePeerCount(ctx context.Context) (int, error) {
-	ret := _m.Called(ctx)
+// NodePeerCount provides a mock function with given fields: ctx, opts
+func (_m *Client) NodePeerCount(ctx context.Context, opts *api.NodePeerCountOpts) (*api.Response[*v1.PeerCount], error) {
+	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
 		panic("no return value specified for NodePeerCount")
 	}
 
-	var r0 int
+	var r0 *api.Response[*v1.PeerCount]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context) (int, error)); ok {
-		return rf(ctx)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.NodePeerCountOpts) (*api.Response[*v1.PeerCount], error)); ok {
+		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context) int); ok {
-		r0 = rf(ctx)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.NodePeerCountOpts) *api.Response[*v1.PeerCount]); ok {
+		r0 = rf(ctx, opts)
 	} else {
-		r0 = ret.Get(0).(int)
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*api.Response[*v1.PeerCount])
+		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context) error); ok {
-		r1 = rf(ctx)
+	if rf, ok := ret.Get(1).(func(context.Context, *api.NodePeerCountOpts) error); ok {
+		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -732,36 +670,6 @@ func (_m *Client) Proposal(ctx context.Context, opts *api.ProposalOpts) (*api.Re
 	return r0, r1
 }
 
-// ProposerConfig provides a mock function with given fields: ctx
-func (_m *Client) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
-	ret := _m.Called(ctx)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ProposerConfig")
-	}
-
-	var r0 *eth2exp.ProposerConfigResponse
-	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context) (*eth2exp.ProposerConfigResponse, error)); ok {
-		return rf(ctx)
-	}
-	if rf, ok := ret.Get(0).(func(context.Context) *eth2exp.ProposerConfigResponse); ok {
-		r0 = rf(ctx)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*eth2exp.ProposerConfigResponse)
-		}
-	}
-
-	if rf, ok := ret.Get(1).(func(context.Context) error); ok {
-		r1 = rf(ctx)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
 // ProposerDuties provides a mock function with given fields: ctx, opts
 func (_m *Client) ProposerDuties(ctx context.Context, opts *api.ProposerDutiesOpts) (*api.Response[[]*v1.ProposerDuty], error) {
 	ret := _m.Called(ctx, opts)
@@ -1176,6 +1084,36 @@ func (_m *Client) SyncCommitteeDuties(ctx context.Context, opts *api.SyncCommitt
 	return r0, r1
 }
 
+// SyncCommitteeSelections provides a mock function with given fields: ctx, opts
+func (_m *Client) SyncCommitteeSelections(ctx context.Context, opts *api.SyncCommitteeSelectionsOpts) (*api.Response[[]*v1.SyncCommitteeSelection], error) {
+	ret := _m.Called(ctx, opts)
+
+	if len(ret) == 0 {
+		panic("no return value specified for SyncCommitteeSelections")
+	}
+
+	var r0 *api.Response[[]*v1.SyncCommitteeSelection]
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *api.SyncCommitteeSelectionsOpts) (*api.Response[[]*v1.SyncCommitteeSelection], error)); ok {
+		return rf(ctx, opts)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *api.SyncCommitteeSelectionsOpts) *api.Response[[]*v1.SyncCommitteeSelection]); ok {
+		r0 = rf(ctx, opts)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*api.Response[[]*v1.SyncCommitteeSelection])
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *api.SyncCommitteeSelectionsOpts) error); ok {
+		r1 = rf(ctx, opts)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // Validators provides a mock function with given fields: ctx, opts
 func (_m *Client) Validators(ctx context.Context, opts *api.ValidatorsOpts) (*api.Response[map[phase0.ValidatorIndex]*v1.Validator], error) {
 	ret := _m.Called(ctx, opts)
diff --git a/app/eth2wrap/multi.go b/app/eth2wrap/multi.go
index b3868297..bc159e0f 100644
--- a/app/eth2wrap/multi.go
+++ b/app/eth2wrap/multi.go
@@ -4,11 +4,6 @@ package eth2wrap
 
 import (
 	"context"
-
-	"github.com/attestantio/go-eth2-client/spec"
-
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
-	"github.com/obolnetwork/charon/eth2util/statecomm"
 )
 
 // NewMultiForT creates a new mutil client for testing.
@@ -124,137 +119,3 @@ func (m multi) CompleteValidators(ctx context.Context) (CompleteValidators, erro
 
 	return res0, err
 }
-
-func (m multi) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
-	const label = "proposer_config"
-	defer latency(ctx, label, false)()
-	defer incRequest(label)
-
-	res0, err := provide(ctx, m.clients, m.fallbacks,
-		func(ctx context.Context, args provideArgs) (*eth2exp.ProposerConfigResponse, error) {
-			return args.client.ProposerConfig(ctx)
-		},
-		nil, m.selector,
-	)
-	if err != nil {
-		incError(label)
-		err = wrapError(ctx, err, label)
-	}
-
-	return res0, err
-}
-
-func (m multi) AggregateBeaconCommitteeSelections(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	const label = "aggregate_beacon_committee_selections"
-	defer latency(ctx, label, false)()
-	defer incRequest(label)
-
-	res0, err := provide(ctx, m.clients, m.fallbacks,
-		func(ctx context.Context, args provideArgs) ([]*eth2exp.BeaconCommitteeSelection, error) {
-			return args.client.AggregateBeaconCommitteeSelections(ctx, selections)
-		},
-		nil, m.selector,
-	)
-	if err != nil {
-		incError(label)
-		err = wrapError(ctx, err, label)
-	}
-
-	return res0, err
-}
-
-func (m multi) AggregateSyncCommitteeSelections(ctx context.Context, selections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-	const label = "aggregate_sync_committee_selections"
-	defer latency(ctx, label, false)()
-	defer incRequest(label)
-
-	res, err := provide(ctx, m.clients, m.fallbacks,
-		func(ctx context.Context, args provideArgs) ([]*eth2exp.SyncCommitteeSelection, error) {
-			return args.client.AggregateSyncCommitteeSelections(ctx, selections)
-		},
-
-		nil, m.selector,
-	)
-	if err != nil {
-		incError(label)
-		err = wrapError(ctx, err, label)
-	}
-
-	return res, err
-}
-
-func (m multi) BlockAttestations(ctx context.Context, stateID string) ([]*spec.VersionedAttestation, error) {
-	const label = "block_attestations_v2"
-	defer latency(ctx, label, false)()
-	defer incRequest(label)
-
-	res, err := provide(ctx, m.clients, m.fallbacks,
-		func(ctx context.Context, args provideArgs) ([]*spec.VersionedAttestation, error) {
-			return args.client.BlockAttestations(ctx, stateID)
-		},
-		nil, m.selector,
-	)
-	if err != nil {
-		incError(label)
-		err = wrapError(ctx, err, label)
-	}
-
-	return res, err
-}
-
-func (m multi) Block(ctx context.Context, stateID string) (*spec.VersionedSignedBeaconBlock, error) {
-	const label = "block_v2"
-	defer latency(ctx, label, false)()
-	defer incRequest(label)
-
-	res, err := provide(ctx, m.clients, m.fallbacks,
-		func(ctx context.Context, args provideArgs) (*spec.VersionedSignedBeaconBlock, error) {
-			return args.client.Block(ctx, stateID)
-		},
-		nil, m.selector,
-	)
-	if err != nil {
-		incError(label)
-		err = wrapError(ctx, err, label)
-	}
-
-	return res, err
-}
-
-func (m multi) BeaconStateCommittees(ctx context.Context, slot uint64) ([]*statecomm.StateCommittee, error) {
-	const label = "beacon_state_committees"
-	defer latency(ctx, label, false)()
-	defer incRequest(label)
-
-	res, err := provide(ctx, m.clients, m.fallbacks,
-		func(ctx context.Context, args provideArgs) ([]*statecomm.StateCommittee, error) {
-			return args.client.BeaconStateCommittees(ctx, slot)
-		},
-		nil, m.selector,
-	)
-	if err != nil {
-		incError(label)
-		err = wrapError(ctx, err, label)
-	}
-
-	return res, err
-}
-
-func (m multi) NodePeerCount(ctx context.Context) (int, error) {
-	const label = "node_peer_count"
-	defer latency(ctx, label, false)()
-	defer incRequest(label)
-
-	res, err := provide(ctx, m.clients, m.fallbacks,
-		func(ctx context.Context, args provideArgs) (int, error) {
-			return args.client.NodePeerCount(ctx)
-		},
-		nil, m.selector,
-	)
-	if err != nil {
-		incError(label)
-		err = wrapError(ctx, err, label)
-	}
-
-	return res, err
-}
diff --git a/app/monitoringapi.go b/app/monitoringapi.go
index de3dbc78..cc33e24a 100644
--- a/app/monitoringapi.go
+++ b/app/monitoringapi.go
@@ -154,7 +154,7 @@ func startReadyChecker(ctx context.Context, p2pNode host.Host, eth2Cl eth2wrap.C
 		ticker := clock.NewTicker(slotDuration)
 		peerCountTicker := clock.NewTicker(1 * time.Minute)
 
-		var bnPeerCount *int // Beacon node peer count value which is queried every minute
+		var bnPeerCount *uint64 // Beacon node peer count value which is queried every minute
 
 		currVAPICount := 0
 		prevVAPICount := 1 // Assume connected.
@@ -167,18 +167,18 @@ func startReadyChecker(ctx context.Context, p2pNode host.Host, eth2Cl eth2wrap.C
 
 		// beaconNodePeerCount queries beacon node peer count and sets the peer count gauge if err is nil.
 		beaconNodePeerCount := func() {
-			peerCount, err := eth2Cl.NodePeerCount(ctx)
+			peerCount, err := eth2Cl.NodePeerCount(ctx, &eth2api.NodePeerCountOpts{})
 			if err != nil {
 				log.Warn(ctx, "Failed to get beacon node peer count", err)
 				return
 			}
 
 			if bnPeerCount == nil {
-				bnPeerCount = new(int)
+				bnPeerCount = new(uint64)
 			}
 
-			bnPeerCount = &peerCount
-			beaconNodePeerCountGauge.Set(float64(peerCount))
+			bnPeerCount = &peerCount.Data.Connected
+			beaconNodePeerCountGauge.Set(float64(peerCount.Data.Connected))
 		}
 
 		for {
diff --git a/core/consensus/mocks/debugger.go b/core/consensus/mocks/debugger.go
index a1711c08..02c99235 100644
--- a/core/consensus/mocks/debugger.go
+++ b/core/consensus/mocks/debugger.go
@@ -1,6 +1,6 @@
 // Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
 
-// Code generated by mockery v2.42.1. DO NOT EDIT.
+// Code generated by mockery v2.53.3. DO NOT EDIT.
 
 package mocks
 
diff --git a/core/fetcher/fetcher.go b/core/fetcher/fetcher.go
index 52f72536..502d34a3 100644
--- a/core/fetcher/fetcher.go
+++ b/core/fetcher/fetcher.go
@@ -324,7 +324,7 @@ func (f *Fetcher) fetchContributionData(ctx context.Context, slot uint64, defSet
 			return core.UnsignedDataSet{}, errors.New("invalid sync committee selection")
 		}
 
-		subcommIdx := uint64(selection.SubcommitteeIndex)
+		subcommIdx := selection.SubcommitteeIndex
 
 		// Check if the validator is an aggregator for the sync committee.
 		ok, err = eth2exp.IsSyncCommAggregator(ctx, f.eth2Cl, selection.SelectionProof)
diff --git a/core/mocks/consensus.go b/core/mocks/consensus.go
index 738bef7b..424c90a8 100644
--- a/core/mocks/consensus.go
+++ b/core/mocks/consensus.go
@@ -1,6 +1,6 @@
 // Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
 
-// Code generated by mockery v2.42.1. DO NOT EDIT.
+// Code generated by mockery v2.53.3. DO NOT EDIT.
 
 package mocks
 
@@ -54,7 +54,7 @@ func (_m *Consensus) Propose(_a0 context.Context, _a1 core.Duty, _a2 core.Unsign
 	return r0
 }
 
-// ProtocolID provides a mock function with given fields:
+// ProtocolID provides a mock function with no fields
 func (_m *Consensus) ProtocolID() protocol.ID {
 	ret := _m.Called()
 
diff --git a/core/mocks/deadliner.go b/core/mocks/deadliner.go
index 9a548e14..28923d91 100644
--- a/core/mocks/deadliner.go
+++ b/core/mocks/deadliner.go
@@ -1,6 +1,6 @@
 // Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
 
-// Code generated by mockery v2.42.1. DO NOT EDIT.
+// Code generated by mockery v2.53.3. DO NOT EDIT.
 
 package mocks
 
@@ -32,7 +32,7 @@ func (_m *Deadliner) Add(duty core.Duty) bool {
 	return r0
 }
 
-// C provides a mock function with given fields:
+// C provides a mock function with no fields
 func (_m *Deadliner) C() <-chan core.Duty {
 	ret := _m.Called()
 
diff --git a/core/signeddata.go b/core/signeddata.go
index 99967ebc..9454297f 100644
--- a/core/signeddata.go
+++ b/core/signeddata.go
@@ -26,7 +26,6 @@ import (
 	"github.com/obolnetwork/charon/app/log"
 	"github.com/obolnetwork/charon/app/z"
 	"github.com/obolnetwork/charon/eth2util"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/eth2util/signing"
 )
 
@@ -1163,14 +1162,14 @@ func (s SignedRandao) clone() (SignedRandao, error) {
 }
 
 // NewBeaconCommitteeSelection is a convenience function which returns new signed BeaconCommitteeSelection.
-func NewBeaconCommitteeSelection(selection *eth2exp.BeaconCommitteeSelection) BeaconCommitteeSelection {
+func NewBeaconCommitteeSelection(selection *eth2v1.BeaconCommitteeSelection) BeaconCommitteeSelection {
 	return BeaconCommitteeSelection{
 		BeaconCommitteeSelection: *selection,
 	}
 }
 
 // NewPartialSignedBeaconCommitteeSelection is a convenience function which returns new partially signed BeaconCommitteeSelection.
-func NewPartialSignedBeaconCommitteeSelection(selection *eth2exp.BeaconCommitteeSelection, shareIdx int) ParSignedData {
+func NewPartialSignedBeaconCommitteeSelection(selection *eth2v1.BeaconCommitteeSelection, shareIdx int) ParSignedData {
 	return ParSignedData{
 		SignedData: NewBeaconCommitteeSelection(selection),
 		ShareIdx:   shareIdx,
@@ -1179,7 +1178,7 @@ func NewPartialSignedBeaconCommitteeSelection(selection *eth2exp.BeaconCommittee
 
 // BeaconCommitteeSelection wraps a BeaconCommitteeSelection which implements SignedData.
 type BeaconCommitteeSelection struct {
-	eth2exp.BeaconCommitteeSelection
+	eth2v1.BeaconCommitteeSelection
 }
 
 func (s BeaconCommitteeSelection) MessageRoot() ([32]byte, error) {
@@ -1225,14 +1224,14 @@ func (s *BeaconCommitteeSelection) UnmarshalJSON(input []byte) error {
 }
 
 // NewSyncCommitteeSelection is a convenience function which returns new signed SyncCommitteeSelection.
-func NewSyncCommitteeSelection(selection *eth2exp.SyncCommitteeSelection) SyncCommitteeSelection {
+func NewSyncCommitteeSelection(selection *eth2v1.SyncCommitteeSelection) SyncCommitteeSelection {
 	return SyncCommitteeSelection{
 		SyncCommitteeSelection: *selection,
 	}
 }
 
 // NewPartialSignedSyncCommitteeSelection is a convenience function which returns new partially signed SyncCommitteeSelection.
-func NewPartialSignedSyncCommitteeSelection(selection *eth2exp.SyncCommitteeSelection, shareIdx int) ParSignedData {
+func NewPartialSignedSyncCommitteeSelection(selection *eth2v1.SyncCommitteeSelection, shareIdx int) ParSignedData {
 	return ParSignedData{
 		SignedData: NewSyncCommitteeSelection(selection),
 		ShareIdx:   shareIdx,
@@ -1241,7 +1240,7 @@ func NewPartialSignedSyncCommitteeSelection(selection *eth2exp.SyncCommitteeSele
 
 // SyncCommitteeSelection wraps an eth2exp.SyncCommitteeSelection and implements SignedData.
 type SyncCommitteeSelection struct {
-	eth2exp.SyncCommitteeSelection
+	eth2v1.SyncCommitteeSelection
 }
 
 // MessageRoot returns the signing roots for the provided SyncCommitteeSelection.
@@ -1249,7 +1248,7 @@ type SyncCommitteeSelection struct {
 func (s SyncCommitteeSelection) MessageRoot() ([32]byte, error) {
 	data := altair.SyncAggregatorSelectionData{
 		Slot:              s.Slot,
-		SubcommitteeIndex: uint64(s.SubcommitteeIndex),
+		SubcommitteeIndex: s.SubcommitteeIndex,
 	}
 
 	return data.HashTreeRoot()
diff --git a/core/tracker/inclusion.go b/core/tracker/inclusion.go
index a5acb963..3b198e4b 100644
--- a/core/tracker/inclusion.go
+++ b/core/tracker/inclusion.go
@@ -23,7 +23,6 @@ import (
 	"github.com/obolnetwork/charon/app/log"
 	"github.com/obolnetwork/charon/app/z"
 	"github.com/obolnetwork/charon/core"
-	"github.com/obolnetwork/charon/eth2util/statecomm"
 )
 
 const (
@@ -59,7 +58,7 @@ type block struct {
 	Slot                   uint64
 	AttDuties              []*eth2v1.AttesterDuty
 	AttestationsByDataRoot map[eth2p0.Root]*eth2spec.VersionedAttestation
-	BeaconCommitees        []*statecomm.StateCommittee
+	BeaconCommitees        []*eth2v1.BeaconCommittee
 }
 
 // attCommittee is a versioned attestation with its aggregation bits mapped to the respective beacon committee
@@ -74,9 +73,9 @@ type trackerInclFunc func(core.Duty, core.PubKey, core.SignedData, error)
 // inclusionCore tracks the inclusion of submitted duties.
 // It has a simplified API to allow for easy testing.
 type inclusionCore struct {
-	mu              sync.Mutex
-	submissions     map[subkey]submission
-	stateCommittees map[eth2p0.Slot][]*statecomm.StateCommittee
+	mu               sync.Mutex
+	submissions      map[subkey]submission
+	beaconCommittees map[eth2p0.Slot][]*eth2v1.BeaconCommittee
 
 	trackerInclFunc trackerInclFunc
 	missedFunc      func(context.Context, submission)
@@ -230,6 +229,15 @@ func (i *inclusionCore) Trim(ctx context.Context, slot uint64) {
 
 		delete(i.submissions, key)
 	}
+
+	// Trim state committees
+	for key := range i.stateCommittees {
+		if uint64(key) > slot {
+			continue
+		}
+
+		delete(i.stateCommittees, key)
+	}
 }
 
 // CheckBlock checks whether the block includes any of the submitted duties.
@@ -338,7 +346,7 @@ func (i *inclusionCore) CheckBlockAndAtts(ctx context.Context, block block) {
 
 	// Delete
 	if block.Slot >= InclMissedLag {
-		delete(i.stateCommittees, eth2p0.Slot(block.Slot-InclMissedLag))
+		delete(i.beaconCommittees, eth2p0.Slot(block.Slot-InclMissedLag))
 	}
 }
 
@@ -528,11 +536,11 @@ func NewInclusion(ctx context.Context, eth2Cl eth2wrap.Client, trackerInclFunc t
 	}
 
 	inclCore := &inclusionCore{
-		attIncludedFunc: reportAttInclusion,
-		missedFunc:      reportMissed,
-		trackerInclFunc: trackerInclFunc,
-		submissions:     make(map[subkey]submission),
-		stateCommittees: make(map[eth2p0.Slot][]*statecomm.StateCommittee),
+		attIncludedFunc:  reportAttInclusion,
+		missedFunc:       reportMissed,
+		trackerInclFunc:  trackerInclFunc,
+		submissions:      make(map[subkey]submission),
+		beaconCommittees: make(map[eth2p0.Slot][]*eth2v1.BeaconCommittee),
 	}
 
 	return &InclusionChecker{
@@ -651,11 +659,13 @@ func (a *InclusionChecker) checkBlock(ctx context.Context, slot uint64, attDutie
 		return a.checkBlockAndAtts(ctx, slot, attDuties)
 	}
 
-	block, err := a.eth2Cl.Block(ctx, strconv.FormatUint(slot, 10))
+	eth2Resp, err := a.eth2Cl.SignedBeaconBlock(ctx, &eth2api.SignedBeaconBlockOpts{Block: strconv.FormatUint(slot, 10)})
 	if err != nil {
 		return err
 	}
 
+	block := eth2Resp.Data
+
 	var found bool
 	if block != nil {
 		found = true
@@ -669,15 +679,17 @@ func (a *InclusionChecker) checkBlock(ctx context.Context, slot uint64, attDutie
 }
 
 func (a *InclusionChecker) checkBlockAndAtts(ctx context.Context, slot uint64, attDuties []*eth2v1.AttesterDuty) error {
-	atts, err := a.eth2Cl.BlockAttestations(ctx, strconv.FormatUint(slot, 10))
+	attsResp, err := a.eth2Cl.BeaconBlockAttestations(ctx, &eth2api.BeaconBlockAttestationsOpts{Block: strconv.FormatUint(slot, 10)})
 	if err != nil {
 		return err
-	} else if len(atts) == 0 {
+	} else if len(attsResp.Data) == 0 {
 		return nil // No block for this slot
 	}
 
+	atts := attsResp.Data
+
 	var (
-		committeesForState []*statecomm.StateCommittee
+		committeesForState []*eth2v1.BeaconCommittee
 		checkedSlots       []eth2p0.Slot
 	)
 
@@ -692,18 +704,20 @@ func (a *InclusionChecker) checkBlockAndAtts(ctx context.Context, slot uint64, a
 			continue
 		}
 
-		stateComms, ok := a.core.stateCommittees[attestationData.Slot]
+		beaconComms, ok := a.core.beaconCommittees[attestationData.Slot]
 		if ok {
-			committeesForState = append(committeesForState, stateComms...)
+			committeesForState = append(committeesForState, beaconComms...)
 		} else {
 			// Get the beacon committee for the above mentioned slot.
-			fetchedCommitteesForState, err := a.eth2Cl.BeaconStateCommittees(ctx, uint64(attestationData.Slot))
+			eth2Resp, err := a.eth2Cl.BeaconCommittees(ctx, &eth2api.BeaconCommitteesOpts{State: fmt.Sprintf("%v", attestationData.Slot)})
 			if err != nil {
 				return err
 			}
 
+			fetchedCommitteesForState := eth2Resp.Data
+
 			committeesForState = append(committeesForState, fetchedCommitteesForState...)
-			a.core.stateCommittees[attestationData.Slot] = fetchedCommitteesForState
+			a.core.beaconCommittees[attestationData.Slot] = fetchedCommitteesForState
 		}
 
 		checkedSlots = append(checkedSlots, attestationData.Slot)
@@ -888,7 +902,7 @@ func setAttestationAggregationBits(att eth2spec.VersionedAttestation, bits bitfi
 	}
 }
 
-func conjugateAggregationBits(att *attCommittee, attsMap map[eth2p0.Root]*attCommittee, root eth2p0.Root, committeesForState []*statecomm.StateCommittee) (map[eth2p0.CommitteeIndex]bitfield.Bitlist, error) {
+func conjugateAggregationBits(att *attCommittee, attsMap map[eth2p0.Root]*attCommittee, root eth2p0.Root, committeesForState []*eth2v1.BeaconCommittee) (map[eth2p0.CommitteeIndex]bitfield.Bitlist, error) {
 	switch att.Attestation.Version {
 	case eth2spec.DataVersionPhase0:
 		if att.Attestation.Phase0 == nil {
@@ -957,7 +971,7 @@ func conjugateAggregationBitsPhase0(att *attCommittee, attsMap map[eth2p0.Root]*
 	return nil
 }
 
-func conjugateAggregationBitsElectra(att *attCommittee, attsMap map[eth2p0.Root]*attCommittee, root eth2p0.Root, committeesForState []*statecomm.StateCommittee) (map[eth2p0.CommitteeIndex]bitfield.Bitlist, error) {
+func conjugateAggregationBitsElectra(att *attCommittee, attsMap map[eth2p0.Root]*attCommittee, root eth2p0.Root, committeesForState []*eth2v1.BeaconCommittee) (map[eth2p0.CommitteeIndex]bitfield.Bitlist, error) {
 	fullAttestationAggregationBits, err := att.Attestation.AggregationBits()
 	if err != nil {
 		return nil, err
diff --git a/core/validatorapi/eth2types.go b/core/validatorapi/eth2types.go
index a070ffe9..d4291d64 100644
--- a/core/validatorapi/eth2types.go
+++ b/core/validatorapi/eth2types.go
@@ -13,7 +13,6 @@ import (
 	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
 
 	"github.com/obolnetwork/charon/app/errors"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 )
 
 // errorResponse an error response from the beacon-node api.
@@ -99,12 +98,12 @@ type aggregateAttestationV2Response struct {
 	Data    any    `json:"data"`
 }
 
-type aggregateBeaconCommitteeSelectionsJSON struct {
-	Data []*eth2exp.BeaconCommitteeSelection `json:"data"`
+type beaconCommitteeSelectionsJSON struct {
+	Data []*eth2v1.BeaconCommitteeSelection `json:"data"`
 }
 
-type aggregateSyncCommitteeSelectionsJSON struct {
-	Data []*eth2exp.SyncCommitteeSelection `json:"data"`
+type syncCommitteeSelectionsJSON struct {
+	Data []*eth2v1.SyncCommitteeSelection `json:"data"`
 }
 
 // root wraps eth2p0 root adding proper json marshalling.
diff --git a/core/validatorapi/mocks/handler.go b/core/validatorapi/mocks/handler.go
index 87b8c026..6d0b7f14 100644
--- a/core/validatorapi/mocks/handler.go
+++ b/core/validatorapi/mocks/handler.go
@@ -10,8 +10,6 @@ import (
 
 	context "context"
 
-	eth2exp "github.com/obolnetwork/charon/eth2util/eth2exp"
-
 	mock "github.com/stretchr/testify/mock"
 
 	phase0 "github.com/attestantio/go-eth2-client/spec/phase0"
@@ -56,66 +54,6 @@ func (_m *Handler) AggregateAttestation(ctx context.Context, opts *api.Aggregate
 	return r0, r1
 }
 
-// AggregateBeaconCommitteeSelections provides a mock function with given fields: ctx, partialSelections
-func (_m *Handler) AggregateBeaconCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	ret := _m.Called(ctx, partialSelections)
-
-	if len(ret) == 0 {
-		panic("no return value specified for AggregateBeaconCommitteeSelections")
-	}
-
-	var r0 []*eth2exp.BeaconCommitteeSelection
-	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error)); ok {
-		return rf(ctx, partialSelections)
-	}
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.BeaconCommitteeSelection) []*eth2exp.BeaconCommitteeSelection); ok {
-		r0 = rf(ctx, partialSelections)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*eth2exp.BeaconCommitteeSelection)
-		}
-	}
-
-	if rf, ok := ret.Get(1).(func(context.Context, []*eth2exp.BeaconCommitteeSelection) error); ok {
-		r1 = rf(ctx, partialSelections)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
-// AggregateSyncCommitteeSelections provides a mock function with given fields: ctx, partialSelections
-func (_m *Handler) AggregateSyncCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-	ret := _m.Called(ctx, partialSelections)
-
-	if len(ret) == 0 {
-		panic("no return value specified for AggregateSyncCommitteeSelections")
-	}
-
-	var r0 []*eth2exp.SyncCommitteeSelection
-	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error)); ok {
-		return rf(ctx, partialSelections)
-	}
-	if rf, ok := ret.Get(0).(func(context.Context, []*eth2exp.SyncCommitteeSelection) []*eth2exp.SyncCommitteeSelection); ok {
-		r0 = rf(ctx, partialSelections)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*eth2exp.SyncCommitteeSelection)
-		}
-	}
-
-	if rf, ok := ret.Get(1).(func(context.Context, []*eth2exp.SyncCommitteeSelection) error); ok {
-		r1 = rf(ctx, partialSelections)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
 // AttestationData provides a mock function with given fields: ctx, opts
 func (_m *Handler) AttestationData(ctx context.Context, opts *api.AttestationDataOpts) (*api.Response[*phase0.AttestationData], error) {
 	ret := _m.Called(ctx, opts)
@@ -176,28 +114,28 @@ func (_m *Handler) AttesterDuties(ctx context.Context, opts *api.AttesterDutiesO
 	return r0, r1
 }
 
-// NodeVersion provides a mock function with given fields: ctx, opts
-func (_m *Handler) NodeVersion(ctx context.Context, opts *api.NodeVersionOpts) (*api.Response[string], error) {
+// BeaconCommitteeSelections provides a mock function with given fields: ctx, opts
+func (_m *Handler) BeaconCommitteeSelections(ctx context.Context, opts *api.BeaconCommitteeSelectionsOpts) (*api.Response[[]*v1.BeaconCommitteeSelection], error) {
 	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
-		panic("no return value specified for NodeVersion")
+		panic("no return value specified for BeaconCommitteeSelections")
 	}
 
-	var r0 *api.Response[string]
+	var r0 *api.Response[[]*v1.BeaconCommitteeSelection]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *api.NodeVersionOpts) (*api.Response[string], error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconCommitteeSelectionsOpts) (*api.Response[[]*v1.BeaconCommitteeSelection], error)); ok {
 		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *api.NodeVersionOpts) *api.Response[string]); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *api.BeaconCommitteeSelectionsOpts) *api.Response[[]*v1.BeaconCommitteeSelection]); ok {
 		r0 = rf(ctx, opts)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*api.Response[string])
+			r0 = ret.Get(0).(*api.Response[[]*v1.BeaconCommitteeSelection])
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *api.NodeVersionOpts) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *api.BeaconCommitteeSelectionsOpts) error); ok {
 		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
@@ -206,28 +144,28 @@ func (_m *Handler) NodeVersion(ctx context.Context, opts *api.NodeVersionOpts) (
 	return r0, r1
 }
 
-// Proposal provides a mock function with given fields: ctx, opts
-func (_m *Handler) Proposal(ctx context.Context, opts *api.ProposalOpts) (*api.Response[*api.VersionedProposal], error) {
+// NodeVersion provides a mock function with given fields: ctx, opts
+func (_m *Handler) NodeVersion(ctx context.Context, opts *api.NodeVersionOpts) (*api.Response[string], error) {
 	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
-		panic("no return value specified for Proposal")
+		panic("no return value specified for NodeVersion")
 	}
 
-	var r0 *api.Response[*api.VersionedProposal]
+	var r0 *api.Response[string]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *api.ProposalOpts) (*api.Response[*api.VersionedProposal], error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *api.NodeVersionOpts) (*api.Response[string], error)); ok {
 		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *api.ProposalOpts) *api.Response[*api.VersionedProposal]); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *api.NodeVersionOpts) *api.Response[string]); ok {
 		r0 = rf(ctx, opts)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*api.Response[*api.VersionedProposal])
+			r0 = ret.Get(0).(*api.Response[string])
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *api.ProposalOpts) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *api.NodeVersionOpts) error); ok {
 		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
@@ -236,29 +174,29 @@ func (_m *Handler) Proposal(ctx context.Context, opts *api.ProposalOpts) (*api.R
 	return r0, r1
 }
 
-// ProposerConfig provides a mock function with given fields: ctx
-func (_m *Handler) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
-	ret := _m.Called(ctx)
+// Proposal provides a mock function with given fields: ctx, opts
+func (_m *Handler) Proposal(ctx context.Context, opts *api.ProposalOpts) (*api.Response[*api.VersionedProposal], error) {
+	ret := _m.Called(ctx, opts)
 
 	if len(ret) == 0 {
-		panic("no return value specified for ProposerConfig")
+		panic("no return value specified for Proposal")
 	}
 
-	var r0 *eth2exp.ProposerConfigResponse
+	var r0 *api.Response[*api.VersionedProposal]
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context) (*eth2exp.ProposerConfigResponse, error)); ok {
-		return rf(ctx)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.ProposalOpts) (*api.Response[*api.VersionedProposal], error)); ok {
+		return rf(ctx, opts)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context) *eth2exp.ProposerConfigResponse); ok {
-		r0 = rf(ctx)
+	if rf, ok := ret.Get(0).(func(context.Context, *api.ProposalOpts) *api.Response[*api.VersionedProposal]); ok {
+		r0 = rf(ctx, opts)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*eth2exp.ProposerConfigResponse)
+			r0 = ret.Get(0).(*api.Response[*api.VersionedProposal])
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context) error); ok {
-		r1 = rf(ctx)
+	if rf, ok := ret.Get(1).(func(context.Context, *api.ProposalOpts) error); ok {
+		r1 = rf(ctx, opts)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -500,6 +438,36 @@ func (_m *Handler) SyncCommitteeDuties(ctx context.Context, opts *api.SyncCommit
 	return r0, r1
 }
 
+// SyncCommitteeSelections provides a mock function with given fields: ctx, opts
+func (_m *Handler) SyncCommitteeSelections(ctx context.Context, opts *api.SyncCommitteeSelectionsOpts) (*api.Response[[]*v1.SyncCommitteeSelection], error) {
+	ret := _m.Called(ctx, opts)
+
+	if len(ret) == 0 {
+		panic("no return value specified for SyncCommitteeSelections")
+	}
+
+	var r0 *api.Response[[]*v1.SyncCommitteeSelection]
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *api.SyncCommitteeSelectionsOpts) (*api.Response[[]*v1.SyncCommitteeSelection], error)); ok {
+		return rf(ctx, opts)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *api.SyncCommitteeSelectionsOpts) *api.Response[[]*v1.SyncCommitteeSelection]); ok {
+		r0 = rf(ctx, opts)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*api.Response[[]*v1.SyncCommitteeSelection])
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *api.SyncCommitteeSelectionsOpts) error); ok {
+		r1 = rf(ctx, opts)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // Validators provides a mock function with given fields: ctx, opts
 func (_m *Handler) Validators(ctx context.Context, opts *api.ValidatorsOpts) (*api.Response[map[phase0.ValidatorIndex]*v1.Validator], error) {
 	ret := _m.Called(ctx, opts)
diff --git a/core/validatorapi/router.go b/core/validatorapi/router.go
index 3331e04b..c46accc6 100644
--- a/core/validatorapi/router.go
+++ b/core/validatorapi/router.go
@@ -43,7 +43,6 @@ import (
 	"github.com/obolnetwork/charon/app/log"
 	"github.com/obolnetwork/charon/app/z"
 	"github.com/obolnetwork/charon/core"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/tbls/tblsconv"
 )
 
@@ -71,7 +70,7 @@ type Handler interface {
 	eth2client.AttesterDutiesProvider
 	eth2client.ProposalProvider
 	eth2client.ProposalSubmitter
-	eth2exp.BeaconCommitteeSelectionAggregator
+	eth2client.BeaconCommitteeSelectionsProvider
 	eth2client.BlindedProposalSubmitter
 	eth2client.NodeVersionProvider
 	eth2client.ProposerDutiesProvider
@@ -79,11 +78,10 @@ type Handler interface {
 	eth2client.SyncCommitteeContributionsSubmitter
 	eth2client.SyncCommitteeDutiesProvider
 	eth2client.SyncCommitteeMessagesSubmitter
-	eth2exp.SyncCommitteeSelectionAggregator
+	eth2client.SyncCommitteeSelectionsProvider
 	eth2client.ValidatorsProvider
 	eth2client.ValidatorRegistrationsSubmitter
 	eth2client.VoluntaryExitSubmitter
-	eth2exp.ProposerConfigProvider
 	// Above sorted alphabetically.
 }
 
@@ -221,21 +219,21 @@ func NewRouter(ctx context.Context, h Handler, eth2Cl eth2wrap.Client, builderEn
 		{
 			Name:      "teku_proposer_config",
 			Path:      "/teku_proposer_config",
-			Handler:   proposerConfig(h),
+			Handler:   respond404("/teku_proposer_config"),
 			Methods:   []string{http.MethodGet},
 			Encodings: []contentType{contentTypeJSON},
 		},
 		{
 			Name:      "proposer_config",
 			Path:      "/proposer_config",
-			Handler:   proposerConfig(h),
+			Handler:   respond404("/proposer_config"),
 			Methods:   []string{http.MethodGet},
 			Encodings: []contentType{contentTypeJSON},
 		},
 		{
 			Name:      "aggregate_beacon_committee_selections",
 			Path:      "/eth/v1/validator/beacon_committee_selections",
-			Handler:   aggregateBeaconCommitteeSelections(h),
+			Handler:   beaconCommitteeSelections(h),
 			Methods:   []string{http.MethodPost},
 			Encodings: []contentType{contentTypeJSON},
 		},
@@ -298,7 +296,7 @@ func NewRouter(ctx context.Context, h Handler, eth2Cl eth2wrap.Client, builderEn
 		{
 			Name:      "aggregate_sync_committee_selections",
 			Path:      "/eth/v1/validator/sync_committee_selections",
-			Handler:   aggregateSyncCommitteeSelections(h),
+			Handler:   syncCommitteeSelections(h),
 			Methods:   []string{http.MethodPost},
 			Encodings: []contentType{contentTypeJSON},
 		},
@@ -1176,37 +1174,37 @@ func submitValidatorRegistrations(r eth2client.ValidatorRegistrationsSubmitter)
 	}
 }
 
-// aggregateBeaconCommitteeSelections receives partial beacon committee selections and returns aggregated selections.
-func aggregateBeaconCommitteeSelections(a eth2exp.BeaconCommitteeSelectionAggregator) handlerFunc {
+// beaconCommitteeSelections receives partial beacon committee selections and returns aggregated selections.
+func beaconCommitteeSelections(a eth2client.BeaconCommitteeSelectionsProvider) handlerFunc {
 	return func(ctx context.Context, _ map[string]string, _ http.Header, _ url.Values, typ contentType, body []byte) (res any, headers http.Header, err error) {
-		var selections []*eth2exp.BeaconCommitteeSelection
+		var selections []*eth2v1.BeaconCommitteeSelection
 		if err := unmarshal(typ, body, &selections); err != nil {
 			return nil, nil, errors.Wrap(err, "unmarshal beacon committee selections")
 		}
 
-		resp, err := a.AggregateBeaconCommitteeSelections(ctx, selections)
+		eth2Resp, err := a.BeaconCommitteeSelections(ctx, &eth2api.BeaconCommitteeSelectionsOpts{Selections: selections})
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, errors.Wrap(err, "beacon committee selections")
 		}
 
-		return aggregateBeaconCommitteeSelectionsJSON{Data: resp}, nil, nil
+		return beaconCommitteeSelectionsJSON{Data: eth2Resp.Data}, nil, nil
 	}
 }
 
-// aggregateSyncCommitteeSelections receives partial sync committee selections and returns aggregated selections.
-func aggregateSyncCommitteeSelections(a eth2exp.SyncCommitteeSelectionAggregator) handlerFunc {
+// syncCommitteeSelections receives partial sync committee selections and returns aggregated selections.
+func syncCommitteeSelections(a eth2client.SyncCommitteeSelectionsProvider) handlerFunc {
 	return func(ctx context.Context, _ map[string]string, _ http.Header, _ url.Values, typ contentType, body []byte) (res any, headers http.Header, err error) {
-		var selections []*eth2exp.SyncCommitteeSelection
+		var selections []*eth2v1.SyncCommitteeSelection
 		if err := unmarshal(typ, body, &selections); err != nil {
 			return nil, nil, errors.Wrap(err, "unmarshal sync committee selections")
 		}
 
-		resp, err := a.AggregateSyncCommitteeSelections(ctx, selections)
+		eth2Resp, err := a.SyncCommitteeSelections(ctx, &eth2api.SyncCommitteeSelectionsOpts{Selections: selections})
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, errors.Wrap(err, "sync committee selections")
 		}
 
-		return aggregateSyncCommitteeSelectionsJSON{Data: resp}, nil, nil
+		return syncCommitteeSelectionsJSON{Data: eth2Resp.Data}, nil, nil
 	}
 }
 
@@ -1222,17 +1220,6 @@ func submitExit(p eth2client.VoluntaryExitSubmitter) handlerFunc {
 	}
 }
 
-func proposerConfig(p eth2exp.ProposerConfigProvider) handlerFunc {
-	return func(ctx context.Context, _ map[string]string, _ http.Header, _ url.Values, _ contentType, _ []byte) (any, http.Header, error) {
-		resp, err := p.ProposerConfig(ctx)
-		if err != nil {
-			return nil, nil, errors.Wrap(err, "proposer config")
-		}
-
-		return resp, nil, nil
-	}
-}
-
 func aggregateAttestation(p eth2client.AggregateAttestationProvider) handlerFunc {
 	return func(ctx context.Context, _ map[string]string, _ http.Header, query url.Values, _ contentType, _ []byte) (any, http.Header, error) {
 		slot, err := uintQuery(query, "slot")
diff --git a/core/validatorapi/validatorapi.go b/core/validatorapi/validatorapi.go
index 38243c35..e146c0ff 100644
--- a/core/validatorapi/validatorapi.go
+++ b/core/validatorapi/validatorapi.go
@@ -8,7 +8,6 @@ import (
 	"maps"
 	"math/big"
 	"runtime"
-	"strconv"
 	"testing"
 	"time"
 
@@ -27,7 +26,6 @@ import (
 	"github.com/obolnetwork/charon/app/z"
 	"github.com/obolnetwork/charon/core"
 	"github.com/obolnetwork/charon/eth2util"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/eth2util/signing"
 	"github.com/obolnetwork/charon/tbls"
 	"github.com/obolnetwork/charon/tbls/tblsconv"
@@ -783,8 +781,8 @@ func (c Component) SubmitVoluntaryExit(ctx context.Context, exit *eth2p0.SignedV
 	return nil
 }
 
-// AggregateBeaconCommitteeSelections returns aggregate beacon committee selection proofs.
-func (c Component) AggregateBeaconCommitteeSelections(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
+// BeaconCommitteeSelections returns aggregate beacon committee selection proofs.
+func (c Component) BeaconCommitteeSelections(ctx context.Context, opts *eth2api.BeaconCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.BeaconCommitteeSelection], error) {
 	vals, err := c.eth2Cl.ActiveValidators(ctx)
 	if err != nil {
 		return nil, err
@@ -792,7 +790,7 @@ func (c Component) AggregateBeaconCommitteeSelections(ctx context.Context, selec
 
 	psigsBySlot := make(map[eth2p0.Slot]core.ParSignedDataSet)
 
-	for _, selection := range selections {
+	for _, selection := range opts.Selections {
 		eth2Pubkey, ok := vals[selection.ValidatorIndex]
 		if !ok {
 			return nil, errors.New("validator not found", z.Any("provided", selection.ValidatorIndex), z.Any("expected", vals.Indices()))
@@ -829,7 +827,27 @@ func (c Component) AggregateBeaconCommitteeSelections(ctx context.Context, selec
 		}
 	}
 
-	return c.getAggregateBeaconCommSelection(ctx, psigsBySlot)
+	var resp []*eth2v1.BeaconCommitteeSelection
+
+	for slot, data := range psigsBySlot {
+		duty := core.NewPrepareAggregatorDuty(uint64(slot))
+		for pk := range data {
+			// Query aggregated subscription from aggsigdb for each duty and public key (this is blocking).
+			s, err := c.awaitAggSigDBFunc(ctx, duty, pk)
+			if err != nil {
+				return nil, err
+			}
+
+			sub, ok := s.(core.BeaconCommitteeSelection)
+			if !ok {
+				return nil, errors.New("invalid beacon committee selection")
+			}
+
+			resp = append(resp, &sub.BeaconCommitteeSelection)
+		}
+	}
+
+	return wrapResponse(resp), nil
 }
 
 // AggregateAttestation returns the aggregate attestation for the given attestation root.
@@ -1037,8 +1055,8 @@ func (c Component) SubmitSyncCommitteeContributions(ctx context.Context, contrib
 	return nil
 }
 
-// AggregateSyncCommitteeSelections returns aggregate sync committee selection proofs.
-func (c Component) AggregateSyncCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
+// SyncCommitteeSelections returns aggregate sync committee selection proofs.
+func (c Component) SyncCommitteeSelections(ctx context.Context, opts *eth2api.SyncCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeSelection], error) {
 	vals, err := c.eth2Cl.ActiveValidators(ctx)
 	if err != nil {
 		return nil, err
@@ -1046,7 +1064,7 @@ func (c Component) AggregateSyncCommitteeSelections(ctx context.Context, partial
 
 	psigsBySlot := make(map[eth2p0.Slot]core.ParSignedDataSet)
 
-	for _, selection := range partialSelections {
+	for _, selection := range opts.Selections {
 		eth2Pubkey, ok := vals[selection.ValidatorIndex]
 		if !ok {
 			return nil, errors.New("validator not found")
@@ -1083,7 +1101,27 @@ func (c Component) AggregateSyncCommitteeSelections(ctx context.Context, partial
 		}
 	}
 
-	return c.getAggregateSyncCommSelection(ctx, psigsBySlot)
+	var resp []*eth2v1.SyncCommitteeSelection
+
+	for slot, data := range psigsBySlot {
+		duty := core.NewPrepareSyncContributionDuty(uint64(slot))
+		for pk := range data {
+			// Query aggregated sync committee selection from aggsigdb for each duty and public key (this is blocking).
+			s, err := c.awaitAggSigDBFunc(ctx, duty, pk)
+			if err != nil {
+				return nil, err
+			}
+
+			sub, ok := s.(core.SyncCommitteeSelection)
+			if !ok {
+				return nil, errors.New("invalid sync committee selection")
+			}
+
+			resp = append(resp, &sub.SyncCommitteeSelection)
+		}
+	}
+
+	return wrapResponse(resp), nil
 }
 
 // ProposerDuties obtains proposer duties for the given options.
@@ -1316,110 +1354,6 @@ func (c Component) verifyPartialSig(ctx context.Context, parSig core.ParSignedDa
 	return core.VerifyEth2SignedData(ctx, c.eth2Cl, eth2Signed, pubshare)
 }
 
-func (c Component) getAggregateBeaconCommSelection(ctx context.Context, psigsBySlot map[eth2p0.Slot]core.ParSignedDataSet) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	var resp []*eth2exp.BeaconCommitteeSelection
-
-	for slot, data := range psigsBySlot {
-		duty := core.NewPrepareAggregatorDuty(uint64(slot))
-		for pk := range data {
-			// Query aggregated subscription from aggsigdb for each duty and public key (this is blocking).
-			s, err := c.awaitAggSigDBFunc(ctx, duty, pk)
-			if err != nil {
-				return nil, err
-			}
-
-			sub, ok := s.(core.BeaconCommitteeSelection)
-			if !ok {
-				return nil, errors.New("invalid beacon committee selection")
-			}
-
-			resp = append(resp, &sub.BeaconCommitteeSelection)
-		}
-	}
-
-	return resp, nil
-}
-
-func (c Component) getAggregateSyncCommSelection(ctx context.Context, psigsBySlot map[eth2p0.Slot]core.ParSignedDataSet) ([]*eth2exp.SyncCommitteeSelection, error) {
-	var resp []*eth2exp.SyncCommitteeSelection
-
-	for slot, data := range psigsBySlot {
-		duty := core.NewPrepareSyncContributionDuty(uint64(slot))
-		for pk := range data {
-			// Query aggregated sync committee selection from aggsigdb for each duty and public key (this is blocking).
-			s, err := c.awaitAggSigDBFunc(ctx, duty, pk)
-			if err != nil {
-				return nil, err
-			}
-
-			sub, ok := s.(core.SyncCommitteeSelection)
-			if !ok {
-				return nil, errors.New("invalid sync committee selection")
-			}
-
-			resp = append(resp, &sub.SyncCommitteeSelection)
-		}
-	}
-
-	return resp, nil
-}
-
-// ProposerConfig returns the proposer configuration for all validators.
-func (c Component) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
-	var targetGasLimit uint
-	if c.targetGasLimit == 0 {
-		log.Warn(ctx, "", errors.New("custom target gas limit not supported, setting to default", z.Uint("default_gas_limit", defaultGasLimit)))
-		targetGasLimit = defaultGasLimit
-	} else {
-		targetGasLimit = c.targetGasLimit
-	}
-
-	resp := eth2exp.ProposerConfigResponse{
-		Proposers: make(map[eth2p0.BLSPubKey]eth2exp.ProposerConfig),
-		Default: eth2exp.ProposerConfig{ // Default doesn't make sense, disable for now.
-			FeeRecipient: zeroAddress,
-			Builder: eth2exp.Builder{
-				Enabled:  false,
-				GasLimit: targetGasLimit,
-			},
-		},
-	}
-
-	genesisTime, err := eth2wrap.FetchGenesisTime(ctx, c.eth2Cl)
-	if err != nil {
-		return nil, err
-	}
-
-	slotDuration, _, err := eth2wrap.FetchSlotsConfig(ctx, c.eth2Cl)
-	if err != nil {
-		return nil, err
-	}
-
-	timestamp := genesisTime
-	timestamp = timestamp.Add(slotDuration) // Use slot 1 for timestamp to override pre-generated registrations.
-
-	for pubkey, pubshare := range c.sharesByKey {
-		eth2Share, err := pubshare.ToETH2()
-		if err != nil {
-			return nil, err
-		}
-
-		resp.Proposers[eth2Share] = eth2exp.ProposerConfig{
-			FeeRecipient: c.feeRecipientFunc(pubkey),
-			Builder: eth2exp.Builder{
-				Enabled:  c.builderEnabled,
-				GasLimit: targetGasLimit,
-				Overrides: map[string]string{
-					"timestamp":  strconv.FormatInt(timestamp.Unix(), 10),
-					"public_key": string(pubkey),
-				},
-			},
-		}
-	}
-
-	return &resp, nil
-}
-
 // wrapResponse wraps the provided data into an API Response and returns the response.
 func wrapResponse[T any](data T) *eth2api.Response[T] {
 	return &eth2api.Response[T]{Data: data}
diff --git a/eth2util/eth2exp/attagg.go b/eth2util/eth2exp/attagg.go
deleted file mode 100644
index 1724ded3..00000000
--- a/eth2util/eth2exp/attagg.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
-
-package eth2exp
-
-import (
-	"context"
-	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"strconv"
-	"strings"
-
-	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
-
-	"github.com/obolnetwork/charon/app/errors"
-)
-
-// BeaconCommitteeSelectionAggregator is the interface for aggregating beacon committee selection proofs in a DVT cluster.
-// NOTE: It should be removed once it is supported by go-eth2-client.
-type BeaconCommitteeSelectionAggregator interface {
-	// AggregateBeaconCommitteeSelections returns DVT aggregated beacon committee selection proofs.
-	// This would call a new BN API endpoint: POST /eth/v1/validator/beacon_committee_selections
-	AggregateBeaconCommitteeSelections(ctx context.Context, partialSelections []*BeaconCommitteeSelection) ([]*BeaconCommitteeSelection, error)
-}
-
-// BeaconCommitteeSelection is the data required for a beacon committee subscription.
-type BeaconCommitteeSelection struct {
-	// ValidatorIdex is the index of the validator making the aggregate request.
-	ValidatorIndex eth2p0.ValidatorIndex
-	// Slot is the slot for which the validator is possibly aggregating.
-	Slot eth2p0.Slot
-	// SelectionProof is the slot signature required to calculate whether the validator is an aggregator.
-	SelectionProof eth2p0.BLSSignature
-}
-
-// beaconCommitteeSelectionJSON is the spec representation of the struct.
-type beaconCommitteeSelectionJSON struct {
-	ValidatorIndex string `json:"validator_index"`
-	Slot           string `json:"slot"`
-	SelectionProof string `json:"selection_proof"`
-}
-
-// MarshalJSON implements json.Marshaler.
-func (b *BeaconCommitteeSelection) MarshalJSON() ([]byte, error) {
-	resp, err := json.Marshal(&beaconCommitteeSelectionJSON{
-		ValidatorIndex: fmt.Sprintf("%d", b.ValidatorIndex),
-		Slot:           fmt.Sprintf("%d", b.Slot),
-		SelectionProof: fmt.Sprintf("%#x", b.SelectionProof),
-	})
-	if err != nil {
-		return nil, errors.Wrap(err, "marshal beacon committee subscription")
-	}
-
-	return resp, nil
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (b *BeaconCommitteeSelection) UnmarshalJSON(input []byte) error {
-	var err error
-
-	var beaconCommitteeSelectionJSON beaconCommitteeSelectionJSON
-	if err = json.Unmarshal(input, &beaconCommitteeSelectionJSON); err != nil {
-		return errors.Wrap(err, "invalid JSON")
-	}
-
-	if beaconCommitteeSelectionJSON.ValidatorIndex == "" {
-		return errors.New("validator index missing")
-	}
-
-	validatorIndex, err := strconv.ParseUint(beaconCommitteeSelectionJSON.ValidatorIndex, 10, 64)
-	if err != nil {
-		return errors.Wrap(err, "invalid value for validator index")
-	}
-
-	b.ValidatorIndex = eth2p0.ValidatorIndex(validatorIndex)
-
-	if beaconCommitteeSelectionJSON.Slot == "" {
-		return errors.New("slot missing")
-	}
-
-	slot, err := strconv.ParseUint(beaconCommitteeSelectionJSON.Slot, 10, 64)
-	if err != nil {
-		return errors.Wrap(err, "invalid value for slot")
-	}
-
-	b.Slot = eth2p0.Slot(slot)
-
-	if beaconCommitteeSelectionJSON.SelectionProof == "" {
-		return errors.New("selection proof missing")
-	}
-
-	signature, err := hex.DecodeString(strings.TrimPrefix(beaconCommitteeSelectionJSON.SelectionProof, "0x"))
-	if err != nil {
-		return errors.Wrap(err, "invalid value for signature")
-	}
-
-	if len(signature) != eth2p0.SignatureLength {
-		return errors.New("incorrect length for signature")
-	}
-
-	copy(b.SelectionProof[:], signature)
-
-	return nil
-}
-
-// String returns a string version of the structure.
-func (b *BeaconCommitteeSelection) String() (string, error) {
-	data, err := json.Marshal(b)
-	if err != nil {
-		return "", errors.Wrap(err, "marshal BeaconCommitteeSelection")
-	}
-
-	return string(data), nil
-}
diff --git a/eth2util/eth2exp/proposeconf.go b/eth2util/eth2exp/proposeconf.go
deleted file mode 100644
index a77983e8..00000000
--- a/eth2util/eth2exp/proposeconf.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
-
-package eth2exp
-
-import (
-	"context"
-	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"strconv"
-	"strings"
-	"time"
-
-	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
-
-	"github.com/obolnetwork/charon/app/errors"
-)
-
-const (
-	builderOverrideTimestamp = "timestamp"
-	builderOverridePublicKey = "public_key"
-)
-
-// ProposerConfigProvider is the interface for providing proposer configuration.
-type ProposerConfigProvider interface {
-	ProposerConfig(ctx context.Context) (*ProposerConfigResponse, error)
-}
-
-// ProposerConfigResponse is the response from the proposer config endpoint.
-type ProposerConfigResponse struct {
-	Proposers map[eth2p0.BLSPubKey]ProposerConfig
-	Default   ProposerConfig
-}
-
-func (p ProposerConfigResponse) MarshalJSON() ([]byte, error) {
-	resp := &proposerConfigResponseJSON{
-		Proposers: make(map[string]ProposerConfig),
-		Default:   p.Default,
-	}
-	for k, v := range p.Proposers {
-		resp.Proposers[fmt.Sprintf("%#x", k)] = v
-	}
-
-	b, err := json.Marshal(resp)
-	if err != nil {
-		return nil, errors.Wrap(err, "marshal proposer config response")
-	}
-
-	return b, nil
-}
-
-func (p *ProposerConfigResponse) UnmarshalJSON(input []byte) error {
-	resp := new(proposerConfigResponseJSON)
-	if err := json.Unmarshal(input, resp); err != nil {
-		return errors.Wrap(err, "unmarshal proposer config response")
-	}
-
-	p.Proposers = make(map[eth2p0.BLSPubKey]ProposerConfig)
-
-	for k, v := range resp.Proposers {
-		pkBytes, err := hex.DecodeString(strings.TrimPrefix(k, "0x"))
-		if err != nil {
-			return errors.Wrap(err, "decode proposer public key")
-		}
-
-		if len(pkBytes) != 48 {
-			return errors.New("invalid proposer public key")
-		}
-
-		p.Proposers[eth2p0.BLSPubKey(pkBytes)] = v
-	}
-
-	p.Default = resp.Default
-
-	return nil
-}
-
-// ProposerConfig is the configuration for a proposer.
-type ProposerConfig struct {
-	FeeRecipient string  `json:"fee_recipient"`
-	Builder      Builder `json:"builder"`
-}
-
-// Builder is the build-API configuration for a proposer.
-type Builder struct {
-	Enabled   bool              `json:"enabled"`
-	GasLimit  uint              `json:"gas_limit"`
-	Overrides map[string]string `json:"registration_overrides,omitempty"`
-}
-
-// TimestampOverride returns the timestamp override and true if it exists.
-func (b Builder) TimestampOverride() (time.Time, bool, error) {
-	ts, ok := b.Overrides[builderOverrideTimestamp]
-	if !ok {
-		return time.Time{}, false, nil
-	}
-
-	tsUnix, err := strconv.ParseInt(ts, 10, 64)
-	if err != nil {
-		return time.Time{}, false, errors.Wrap(err, "parse timestamp override")
-	}
-
-	return time.Unix(tsUnix, 0), true, nil
-}
-
-// PublicKeyOverride returns the public key override and true if it exists.
-func (b Builder) PublicKeyOverride() (eth2p0.BLSPubKey, bool, error) {
-	pk, ok := b.Overrides[builderOverridePublicKey]
-	if !ok {
-		return eth2p0.BLSPubKey{}, false, nil
-	}
-
-	pkBytes, err := hex.DecodeString(strings.TrimPrefix(pk, "0x"))
-	if err != nil {
-		return eth2p0.BLSPubKey{}, false, errors.Wrap(err, "parse public key override")
-	} else if len(pkBytes) != 48 {
-		return eth2p0.BLSPubKey{}, false, errors.New("invalid public key override")
-	}
-
-	return eth2p0.BLSPubKey(pkBytes), true, nil
-}
-
-type proposerConfigResponseJSON struct {
-	Proposers map[string]ProposerConfig `json:"proposer_config"`
-	Default   ProposerConfig            `json:"default_config"`
-}
diff --git a/eth2util/eth2exp/synccomm_agg.go b/eth2util/eth2exp/synccomm_agg.go
deleted file mode 100644
index f4c09836..00000000
--- a/eth2util/eth2exp/synccomm_agg.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
-
-package eth2exp
-
-import (
-	"context"
-	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"strconv"
-	"strings"
-
-	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
-
-	"github.com/obolnetwork/charon/app/errors"
-	"github.com/obolnetwork/charon/app/z"
-)
-
-// SyncCommitteeSelectionAggregator is the interface for aggregating sync committee selection proofs.
-type SyncCommitteeSelectionAggregator interface {
-	// AggregateSyncCommitteeSelections returns the threshold aggregated sync committee selection proofs.
-	// This would call a new BN API endpoint: POST /eth/v1/validator/sync_committee_selections
-	AggregateSyncCommitteeSelections(ctx context.Context, partialSelections []*SyncCommitteeSelection) ([]*SyncCommitteeSelection, error)
-}
-
-// SyncCommitteeSelection is the data required for a sync committee subscription.
-type SyncCommitteeSelection struct {
-	// ValidatorIndex is the index of the validator making the selection request.
-	ValidatorIndex eth2p0.ValidatorIndex
-	// Slot is the slot for which the selection request is made.
-	Slot eth2p0.Slot
-	// SubcommitteeIndex is the subcommittee to which the validator is assigned.
-	SubcommitteeIndex eth2p0.CommitteeIndex
-	// SelectionProof is a partial or an aggregated signature.
-	SelectionProof eth2p0.BLSSignature
-}
-
-// syncCommitteeSelectionJSON is the spec representation of the struct.
-type syncCommitteeSelectionJSON struct {
-	ValidatorIndex    string `json:"validator_index"`
-	Slot              string `json:"slot"`
-	SubcommitteeIndex string `json:"subcommittee_index"`
-	SelectionProof    string `json:"selection_proof"`
-}
-
-// MarshalJSON implements json.Marshaler.
-func (s *SyncCommitteeSelection) MarshalJSON() ([]byte, error) {
-	resp, err := json.Marshal(&syncCommitteeSelectionJSON{
-		ValidatorIndex:    fmt.Sprintf("%d", s.ValidatorIndex),
-		Slot:              fmt.Sprintf("%d", s.Slot),
-		SubcommitteeIndex: fmt.Sprintf("%d", s.SubcommitteeIndex),
-		SelectionProof:    fmt.Sprintf("%#x", s.SelectionProof),
-	})
-	if err != nil {
-		return nil, errors.Wrap(err, "marshal sync committee selection")
-	}
-
-	return resp, nil
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *SyncCommitteeSelection) UnmarshalJSON(input []byte) error {
-	var err error
-
-	var syncCommitteeSelectionJSON syncCommitteeSelectionJSON
-	if err = json.Unmarshal(input, &syncCommitteeSelectionJSON); err != nil {
-		return errors.Wrap(err, "invalid JSON")
-	}
-
-	// verify and unmarshal ValidatorIndex.
-	if syncCommitteeSelectionJSON.ValidatorIndex == "" {
-		return errors.New("validator index missing")
-	}
-
-	validatorIndex, err := strconv.ParseUint(syncCommitteeSelectionJSON.ValidatorIndex, 10, 64)
-	if err != nil {
-		return errors.Wrap(err, "invalid value for validator index", z.Str("vIdx", syncCommitteeSelectionJSON.ValidatorIndex))
-	}
-
-	s.ValidatorIndex = eth2p0.ValidatorIndex(validatorIndex)
-
-	// verify and unmarshal Slot.
-	if syncCommitteeSelectionJSON.Slot == "" {
-		return errors.New("slot missing")
-	}
-
-	slot, err := strconv.ParseUint(syncCommitteeSelectionJSON.Slot, 10, 64)
-	if err != nil {
-		return errors.Wrap(err, "invalid value for slot", z.Str("slot", syncCommitteeSelectionJSON.Slot))
-	}
-
-	s.Slot = eth2p0.Slot(slot)
-
-	// verify and unmarshal SubcommitteeIndex.
-	if syncCommitteeSelectionJSON.SubcommitteeIndex == "" {
-		return errors.New("subcommittee index missing")
-	}
-
-	subcommIdx, err := strconv.ParseUint(syncCommitteeSelectionJSON.SubcommitteeIndex, 10, 64)
-	if err != nil {
-		return errors.Wrap(err, "invalid value for subcommittee index", z.Str("subcommIdx", syncCommitteeSelectionJSON.SubcommitteeIndex))
-	}
-
-	s.SubcommitteeIndex = eth2p0.CommitteeIndex(subcommIdx)
-
-	// verify and unmarshal SelectionProof.
-	if syncCommitteeSelectionJSON.SelectionProof == "" {
-		return errors.New("selection proof missing")
-	}
-
-	signature, err := hex.DecodeString(strings.TrimPrefix(syncCommitteeSelectionJSON.SelectionProof, "0x"))
-	if err != nil {
-		return errors.Wrap(err, "invalid value for signature", z.Str("sig", syncCommitteeSelectionJSON.SelectionProof))
-	}
-
-	if len(signature) != eth2p0.SignatureLength {
-		return errors.New("invalid signature length")
-	}
-
-	copy(s.SelectionProof[:], signature)
-
-	return nil
-}
-
-// String returns a string version of SyncCommitteeSelection.
-func (s *SyncCommitteeSelection) String() (string, error) {
-	data, err := json.Marshal(s)
-	if err != nil {
-		return "", errors.Wrap(err, "marshal SyncCommitteeSelection")
-	}
-
-	return string(data), nil
-}
diff --git a/eth2util/statecomm/statecomm.go b/eth2util/statecomm/statecomm.go
deleted file mode 100644
index f1e6f3f8..00000000
--- a/eth2util/statecomm/statecomm.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright © 2022-2025 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
-
-package statecomm
-
-import (
-	"encoding/json"
-	"fmt"
-	"strconv"
-
-	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
-
-	"github.com/obolnetwork/charon/app/errors"
-)
-
-// StateCommitteesResponse is simplified response structure for fetching the beacon node committees for a given state.
-// This is the response from the BN API endpoint: /eth/v1/beacon/states/{state_id}/validators.
-type StateCommitteesResponse struct {
-	Data []*StateCommittee `json:"data"`
-}
-
-// StateCommittee is the Data field from the BN API endpoint /eth/v1/beacon/states/{state_id}/validators response.
-type StateCommittee struct {
-	Index      eth2p0.CommitteeIndex   `json:"index"`
-	Slot       eth2p0.Slot             `json:"slot"`
-	Validators []eth2p0.ValidatorIndex `json:"validators"`
-}
-
-// beaconCommitteeSelectionJSON is the spec representation of the struct.
-type stateCommitteeJSON struct {
-	Index      string   `json:"index"`
-	Slot       string   `json:"slot"`
-	Validators []string `json:"validators"`
-}
-
-// MarshalJSON implements json.Marshaler.
-func (b *StateCommittee) MarshalJSON() ([]byte, error) {
-	var validators []string
-	for _, v := range b.Validators {
-		validators = append(validators, fmt.Sprintf("%d", v))
-	}
-
-	resp, err := json.Marshal(&stateCommitteeJSON{
-		Index:      fmt.Sprintf("%d", b.Index),
-		Slot:       fmt.Sprintf("%d", b.Slot),
-		Validators: validators,
-	})
-	if err != nil {
-		return nil, errors.Wrap(err, "marshal state committee subscription")
-	}
-
-	return resp, nil
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (b *StateCommittee) UnmarshalJSON(input []byte) error {
-	var err error
-
-	var stateCommitteeJSON stateCommitteeJSON
-	if err = json.Unmarshal(input, &stateCommitteeJSON); err != nil {
-		return errors.Wrap(err, "invalid JSON")
-	}
-
-	if stateCommitteeJSON.Index == "" {
-		return errors.New("index missing")
-	}
-
-	index, err := strconv.ParseUint(stateCommitteeJSON.Index, 10, 64)
-	if err != nil {
-		return errors.Wrap(err, "invalid value for index")
-	}
-
-	b.Index = eth2p0.CommitteeIndex(index)
-
-	if stateCommitteeJSON.Slot == "" {
-		return errors.New("slot missing")
-	}
-
-	slot, err := strconv.ParseUint(stateCommitteeJSON.Slot, 10, 64)
-	if err != nil {
-		return errors.Wrap(err, "invalid value for slot")
-	}
-
-	b.Slot = eth2p0.Slot(slot)
-
-	var validators []eth2p0.ValidatorIndex
-
-	for _, v := range stateCommitteeJSON.Validators {
-		validator, err := strconv.ParseUint(v, 10, 64)
-		if err != nil {
-			return errors.Wrap(err, "invalid value for validator")
-		}
-
-		validators = append(validators, eth2p0.ValidatorIndex(validator))
-	}
-
-	b.Validators = validators
-
-	return nil
-}
-
-// String returns a string version of the structure.
-func (b *StateCommittee) String() (string, error) {
-	data, err := json.Marshal(b)
-	if err != nil {
-		return "", errors.Wrap(err, "marshal StateCommittee")
-	}
-
-	return string(data), nil
-}
diff --git a/go.mod b/go.mod
index 3e2a3eed..6ac789e2 100644
--- a/go.mod
+++ b/go.mod
@@ -14,13 +14,13 @@ require (
 	github.com/gorilla/mux v1.8.1
 	github.com/herumi/bls-eth-go-binary v1.36.4
 	github.com/holiman/uint256 v1.3.2
-	github.com/ipfs/go-log/v2 v2.7.0
+	github.com/ipfs/go-log/v2 v2.8.0
 	github.com/jonboulle/clockwork v0.5.0
 	github.com/jsternberg/zap-logfmt v1.3.0
 	github.com/libp2p/go-libp2p v0.41.1
 	github.com/libp2p/go-msgio v0.3.0
 	github.com/multiformats/go-multiaddr v0.16.0
-	github.com/prometheus/client_golang v1.22.0
+	github.com/prometheus/client_golang v1.23.0
 	github.com/prometheus/client_model v0.6.2
 	github.com/protolambda/eth2-shuffle v1.1.0
 	github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15
@@ -229,8 +229,8 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pkg/profile v1.7.0 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-	github.com/prometheus/common v0.63.0 // indirect
-	github.com/prometheus/procfs v0.16.0 // indirect
+	github.com/prometheus/common v0.65.0 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/quic-go/qpack v0.5.1 // indirect
 	github.com/quic-go/quic-go v0.50.1 // indirect
 	github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect
@@ -293,9 +293,8 @@ require (
 // We're replacing kryptology with our own fork, which fixes dependencies' security vulnerabilities.
 replace github.com/coinbase/kryptology => github.com/ObolNetwork/kryptology v0.1.0
 
-// We're replacing go-eth2-client with a branch off our fork, at version v0.21.10.
-// This is needed to ensure Gnosis compatibility.
-// replace github.com/attestantio/go-eth2-client => github.com/ObolNetwork/go-eth2-client v0.21.11-0.20240822135044-f0a5b21e02c6
+// We're replacing go-eth2-client with a branch off our fork. The branch is kept up to date with the latest attestantio versions.
+replace github.com/attestantio/go-eth2-client => github.com/ObolNetwork/go-eth2-client v0.26.0-obol.2 //nolint
 
 tool (
 	github.com/bufbuild/buf/cmd/buf
diff --git a/go.sum b/go.sum
index 5af7d71b..9ef8e6ce 100644
--- a/go.sum
+++ b/go.sum
@@ -38,6 +38,8 @@ github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
 github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/ObolNetwork/go-eth2-client v0.26.0-obol.2 h1:IwvyBXUPwMFizl9TZeQaG1TwB67ScS1LdXgUrHwD//o=
+github.com/ObolNetwork/go-eth2-client v0.26.0-obol.2/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY=
 github.com/ObolNetwork/kryptology v0.1.0 h1:AhoG4My70+xMhEJSpVaJay/t+T/vIUNHQYLjsDJHulI=
 github.com/ObolNetwork/kryptology v0.1.0/go.mod h1:/Wl7Js2f676GyXZDTaojf/O+l0fxFPWudbyjdFhkpSA=
 github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
@@ -52,8 +54,6 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D
 github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
 github.com/attestantio/go-builder-client v0.6.4 h1:D0gBdQO4vJLhoFLxdR2TbGdfsWBQZ5YDZBNdU2Ap3uk=
 github.com/attestantio/go-builder-client v0.6.4/go.mod h1:f8wi3HzuPxfJoi2PirpJK3yZhte4SavDgKJbRrKoB1Q=
-github.com/attestantio/go-eth2-client v0.26.0 h1:oDWKvIUJfvr1EBi/w9L6mawYZHOCymjHkml7fZplT20=
-github.com/attestantio/go-eth2-client v0.26.0/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY=
 github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
 github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@@ -327,8 +327,8 @@ github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmne
 github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
 github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
 github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
-github.com/ipfs/go-log/v2 v2.7.0 h1:1lYgX/HfwQJj0fKgphYVlA0vUHcvCgPgd+uwYni5++8=
-github.com/ipfs/go-log/v2 v2.7.0/go.mod h1:6Gu9uvi/SfPIIUn3M3NOR9NnDfhHRjBLyNoFMrrlczg=
+github.com/ipfs/go-log/v2 v2.8.0 h1:SptNTPJQV3s5EF4FdrTu/yVdOKfGbDgn1EBZx4til2o=
+github.com/ipfs/go-log/v2 v2.8.0/go.mod h1:2LEEhdv8BGubPeSFTyzbqhCqrwqxCbuTNTLWqgNAipo=
 github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
 github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
 github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
@@ -580,17 +580,17 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
 github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
-github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
+github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
-github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/protolambda/eth2-shuffle v1.1.0 h1:gixIBI84IeugTwwHXm8vej1bSSEhueBCSryA4lAKRLU=
 github.com/protolambda/eth2-shuffle v1.1.0/go.mod h1:FhA2c0tN15LTC+4T9DNVm+55S7uXTTjQ8TQnBuXlkF8=
 github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94=



Test patch:
diff --git a/app/eth2wrap/eth2wrap_test.go b/app/eth2wrap/eth2wrap_test.go
index 641c7a1e..e9a71915 100644
--- a/app/eth2wrap/eth2wrap_test.go
+++ b/app/eth2wrap/eth2wrap_test.go
@@ -5,7 +5,6 @@ package eth2wrap_test
 import (
 	"context"
 	"encoding/hex"
-	"encoding/json"
 	"fmt"
 	"net"
 	"net/http"
@@ -19,7 +18,6 @@ import (
 	eth2api "github.com/attestantio/go-eth2-client/api"
 	eth2v1 "github.com/attestantio/go-eth2-client/api/v1"
 	eth2spec "github.com/attestantio/go-eth2-client/spec"
-	eth2e "github.com/attestantio/go-eth2-client/spec/electra"
 	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -37,42 +35,42 @@ func TestMulti(t *testing.T) {
 
 	tests := []struct {
 		name   string
-		handle func(cl1Resp, cl2Resp chan int, ctxCancel context.CancelFunc)
+		handle func(cl1Resp, cl2Resp chan *eth2v1.PeerCount, ctxCancel context.CancelFunc)
 		expErr error
-		expRes int
+		expRes *eth2api.Response[*eth2v1.PeerCount]
 	}{
 		{
 			name: "cl1 only",
-			handle: func(cl1Resp, _ chan int, _ context.CancelFunc) {
-				cl1Resp <- 99
+			handle: func(cl1Resp, _ chan *eth2v1.PeerCount, _ context.CancelFunc) {
+				cl1Resp <- &eth2v1.PeerCount{Connected: 99}
 			},
-			expRes: 99,
+			expRes: &eth2api.Response[*eth2v1.PeerCount]{Data: &eth2v1.PeerCount{Connected: 99}},
 		},
 		{
 			name: "cl2 only",
-			handle: func(_, cl2Resp chan int, _ context.CancelFunc) {
-				cl2Resp <- 99
+			handle: func(_, cl2Resp chan *eth2v1.PeerCount, _ context.CancelFunc) {
+				cl2Resp <- &eth2v1.PeerCount{Connected: 99}
 			},
-			expRes: 99,
+			expRes: &eth2api.Response[*eth2v1.PeerCount]{Data: &eth2v1.PeerCount{Connected: 99}},
 		},
 		{
 			name: "ctx cancel",
-			handle: func(_, _ chan int, ctxCancel context.CancelFunc) {
+			handle: func(_, _ chan *eth2v1.PeerCount, ctxCancel context.CancelFunc) {
 				ctxCancel()
 			},
 			expErr: context.Canceled,
 		},
 		{
 			name: "cl1 error, cl2 ok",
-			handle: func(cl1, cl2 chan int, _ context.CancelFunc) {
+			handle: func(cl1, cl2 chan *eth2v1.PeerCount, _ context.CancelFunc) {
 				close(cl1)
-				cl2 <- 99
+				cl2 <- &eth2v1.PeerCount{Connected: 99}
 			},
-			expRes: 99,
+			expRes: &eth2api.Response[*eth2v1.PeerCount]{Data: &eth2v1.PeerCount{Connected: 99}},
 		},
 		{
 			name: "all error",
-			handle: func(cl1, cl2 chan int, _ context.CancelFunc) {
+			handle: func(cl1, cl2 chan *eth2v1.PeerCount, _ context.CancelFunc) {
 				close(cl1)
 				close(cl2)
 			},
@@ -80,7 +78,7 @@ func TestMulti(t *testing.T) {
 		},
 		{
 			name: "cl1 error, ctx cancel",
-			handle: func(cl1, _ chan int, cancel context.CancelFunc) {
+			handle: func(cl1, _ chan *eth2v1.PeerCount, cancel context.CancelFunc) {
 				close(cl1)
 				cancel()
 			},
@@ -88,13 +86,13 @@ func TestMulti(t *testing.T) {
 		},
 		{
 			name: "cl2 before cl1",
-			handle: func(cl1, cl2 chan int, cancel context.CancelFunc) {
-				cl2 <- 99
+			handle: func(cl1, cl2 chan *eth2v1.PeerCount, cancel context.CancelFunc) {
+				cl2 <- &eth2v1.PeerCount{Connected: 99}
 
 				time.Sleep(time.Millisecond)
-				cl1 <- 98 // This might flap?
+				cl1 <- &eth2v1.PeerCount{Connected: 98} // This might flap?
 			},
-			expRes: 99,
+			expRes: &eth2api.Response[*eth2v1.PeerCount]{Data: &eth2v1.PeerCount{Connected: 99}},
 		},
 	}
 	for _, test := range tests {
@@ -107,28 +105,28 @@ func TestMulti(t *testing.T) {
 			cl2, err := beaconmock.New()
 			require.NoError(t, err)
 
-			cl1Resp := make(chan int)
-			cl2Resp := make(chan int)
+			cl1Resp := make(chan *eth2v1.PeerCount)
+			cl2Resp := make(chan *eth2v1.PeerCount)
 
-			cl1.NodePeerCountFunc = func(ctx context.Context) (int, error) {
+			cl1.NodePeerCountFunc = func(ctx context.Context, opts *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error) {
 				select {
 				case <-ctx.Done():
-					return 0, ctx.Err()
+					return &eth2v1.PeerCount{Connected: 0}, ctx.Err()
 				case resp, ok := <-cl1Resp:
 					if !ok {
-						return 0, closedErr
+						return &eth2v1.PeerCount{Connected: 0}, closedErr
 					}
 
 					return resp, nil
 				}
 			}
-			cl2.NodePeerCountFunc = func(ctx context.Context) (int, error) {
+			cl2.NodePeerCountFunc = func(ctx context.Context, opts *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error) {
 				select {
 				case <-ctx.Done():
-					return 0, ctx.Err()
+					return &eth2v1.PeerCount{Connected: 0}, ctx.Err()
 				case resp, ok := <-cl2Resp:
 					if !ok {
-						return 0, closedErr
+						return &eth2v1.PeerCount{Connected: 0}, closedErr
 					}
 
 					return resp, nil
@@ -140,7 +138,7 @@ func TestMulti(t *testing.T) {
 
 			go test.handle(cl1Resp, cl2Resp, cancel)
 
-			resp, err := eth2Cl.NodePeerCount(ctx)
+			resp, err := eth2Cl.NodePeerCount(ctx, &eth2api.NodePeerCountOpts{})
 			require.ErrorIs(t, err, test.expErr)
 			require.Equal(t, test.expRes, resp)
 		})
@@ -148,7 +146,7 @@ func TestMulti(t *testing.T) {
 }
 
 func TestFallback(t *testing.T) {
-	returnValue := 42
+	returnValue := &eth2v1.PeerCount{Connected: 42}
 	closedErr := errors.New("error")
 
 	tests := []struct {
@@ -197,7 +195,7 @@ func TestFallback(t *testing.T) {
 				cl, err := beaconmock.New()
 				require.NoError(t, err)
 
-				cl.NodePeerCountFunc = func(context.Context) (int, error) {
+				cl.NodePeerCountFunc = func(context.Context, *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error) {
 					calledMu.Lock()
 
 					primaryCalled[i] = true
@@ -215,7 +213,7 @@ func TestFallback(t *testing.T) {
 				cl, err := beaconmock.New()
 				require.NoError(t, err)
 
-				cl.NodePeerCountFunc = func(context.Context) (int, error) {
+				cl.NodePeerCountFunc = func(context.Context, *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error) {
 					calledMu.Lock()
 
 					fallbackCalled[i] = true
@@ -229,9 +227,9 @@ func TestFallback(t *testing.T) {
 
 			eth2Cl, err := eth2wrap.Instrument(primaryClients, fallbackClients)
 			require.NoError(t, err)
-			res, err := eth2Cl.NodePeerCount(t.Context())
+			res, err := eth2Cl.NodePeerCount(t.Context(), &eth2api.NodePeerCountOpts{})
 			require.NoError(t, err)
-			require.Equal(t, returnValue, res)
+			require.Equal(t, returnValue, res.Data)
 
 			calledMu.Lock()
 			defer calledMu.Unlock()
@@ -290,10 +288,10 @@ func TestErrors(t *testing.T) {
 		cl, err := eth2wrap.NewMultiHTTP(time.Hour, [4]byte{}, nil, []string{"localhost:22222"}, nil)
 		require.NoError(t, err)
 
-		_, err = cl.NodePeerCount(ctx)
+		_, err = cl.NodePeerCount(ctx, &eth2api.NodePeerCountOpts{})
 		log.Error(ctx, "See this error log for fields", err)
 		require.Error(t, err)
-		require.ErrorContains(t, err, "beacon api node_peer_count: network operation error")
+		require.ErrorContains(t, err, "beacon api node_peer_count: client is not active")
 	})
 
 	// Test http server that just hangs until request cancelled
@@ -305,10 +303,10 @@ func TestErrors(t *testing.T) {
 		cl, err := eth2wrap.NewMultiHTTP(time.Millisecond, [4]byte{}, nil, []string{srv.URL}, nil)
 		require.NoError(t, err)
 
-		_, err = cl.NodePeerCount(ctx)
+		_, err = cl.NodePeerCount(ctx, &eth2api.NodePeerCountOpts{})
 		log.Error(ctx, "See this error log for fields", err)
 		require.Error(t, err)
-		require.ErrorContains(t, err, "beacon api node_peer_count: http request timeout")
+		require.ErrorContains(t, err, "beacon api node_peer_count: client is not active")
 	})
 
 	t.Run("caller cancelled", func(t *testing.T) {
@@ -318,7 +316,7 @@ func TestErrors(t *testing.T) {
 		cl, err := eth2wrap.NewMultiHTTP(time.Millisecond, [4]byte{}, nil, []string{srv.URL}, nil)
 		require.NoError(t, err)
 
-		_, err = cl.NodePeerCount(ctx)
+		_, err = cl.NodePeerCount(ctx, &eth2api.NodePeerCountOpts{})
 		log.Error(ctx, "See this error log for fields", err)
 		require.Error(t, err)
 		require.ErrorContains(t, err, "beacon api node_peer_count: context canceled")
@@ -381,65 +379,6 @@ func TestCtxCancel(t *testing.T) {
 	}
 }
 
-func TestBlockAttestations(t *testing.T) {
-	electraAtt1 := testutil.RandomElectraAttestation()
-	electraAtt2 := testutil.RandomElectraAttestation()
-
-	tests := []struct {
-		version          string
-		attestations     []*eth2spec.VersionedAttestation
-		serverJSONStruct any
-		expErr           string
-	}{
-		{
-			version: "electra",
-			attestations: []*eth2spec.VersionedAttestation{
-				{Version: eth2spec.DataVersionElectra, Electra: electraAtt1},
-				{Version: eth2spec.DataVersionElectra, Electra: electraAtt2},
-			},
-			serverJSONStruct: struct{ Data []*eth2e.Attestation }{Data: []*eth2e.Attestation{electraAtt1, electraAtt2}},
-			expErr:           "",
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.version, func(t *testing.T) {
-			statusCode := http.StatusOK
-			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-				require.Equal(t, http.MethodGet, r.Method)
-				require.Equal(t, "/eth/v2/beacon/blocks/head/attestations", r.URL.Path)
-
-				b, err := json.Marshal(test.serverJSONStruct)
-				require.NoError(t, err)
-
-				w.Header().Add("Eth-Consensus-Version", test.version)
-				w.WriteHeader(statusCode)
-				_, _ = w.Write(b)
-			}))
-
-			cl := eth2wrap.NewHTTPAdapterForT(t, srv.URL, nil, time.Hour)
-
-			resp, err := cl.BlockAttestations(context.Background(), "head")
-			if test.expErr != "" {
-				require.ErrorContains(t, err, test.expErr)
-			} else {
-				require.NoError(t, err)
-			}
-
-			require.Equal(t, test.attestations, resp)
-
-			statusCode = http.StatusNotFound
-			resp, err = cl.BlockAttestations(context.Background(), "head")
-			require.NoError(t, err)
-			require.Empty(t, resp)
-
-			statusCode = http.StatusBadRequest
-			resp, err = cl.BlockAttestations(context.Background(), "head")
-			require.Error(t, err)
-			require.Empty(t, resp)
-		})
-	}
-}
-
 // TestOneError tests the case where one of the servers returns errors.
 func TestOneError(t *testing.T) {
 	// Start an erroring server.
diff --git a/app/eth2wrap/lazy_test.go b/app/eth2wrap/lazy_test.go
index 9f8f6f75..212af598 100644
--- a/app/eth2wrap/lazy_test.go
+++ b/app/eth2wrap/lazy_test.go
@@ -6,13 +6,11 @@ import (
 	"context"
 	"testing"
 
-	"github.com/attestantio/go-eth2-client/spec"
 	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 
 	"github.com/obolnetwork/charon/app/eth2wrap"
 	"github.com/obolnetwork/charon/app/eth2wrap/mocks"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 )
 
 func TestLazy_Name(t *testing.T) {
@@ -51,75 +49,6 @@ func TestLazy_IsSynced(t *testing.T) {
 	require.True(t, l.IsSynced())
 }
 
-func TestLazy_NodePeerCount(t *testing.T) {
-	client := mocks.NewClient(t)
-	client.On("NodePeerCount", mock.Anything).Return(5, nil).Once()
-
-	l := eth2wrap.NewLazyForT(client)
-
-	c, err := l.NodePeerCount(context.Background())
-	require.NoError(t, err)
-	require.Equal(t, 5, c)
-}
-
-func TestLazy_BlockAttestations(t *testing.T) {
-	ctx := context.Background()
-	atts := make([]*spec.VersionedAttestation, 3)
-
-	client := mocks.NewClient(t)
-	client.On("BlockAttestations", ctx, "state").Return(atts, nil).Once()
-
-	l := eth2wrap.NewLazyForT(client)
-
-	atts2, err := l.BlockAttestations(ctx, "state")
-	require.NoError(t, err)
-	require.Equal(t, atts, atts2)
-}
-
-func TestLazy_AggregateSyncCommitteeSelections(t *testing.T) {
-	ctx := context.Background()
-	partsel := make([]*eth2exp.SyncCommitteeSelection, 1)
-	selections := make([]*eth2exp.SyncCommitteeSelection, 3)
-
-	client := mocks.NewClient(t)
-	client.On("AggregateSyncCommitteeSelections", ctx, partsel).Return(selections, nil).Once()
-
-	l := eth2wrap.NewLazyForT(client)
-
-	selections2, err := l.AggregateSyncCommitteeSelections(ctx, partsel)
-	require.NoError(t, err)
-	require.Equal(t, selections, selections2)
-}
-
-func TestLazy_AggregateBeaconCommitteeSelections(t *testing.T) {
-	ctx := context.Background()
-	partsel := make([]*eth2exp.BeaconCommitteeSelection, 1)
-	selections := make([]*eth2exp.BeaconCommitteeSelection, 3)
-
-	client := mocks.NewClient(t)
-	client.On("AggregateBeaconCommitteeSelections", ctx, partsel).Return(selections, nil).Once()
-
-	l := eth2wrap.NewLazyForT(client)
-
-	selections2, err := l.AggregateBeaconCommitteeSelections(ctx, partsel)
-	require.NoError(t, err)
-	require.Equal(t, selections, selections2)
-}
-
-func TestLazy_ProposerConfig(t *testing.T) {
-	ctx := context.Background()
-	resp := &eth2exp.ProposerConfigResponse{}
-
-	client := mocks.NewClient(t)
-	client.On("ProposerConfig", ctx).Return(resp, nil).Once()
-
-	l := eth2wrap.NewLazyForT(client)
-
-	resp2, err := l.ProposerConfig(ctx)
-	require.NoError(t, err)
-	require.Equal(t, resp, resp2)
-}
-
 func TestLazy_ActiveValidators(t *testing.T) {
 	ctx := context.Background()
 	vals := make(eth2wrap.ActiveValidators)
diff --git a/app/eth2wrap/multi_test.go b/app/eth2wrap/multi_test.go
index 61017ae8..5ba1d736 100644
--- a/app/eth2wrap/multi_test.go
+++ b/app/eth2wrap/multi_test.go
@@ -7,13 +7,11 @@ import (
 	"errors"
 	"testing"
 
-	"github.com/attestantio/go-eth2-client/spec"
 	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 
 	"github.com/obolnetwork/charon/app/eth2wrap"
 	"github.com/obolnetwork/charon/app/eth2wrap/mocks"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 )
 
 func TestMulti_Name(t *testing.T) {
@@ -57,108 +55,6 @@ func TestMulti_IsSynced(t *testing.T) {
 	require.True(t, m.IsSynced())
 }
 
-func TestMulti_NodePeerCount(t *testing.T) {
-	client := mocks.NewClient(t)
-	client.On("Address").Return("test").Once()
-	client.On("NodePeerCount", mock.Anything).Return(5, nil).Once()
-
-	m := eth2wrap.NewMultiForT([]eth2wrap.Client{client}, nil)
-
-	c, err := m.NodePeerCount(context.Background())
-	require.NoError(t, err)
-	require.Equal(t, 5, c)
-
-	expectedErr := errors.New("boo")
-	client.On("NodePeerCount", mock.Anything).Return(0, expectedErr).Once()
-
-	_, err = m.NodePeerCount(context.Background())
-	require.ErrorIs(t, err, expectedErr)
-}
-
-func TestMulti_BlockAttestations(t *testing.T) {
-	ctx := context.Background()
-	atts := make([]*spec.VersionedAttestation, 3)
-
-	client := mocks.NewClient(t)
-	client.On("Address").Return("test").Once()
-	client.On("BlockAttestations", mock.Anything, "state").Return(atts, nil).Once()
-
-	m := eth2wrap.NewMultiForT([]eth2wrap.Client{client}, nil)
-
-	atts2, err := m.BlockAttestations(ctx, "state")
-	require.NoError(t, err)
-	require.Equal(t, atts, atts2)
-
-	expectedErr := errors.New("boo")
-	client.On("BlockAttestations", mock.Anything, "state").Return(nil, expectedErr).Once()
-
-	_, err = m.BlockAttestations(ctx, "state")
-	require.ErrorIs(t, err, expectedErr)
-}
-
-func TestMulti_AggregateSyncCommitteeSelections(t *testing.T) {
-	ctx := context.Background()
-	partsel := make([]*eth2exp.SyncCommitteeSelection, 1)
-	selections := make([]*eth2exp.SyncCommitteeSelection, 3)
-
-	client := mocks.NewClient(t)
-	client.On("Address").Return("test").Once()
-	client.On("AggregateSyncCommitteeSelections", mock.Anything, partsel).Return(selections, nil).Once()
-
-	m := eth2wrap.NewMultiForT([]eth2wrap.Client{client}, nil)
-
-	selections2, err := m.AggregateSyncCommitteeSelections(ctx, partsel)
-	require.NoError(t, err)
-	require.Equal(t, selections, selections2)
-
-	expectedErr := errors.New("boo")
-	client.On("AggregateSyncCommitteeSelections", mock.Anything, partsel).Return(nil, expectedErr).Once()
-	_, err = m.AggregateSyncCommitteeSelections(ctx, partsel)
-	require.ErrorIs(t, err, expectedErr)
-}
-
-func TestMulti_AggregateBeaconCommitteeSelections(t *testing.T) {
-	ctx := context.Background()
-	partsel := make([]*eth2exp.BeaconCommitteeSelection, 1)
-	selections := make([]*eth2exp.BeaconCommitteeSelection, 3)
-
-	client := mocks.NewClient(t)
-	client.On("Address").Return("test").Once()
-	client.On("AggregateBeaconCommitteeSelections", mock.Anything, partsel).Return(selections, nil).Once()
-
-	m := eth2wrap.NewMultiForT([]eth2wrap.Client{client}, nil)
-
-	selections2, err := m.AggregateBeaconCommitteeSelections(ctx, partsel)
-	require.NoError(t, err)
-	require.Equal(t, selections, selections2)
-
-	expectedErr := errors.New("boo")
-	client.On("AggregateBeaconCommitteeSelections", mock.Anything, partsel).Return(nil, expectedErr).Once()
-	_, err = m.AggregateBeaconCommitteeSelections(ctx, partsel)
-	require.ErrorIs(t, err, expectedErr)
-}
-
-func TestMulti_ProposerConfig(t *testing.T) {
-	ctx := context.Background()
-	resp := &eth2exp.ProposerConfigResponse{}
-
-	client := mocks.NewClient(t)
-	client.On("Address").Return("test").Once()
-	client.On("ProposerConfig", mock.Anything).Return(resp, nil).Once()
-
-	m := eth2wrap.NewMultiForT([]eth2wrap.Client{client}, nil)
-
-	resp2, err := m.ProposerConfig(ctx)
-	require.NoError(t, err)
-	require.Equal(t, resp, resp2)
-
-	expectedErr := errors.New("boo")
-	client.On("ProposerConfig", mock.Anything).Return(nil, expectedErr).Once()
-
-	_, err = m.ProposerConfig(ctx)
-	require.ErrorIs(t, err, expectedErr)
-}
-
 func TestMulti_ActiveValidators(t *testing.T) {
 	ctx := context.Background()
 	vals := make(eth2wrap.ActiveValidators)
diff --git a/app/monitoringapi_internal_test.go b/app/monitoringapi_internal_test.go
index 9a03ceed..256f542f 100644
--- a/app/monitoringapi_internal_test.go
+++ b/app/monitoringapi_internal_test.go
@@ -117,8 +117,8 @@ func TestStartChecker(t *testing.T) {
 			}
 
 			if tt.zeroBNPeers {
-				bmock.NodePeerCountFunc = func(ctx context.Context) (int, error) {
-					return 0, nil
+				bmock.NodePeerCountFunc = func(ctx context.Context, _ *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error) {
+					return &eth2v1.PeerCount{Connected: 0}, nil
 				}
 			}
 
diff --git a/core/fetcher/fetcher_test.go b/core/fetcher/fetcher_test.go
index 5c4ae39c..9262a3e5 100644
--- a/core/fetcher/fetcher_test.go
+++ b/core/fetcher/fetcher_test.go
@@ -22,7 +22,6 @@ import (
 	"github.com/obolnetwork/charon/app/eth2wrap/mocks"
 	"github.com/obolnetwork/charon/core"
 	"github.com/obolnetwork/charon/core/fetcher"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/testutil"
 	"github.com/obolnetwork/charon/testutil/beaconmock"
 )
@@ -385,14 +384,14 @@ func TestFetchSyncContribution(t *testing.T) {
 		sigB = "99e60f20dde4d4872b048d703f1943071c20213d504012e7e520c229da87661803b9f139b9a0c5be31de3cef6821c080125aed38ebaf51ba9a2e9d21d7fbf2903577983109d097a8599610a92c0305408d97c1fd4b0b2d1743fb4eedf5443f99"
 	)
 	// Construct sync committee selections.
-	selectionA := &eth2exp.SyncCommitteeSelection{
+	selectionA := &eth2v1.SyncCommitteeSelection{
 		ValidatorIndex:    vIdxA,
 		Slot:              slot,
 		SubcommitteeIndex: subCommIdxA,
 		SelectionProof:    blsSigFromHex(t, sigA),
 	}
 
-	selectionB := &eth2exp.SyncCommitteeSelection{
+	selectionB := &eth2v1.SyncCommitteeSelection{
 		ValidatorIndex:    vIdxB,
 		Slot:              slot,
 		SubcommitteeIndex: subCommIdxB,
@@ -439,7 +438,7 @@ func TestFetchSyncContribution(t *testing.T) {
 				s, ok := selection.(core.SyncCommitteeSelection)
 				require.True(t, ok)
 
-				if s.SubcommitteeIndex == eth2p0.CommitteeIndex(subcommitteeIndex) {
+				if s.SubcommitteeIndex == subcommitteeIndex {
 					signedSelection = s
 				}
 			}
@@ -489,12 +488,12 @@ func TestFetchSyncContribution(t *testing.T) {
 				}
 
 				require.Equal(t, eth2p0.Slot(slot), selection.Slot)
-				require.Equal(t, eth2p0.CommitteeIndex(contribution.SubcommitteeIndex), selection.SubcommitteeIndex)
+				require.Equal(t, contribution.SubcommitteeIndex, selection.SubcommitteeIndex)
 
 				if selection.ValidatorIndex == eth2p0.ValidatorIndex(vIdxA) {
-					require.Equal(t, eth2p0.CommitteeIndex(subCommIdxA), selection.SubcommitteeIndex)
+					require.Equal(t, uint64(subCommIdxA), selection.SubcommitteeIndex)
 				} else if selection.ValidatorIndex == eth2p0.ValidatorIndex(vIdxB) {
-					require.Equal(t, eth2p0.CommitteeIndex(subCommIdxB), selection.SubcommitteeIndex)
+					require.Equal(t, uint64(subCommIdxB), selection.SubcommitteeIndex)
 				}
 
 				message, ok := syncMsgsByPubkey[pubkey].(core.SignedSyncMessage)
@@ -521,7 +520,7 @@ func TestFetchSyncContribution(t *testing.T) {
 				// Signature corresponding to a non-aggregator. Refer https://github.com/prysmaticlabs/prysm/blob/39a7988e9edbed5b517229b4d66c2a8aab7c7b4d/beacon-chain/sync/validate_sync_contribution_proof_test.go#L336
 				sig := "b9251a82040d4620b8c5665f328ee6c2eaa02d31d71d153f4abba31a7922a981e541e85283f0ced387d26e86aef9386d18c6982b9b5f8759882fe7f25a328180d86e146994ef19d28bc1432baf29751dec12b5f3d65dbbe224d72cf900c6831a"
 				resp := blsSigFromHex(t, sig)
-				selection := &eth2exp.SyncCommitteeSelection{
+				selection := &eth2v1.SyncCommitteeSelection{
 					SelectionProof: resp,
 				}
 
diff --git a/core/parsigdb/memory_internal_test.go b/core/parsigdb/memory_internal_test.go
index 97dc7d6c..1e4c0a8b 100644
--- a/core/parsigdb/memory_internal_test.go
+++ b/core/parsigdb/memory_internal_test.go
@@ -6,13 +6,13 @@ import (
 	"context"
 	"testing"
 
+	eth2v1 "github.com/attestantio/go-eth2-client/api/v1"
 	"github.com/attestantio/go-eth2-client/spec/altair"
 	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
 	"github.com/stretchr/testify/require"
 
 	"github.com/obolnetwork/charon/cluster"
 	"github.com/obolnetwork/charon/core"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/testutil"
 )
 
@@ -73,7 +73,7 @@ func TestGetThresholdMatching(t *testing.T) {
 				},
 				"Selection": func(i int) core.ParSignedData {
 					// Message is constant
-					msg := &eth2exp.BeaconCommitteeSelection{
+					msg := &eth2v1.BeaconCommitteeSelection{
 						ValidatorIndex: valIdx,
 						Slot:           eth2p0.Slot(test.input[i]), // Vary slot based on input
 						SelectionProof: testutil.RandomEth2Signature(),
diff --git a/core/parsigex/parsigex_test.go b/core/parsigex/parsigex_test.go
index 5feb7a7e..d4b89706 100644
--- a/core/parsigex/parsigex_test.go
+++ b/core/parsigex/parsigex_test.go
@@ -314,7 +314,7 @@ func TestParSigExVerifier(t *testing.T) {
 
 		data := &altair.SyncAggregatorSelectionData{
 			Slot:              selection.Slot,
-			SubcommitteeIndex: uint64(selection.SubcommitteeIndex),
+			SubcommitteeIndex: selection.SubcommitteeIndex,
 		}
 		sigRoot, err := data.HashTreeRoot()
 		require.NoError(t, err)
diff --git a/core/tracker/inclusion_internal_test.go b/core/tracker/inclusion_internal_test.go
index 68d08ac8..081c12a3 100644
--- a/core/tracker/inclusion_internal_test.go
+++ b/core/tracker/inclusion_internal_test.go
@@ -8,6 +8,8 @@ import (
 	"slices"
 	"testing"
 
+	eth2api "github.com/attestantio/go-eth2-client/api"
+	eth2v1 "github.com/attestantio/go-eth2-client/api/v1"
 	eth2spec "github.com/attestantio/go-eth2-client/spec"
 	"github.com/attestantio/go-eth2-client/spec/electra"
 	eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
@@ -17,7 +19,6 @@ import (
 	"github.com/obolnetwork/charon/app/eth2wrap"
 	"github.com/obolnetwork/charon/app/featureset"
 	"github.com/obolnetwork/charon/core"
-	"github.com/obolnetwork/charon/eth2util/statecomm"
 	"github.com/obolnetwork/charon/testutil"
 	"github.com/obolnetwork/charon/testutil/beaconmock"
 )
@@ -28,10 +29,10 @@ func TestDuplicateAttData(t *testing.T) {
 	featureset.EnableForT(t, featureset.AttestationInclusion)
 
 	tests := []struct {
-		name                      string
-		attData                   *eth2p0.AttestationData
-		attestationsFunc          func(*eth2p0.AttestationData, bitfield.Bitlist, bitfield.Bitlist, bitfield.Bitlist) []*eth2spec.VersionedAttestation
-		beaconStateCommitteesFunc func(*eth2p0.AttestationData) []*statecomm.StateCommittee
+		name                 string
+		attData              *eth2p0.AttestationData
+		attestationsFunc     func(*eth2p0.AttestationData, bitfield.Bitlist, bitfield.Bitlist, bitfield.Bitlist) []*eth2spec.VersionedAttestation
+		beaconCommitteesFunc func(*eth2p0.AttestationData) []*eth2v1.BeaconCommittee
 	}{
 		{
 			name:    "phase0",
@@ -43,8 +44,8 @@ func TestDuplicateAttData(t *testing.T) {
 					{Version: eth2spec.DataVersionPhase0, Phase0: &eth2p0.Attestation{AggregationBits: aggBits3, Data: attData}},
 				}
 			},
-			beaconStateCommitteesFunc: func(attData *eth2p0.AttestationData) []*statecomm.StateCommittee {
-				return []*statecomm.StateCommittee{
+			beaconCommitteesFunc: func(attData *eth2p0.AttestationData) []*eth2v1.BeaconCommittee {
+				return []*eth2v1.BeaconCommittee{
 					{Index: attData.Index, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1}},
 				}
 			},
@@ -59,8 +60,8 @@ func TestDuplicateAttData(t *testing.T) {
 					{Version: eth2spec.DataVersionAltair, Altair: &eth2p0.Attestation{AggregationBits: aggBits3, Data: attData}},
 				}
 			},
-			beaconStateCommitteesFunc: func(attData *eth2p0.AttestationData) []*statecomm.StateCommittee {
-				return []*statecomm.StateCommittee{
+			beaconCommitteesFunc: func(attData *eth2p0.AttestationData) []*eth2v1.BeaconCommittee {
+				return []*eth2v1.BeaconCommittee{
 					{Index: attData.Index, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1}},
 				}
 			},
@@ -75,8 +76,8 @@ func TestDuplicateAttData(t *testing.T) {
 					{Version: eth2spec.DataVersionBellatrix, Bellatrix: &eth2p0.Attestation{AggregationBits: aggBits3, Data: attData}},
 				}
 			},
-			beaconStateCommitteesFunc: func(attData *eth2p0.AttestationData) []*statecomm.StateCommittee {
-				return []*statecomm.StateCommittee{
+			beaconCommitteesFunc: func(attData *eth2p0.AttestationData) []*eth2v1.BeaconCommittee {
+				return []*eth2v1.BeaconCommittee{
 					{Index: attData.Index, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1}},
 				}
 			},
@@ -91,8 +92,8 @@ func TestDuplicateAttData(t *testing.T) {
 					{Version: eth2spec.DataVersionCapella, Capella: &eth2p0.Attestation{AggregationBits: aggBits3, Data: attData}},
 				}
 			},
-			beaconStateCommitteesFunc: func(attData *eth2p0.AttestationData) []*statecomm.StateCommittee {
-				return []*statecomm.StateCommittee{
+			beaconCommitteesFunc: func(attData *eth2p0.AttestationData) []*eth2v1.BeaconCommittee {
+				return []*eth2v1.BeaconCommittee{
 					{Index: attData.Index, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1}},
 				}
 			},
@@ -107,8 +108,8 @@ func TestDuplicateAttData(t *testing.T) {
 					{Version: eth2spec.DataVersionDeneb, Deneb: &eth2p0.Attestation{AggregationBits: aggBits3, Data: attData}},
 				}
 			},
-			beaconStateCommitteesFunc: func(attData *eth2p0.AttestationData) []*statecomm.StateCommittee {
-				return []*statecomm.StateCommittee{
+			beaconCommitteesFunc: func(attData *eth2p0.AttestationData) []*eth2v1.BeaconCommittee {
+				return []*eth2v1.BeaconCommittee{
 					{Index: attData.Index, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1}},
 				}
 			},
@@ -130,8 +131,8 @@ func TestDuplicateAttData(t *testing.T) {
 					{Version: eth2spec.DataVersionElectra, Electra: &electra.Attestation{AggregationBits: aggBits3, Data: attData, CommitteeBits: twoComm}},
 				}
 			},
-			beaconStateCommitteesFunc: func(attData *eth2p0.AttestationData) []*statecomm.StateCommittee {
-				return []*statecomm.StateCommittee{
+			beaconCommitteesFunc: func(attData *eth2p0.AttestationData) []*eth2v1.BeaconCommittee {
+				return []*eth2v1.BeaconCommittee{
 					{Index: 0, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1, 2}},
 					{Index: 1, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1, 2}},
 					{Index: 2, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1, 2}},
@@ -154,8 +155,8 @@ func TestDuplicateAttData(t *testing.T) {
 					{Version: eth2spec.DataVersionElectra, Electra: &electra.Attestation{AggregationBits: aggBits2, Data: attData, CommitteeBits: oneComm}},
 				}
 			},
-			beaconStateCommitteesFunc: func(attData *eth2p0.AttestationData) []*statecomm.StateCommittee {
-				return []*statecomm.StateCommittee{
+			beaconCommitteesFunc: func(attData *eth2p0.AttestationData) []*eth2v1.BeaconCommittee {
+				return []*eth2v1.BeaconCommittee{
 					{Index: 0, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1, 2}},
 					{Index: 1, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1, 2}},
 					{Index: 2, Slot: attData.Slot, Validators: []eth2p0.ValidatorIndex{0, 1, 2}},
@@ -175,12 +176,12 @@ func TestDuplicateAttData(t *testing.T) {
 			aggBits2 := testutil.RandomBitList(8)
 			aggBits3 := testutil.RandomBitList(8)
 
-			bmock.BlockAttestationsFunc = func(_ context.Context, _ string) ([]*eth2spec.VersionedAttestation, error) {
+			bmock.BeaconBlockAttestationsFunc = func(_ context.Context, _ *eth2api.BeaconBlockAttestationsOpts) ([]*eth2spec.VersionedAttestation, error) {
 				return test.attestationsFunc(attData, aggBits1, aggBits2, aggBits3), nil
 			}
 
-			bmock.BeaconStateCommitteesFunc = func(_ context.Context, slot uint64) ([]*statecomm.StateCommittee, error) {
-				return test.beaconStateCommitteesFunc(attData), nil
+			bmock.BeaconCommitteesFunc = func(_ context.Context, opts *eth2api.BeaconCommitteesOpts) ([]*eth2v1.BeaconCommittee, error) {
+				return test.beaconCommitteesFunc(attData), nil
 			}
 
 			noopTrackerInclFunc := func(duty core.Duty, key core.PubKey, data core.SignedData, err error) {}
diff --git a/core/validatorapi/router_internal_test.go b/core/validatorapi/router_internal_test.go
index 744161ef..524419e3 100644
--- a/core/validatorapi/router_internal_test.go
+++ b/core/validatorapi/router_internal_test.go
@@ -40,7 +40,6 @@ import (
 
 	"github.com/obolnetwork/charon/app/errors"
 	"github.com/obolnetwork/charon/app/eth2wrap"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/testutil"
 )
 
@@ -1392,23 +1391,23 @@ func TestRouter(t *testing.T) {
 	})
 
 	t.Run("aggregate sync committee selections", func(t *testing.T) {
-		selections := []*eth2exp.SyncCommitteeSelection{testutil.RandomSyncCommitteeSelection(), testutil.RandomSyncCommitteeSelection()}
+		selections := []*eth2v1.SyncCommitteeSelection{testutil.RandomSyncCommitteeSelection(), testutil.RandomSyncCommitteeSelection()}
 
 		handler := testHandler{
-			AggregateSyncCommitteeSelectionsFunc: func(ctx context.Context, partialSelections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
+			SyncCommitteeSelectionsFunc: func(ctx context.Context, opts *eth2api.SyncCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeSelection], error) {
 				for i := range selections {
-					require.Equal(t, selections[i], partialSelections[i])
+					require.Equal(t, selections[i], opts.Selections[i])
 				}
 
-				return partialSelections, nil
+				return wrapResponse(opts.Selections), nil
 			},
 		}
 
 		callback := func(ctx context.Context, cl *eth2http.Service) {
 			eth2Cl := eth2wrap.AdaptEth2HTTP(cl, nil, time.Second)
-			actual, err := eth2Cl.AggregateSyncCommitteeSelections(ctx, selections)
+			actual, err := eth2Cl.SyncCommitteeSelections(ctx, &eth2api.SyncCommitteeSelectionsOpts{Selections: selections})
 			require.NoError(t, err)
-			require.Equal(t, selections, actual)
+			require.Equal(t, selections, actual.Data)
 		}
 
 		testRouter(t, handler, callback)
@@ -1447,8 +1446,8 @@ func TestBeaconCommitteeSelections(t *testing.T) {
 	)
 
 	handler := testHandler{
-		AggregateBeaconCommitteeSelectionsFunc: func(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-			return selections, nil
+		BeaconCommitteeSelectionsFunc: func(ctx context.Context, opts *eth2api.BeaconCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.BeaconCommitteeSelection], error) {
+			return wrapResponse(opts.Selections), nil
 		},
 	}
 
@@ -1469,7 +1468,7 @@ func TestBeaconCommitteeSelections(t *testing.T) {
 	)
 	require.NoError(t, err)
 
-	selections := []*eth2exp.BeaconCommitteeSelection{
+	selections := []*eth2v1.BeaconCommitteeSelection{
 		{
 			Slot:           slotA,
 			ValidatorIndex: vIdxA,
@@ -1488,9 +1487,9 @@ func TestBeaconCommitteeSelections(t *testing.T) {
 	}
 
 	eth2Cl := eth2wrap.AdaptEth2HTTP(eth2Svc.(*eth2http.Service), nil, time.Second)
-	actual, err := eth2Cl.AggregateBeaconCommitteeSelections(ctx, selections)
+	actual, err := eth2Cl.BeaconCommitteeSelections(ctx, &eth2api.BeaconCommitteeSelectionsOpts{Selections: selections})
 	require.NoError(t, err)
-	require.Equal(t, selections, actual)
+	require.Equal(t, selections, actual.Data)
 }
 
 func TestSubmitAggregateAttestations(t *testing.T) {
@@ -1990,26 +1989,26 @@ type testHandler struct {
 	Handler
 	eth2client.BeaconStateProvider
 
-	ProxyHandler                           http.HandlerFunc
-	AggregateSyncCommitteeSelectionsFunc   func(ctx context.Context, partialSelections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error)
-	AttestationDataFunc                    func(ctx context.Context, opts *eth2api.AttestationDataOpts) (*eth2api.Response[*eth2p0.AttestationData], error)
-	AttesterDutiesFunc                     func(ctx context.Context, opts *eth2api.AttesterDutiesOpts) (*eth2api.Response[[]*eth2v1.AttesterDuty], error)
-	SubmitAttestationsFunc                 func(ctx context.Context, opts *eth2api.SubmitAttestationsOpts) error
-	ProposalFunc                           func(ctx context.Context, opts *eth2api.ProposalOpts) (*eth2api.Response[*eth2api.VersionedProposal], error)
-	SubmitProposalFunc                     func(ctx context.Context, proposal *eth2api.SubmitProposalOpts) error
-	SubmitBlindedProposalFunc              func(ctx context.Context, proposal *eth2api.SubmitBlindedProposalOpts) error
-	ProposerDutiesFunc                     func(ctx context.Context, opts *eth2api.ProposerDutiesOpts) (*eth2api.Response[[]*eth2v1.ProposerDuty], error)
-	NodeVersionFunc                        func(ctx context.Context, opts *eth2api.NodeVersionOpts) (*eth2api.Response[string], error)
-	ValidatorsFunc                         func(ctx context.Context, opts *eth2api.ValidatorsOpts) (*eth2api.Response[map[eth2p0.ValidatorIndex]*eth2v1.Validator], error)
-	BeaconStateFunc                        func(ctx context.Context, stateId string) (*eth2spec.VersionedBeaconState, error)
-	ValidatorsByPubKeyFunc                 func(ctx context.Context, stateID string, pubkeys []eth2p0.BLSPubKey) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error)
-	SubmitVoluntaryExitFunc                func(ctx context.Context, exit *eth2p0.SignedVoluntaryExit) error
-	SubmitValidatorRegistrationsFunc       func(ctx context.Context, registrations []*eth2api.VersionedSignedValidatorRegistration) error
-	AggregateBeaconCommitteeSelectionsFunc func(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error)
-	SubmitAggregateAttestationsFunc        func(ctx context.Context, opts *eth2api.SubmitAggregateAttestationsOpts) error
-	SubmitSyncCommitteeMessagesFunc        func(ctx context.Context, messages []*altair.SyncCommitteeMessage) error
-	SyncCommitteeDutiesFunc                func(ctx context.Context, opts *eth2api.SyncCommitteeDutiesOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeDuty], error)
-	SyncCommitteeContributionFunc          func(ctx context.Context, opts *eth2api.SyncCommitteeContributionOpts) (*eth2api.Response[*altair.SyncCommitteeContribution], error)
+	ProxyHandler                     http.HandlerFunc
+	SyncCommitteeSelectionsFunc      func(ctx context.Context, opts *eth2api.SyncCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeSelection], error)
+	AttestationDataFunc              func(ctx context.Context, opts *eth2api.AttestationDataOpts) (*eth2api.Response[*eth2p0.AttestationData], error)
+	AttesterDutiesFunc               func(ctx context.Context, opts *eth2api.AttesterDutiesOpts) (*eth2api.Response[[]*eth2v1.AttesterDuty], error)
+	SubmitAttestationsFunc           func(ctx context.Context, opts *eth2api.SubmitAttestationsOpts) error
+	ProposalFunc                     func(ctx context.Context, opts *eth2api.ProposalOpts) (*eth2api.Response[*eth2api.VersionedProposal], error)
+	SubmitProposalFunc               func(ctx context.Context, proposal *eth2api.SubmitProposalOpts) error
+	SubmitBlindedProposalFunc        func(ctx context.Context, proposal *eth2api.SubmitBlindedProposalOpts) error
+	ProposerDutiesFunc               func(ctx context.Context, opts *eth2api.ProposerDutiesOpts) (*eth2api.Response[[]*eth2v1.ProposerDuty], error)
+	NodeVersionFunc                  func(ctx context.Context, opts *eth2api.NodeVersionOpts) (*eth2api.Response[string], error)
+	ValidatorsFunc                   func(ctx context.Context, opts *eth2api.ValidatorsOpts) (*eth2api.Response[map[eth2p0.ValidatorIndex]*eth2v1.Validator], error)
+	BeaconStateFunc                  func(ctx context.Context, stateId string) (*eth2spec.VersionedBeaconState, error)
+	ValidatorsByPubKeyFunc           func(ctx context.Context, stateID string, pubkeys []eth2p0.BLSPubKey) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error)
+	SubmitVoluntaryExitFunc          func(ctx context.Context, exit *eth2p0.SignedVoluntaryExit) error
+	SubmitValidatorRegistrationsFunc func(ctx context.Context, registrations []*eth2api.VersionedSignedValidatorRegistration) error
+	BeaconCommitteeSelectionsFunc    func(ctx context.Context, opts *eth2api.BeaconCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.BeaconCommitteeSelection], error)
+	SubmitAggregateAttestationsFunc  func(ctx context.Context, opts *eth2api.SubmitAggregateAttestationsOpts) error
+	SubmitSyncCommitteeMessagesFunc  func(ctx context.Context, messages []*altair.SyncCommitteeMessage) error
+	SyncCommitteeDutiesFunc          func(ctx context.Context, opts *eth2api.SyncCommitteeDutiesOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeDuty], error)
+	SyncCommitteeContributionFunc    func(ctx context.Context, opts *eth2api.SyncCommitteeContributionOpts) (*eth2api.Response[*altair.SyncCommitteeContribution], error)
 }
 
 func (h testHandler) AttestationData(ctx context.Context, opts *eth2api.AttestationDataOpts) (*eth2api.Response[*eth2p0.AttestationData], error) {
@@ -2068,8 +2067,8 @@ func (h testHandler) SubmitValidatorRegistrations(ctx context.Context, registrat
 	return h.SubmitValidatorRegistrationsFunc(ctx, registrations)
 }
 
-func (h testHandler) AggregateBeaconCommitteeSelections(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	return h.AggregateBeaconCommitteeSelectionsFunc(ctx, selections)
+func (h testHandler) BeaconCommitteeSelections(ctx context.Context, opts *eth2api.BeaconCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.BeaconCommitteeSelection], error) {
+	return h.BeaconCommitteeSelectionsFunc(ctx, opts)
 }
 
 func (h testHandler) SubmitAggregateAttestations(ctx context.Context, opts *eth2api.SubmitAggregateAttestationsOpts) error {
@@ -2088,8 +2087,8 @@ func (h testHandler) SyncCommitteeContribution(ctx context.Context, opts *eth2ap
 	return h.SyncCommitteeContributionFunc(ctx, opts)
 }
 
-func (h testHandler) AggregateSyncCommitteeSelections(ctx context.Context, partialSelections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-	return h.AggregateSyncCommitteeSelectionsFunc(ctx, partialSelections)
+func (h testHandler) SyncCommitteeSelections(ctx context.Context, opts *eth2api.SyncCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeSelection], error) {
+	return h.SyncCommitteeSelectionsFunc(ctx, opts)
 }
 
 // newBeaconHandler returns a mock beacon node handler. It registers a few mock handlers required by the
diff --git a/core/validatorapi/validatorapi_test.go b/core/validatorapi/validatorapi_test.go
index fa268a29..0d7402ee 100644
--- a/core/validatorapi/validatorapi_test.go
+++ b/core/validatorapi/validatorapi_test.go
@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"maps"
 	"sort"
-	"strconv"
 	"sync"
 	"testing"
 	"time"
@@ -33,7 +32,6 @@ import (
 	"github.com/obolnetwork/charon/core"
 	"github.com/obolnetwork/charon/core/validatorapi"
 	"github.com/obolnetwork/charon/eth2util"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/eth2util/signing"
 	"github.com/obolnetwork/charon/tbls"
 	"github.com/obolnetwork/charon/tbls/tblsconv"
@@ -1884,80 +1882,6 @@ func TestComponent_SubmitValidatorRegistrationInvalidSignature(t *testing.T) {
 	require.ErrorContains(t, err, "signature not verified")
 }
 
-func TestComponent_TekuProposerConfig(t *testing.T) {
-	ctx := context.Background()
-
-	const (
-		zeroAddr     = "0x0000000000000000000000000000000000000000"
-		feeRecipient = "0x123456"
-		shareIdx     = 1
-	)
-	// Create keys (just use normal keys, not split tbls)
-	secret, err := tbls.GenerateSecretKey()
-	require.NoError(t, err)
-
-	pubkey, err := tbls.SecretToPublicKey(secret)
-	require.NoError(t, err)
-
-	// Convert pubkey
-	corePubKey, err := core.PubKeyFromBytes(pubkey[:])
-	require.NoError(t, err)
-
-	allPubSharesByKey := map[core.PubKey]map[int]tbls.PublicKey{corePubKey: {shareIdx: pubkey}} // Maps self to self since not tbls
-
-	// Configure beacon mock
-	bmock, err := beaconmock.New()
-	require.NoError(t, err)
-
-	// Construct the validator api component
-	vapi, err := validatorapi.NewComponent(bmock, allPubSharesByKey, shareIdx, func(core.PubKey) string {
-		return feeRecipient
-	}, true, 30000000, nil)
-	require.NoError(t, err)
-
-	resp, err := vapi.ProposerConfig(ctx)
-	require.NoError(t, err)
-
-	pk, err := core.PubKeyFromBytes(pubkey[:])
-	require.NoError(t, err)
-
-	genesis, err := bmock.Genesis(ctx, &eth2api.GenesisOpts{})
-	require.NoError(t, err)
-
-	genesisTime := genesis.Data.GenesisTime
-	respSpec, err := bmock.Spec(ctx, &eth2api.SpecOpts{})
-	require.NoError(t, err)
-
-	slotDuration, ok := respSpec.Data["SECONDS_PER_SLOT"].(time.Duration)
-	require.True(t, ok)
-
-	eth2pk, err := pk.ToETH2()
-	require.NoError(t, err)
-
-	require.Equal(t, &eth2exp.ProposerConfigResponse{
-		Proposers: map[eth2p0.BLSPubKey]eth2exp.ProposerConfig{
-			eth2pk: {
-				FeeRecipient: feeRecipient,
-				Builder: eth2exp.Builder{
-					Enabled:  true,
-					GasLimit: 30000000,
-					Overrides: map[string]string{
-						"timestamp":  strconv.FormatInt(genesisTime.Add(slotDuration).Unix(), 10),
-						"public_key": string(pk),
-					},
-				},
-			},
-		},
-		Default: eth2exp.ProposerConfig{
-			FeeRecipient: zeroAddr,
-			Builder: eth2exp.Builder{
-				Enabled:  false,
-				GasLimit: 30000000,
-			},
-		},
-	}, resp)
-}
-
 func TestComponent_AggregateBeaconCommitteeSelections(t *testing.T) {
 	ctx := context.Background()
 
@@ -1970,7 +1894,7 @@ func TestComponent_AggregateBeaconCommitteeSelections(t *testing.T) {
 	vapi, err := validatorapi.NewComponentInsecure(t, eth2Cl, 0)
 	require.NoError(t, err)
 
-	selections := []*eth2exp.BeaconCommitteeSelection{
+	selections := []*eth2v1.BeaconCommitteeSelection{
 		{
 			ValidatorIndex: valSet[1].Index,
 			Slot:           slot,
@@ -2003,9 +1927,11 @@ func TestComponent_AggregateBeaconCommitteeSelections(t *testing.T) {
 		return nil, errors.New("unknown public key")
 	})
 
-	actual, err := vapi.AggregateBeaconCommitteeSelections(ctx, selections)
+	eth2Resp, err := vapi.BeaconCommitteeSelections(ctx, &eth2api.BeaconCommitteeSelectionsOpts{Selections: selections})
 	require.NoError(t, err)
 
+	actual := eth2Resp.Data
+
 	// Sort by VIdx before comparing
 	sort.Slice(actual, func(i, j int) bool {
 		return actual[i].ValidatorIndex < actual[j].ValidatorIndex
@@ -2139,7 +2065,7 @@ func TestComponent_SubmitSyncCommitteeContributionsVerify(t *testing.T) {
 		ctx        = context.Background()
 		val        = testutil.RandomValidator(t)
 		slot       = eth2p0.Slot(50)
-		subcommIdx = eth2p0.CommitteeIndex(1)
+		subcommIdx = uint64(1)
 	)
 
 	// Create keys (just use normal keys, not split tbls).
@@ -2165,7 +2091,7 @@ func TestComponent_SubmitSyncCommitteeContributionsVerify(t *testing.T) {
 		Contribution:    testutil.RandomSyncCommitteeContribution(),
 	}
 	contribAndProof.Contribution.Slot = slot
-	contribAndProof.Contribution.SubcommitteeIndex = uint64(subcommIdx)
+	contribAndProof.Contribution.SubcommitteeIndex = subcommIdx
 	contribAndProof.SelectionProof = syncCommSelectionProof(t, bmock, secret, slot, subcommIdx)
 
 	signedContribAndProof := &altair.SignedContributionAndProof{
@@ -2432,7 +2358,7 @@ func TestComponent_AggregateSyncCommitteeSelectionsVerify(t *testing.T) {
 	selection1.SelectionProof = syncCommSelectionProof(t, bmock, secret1, slot, selection1.SubcommitteeIndex)
 	selection2.SelectionProof = syncCommSelectionProof(t, bmock, secret2, slot, selection2.SubcommitteeIndex)
 
-	selections := []*eth2exp.SyncCommitteeSelection{selection1, selection2}
+	selections := []*eth2v1.SyncCommitteeSelection{selection1, selection2}
 
 	// Populate all pubshares map.
 	corePubKey1, err := core.PubKeyFromBytes(pubkey1[:])
@@ -2485,9 +2411,11 @@ func TestComponent_AggregateSyncCommitteeSelectionsVerify(t *testing.T) {
 		return nil
 	})
 
-	got, err := vapi.AggregateSyncCommitteeSelections(ctx, selections)
+	eth2Resp, err := vapi.SyncCommitteeSelections(ctx, &eth2api.SyncCommitteeSelectionsOpts{Selections: selections})
 	require.NoError(t, err)
 
+	got := eth2Resp.Data
+
 	// Sort by VIdx before comparing.
 	sort.Slice(got, func(i, j int) bool {
 		return got[i].ValidatorIndex < got[j].ValidatorIndex
@@ -2498,7 +2426,7 @@ func TestComponent_AggregateSyncCommitteeSelectionsVerify(t *testing.T) {
 
 // syncCommSelectionProof returns the selection_proof corresponding to the provided altair.ContributionAndProof.
 // Refer get_sync_committee_selection_proof from https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/validator.md#aggregation-selection.
-func syncCommSelectionProof(t *testing.T, eth2Cl eth2wrap.Client, secret tbls.PrivateKey, slot eth2p0.Slot, subcommIdx eth2p0.CommitteeIndex) eth2p0.BLSSignature {
+func syncCommSelectionProof(t *testing.T, eth2Cl eth2wrap.Client, secret tbls.PrivateKey, slot eth2p0.Slot, subcommIdx uint64) eth2p0.BLSSignature {
 	t.Helper()
 
 	epoch, err := eth2util.EpochFromSlot(context.Background(), eth2Cl, slot)
@@ -2506,7 +2434,7 @@ func syncCommSelectionProof(t *testing.T, eth2Cl eth2wrap.Client, secret tbls.Pr
 
 	data := altair.SyncAggregatorSelectionData{
 		Slot:              slot,
-		SubcommitteeIndex: uint64(subcommIdx),
+		SubcommitteeIndex: subcommIdx,
 	}
 
 	sigRoot, err := data.HashTreeRoot()
diff --git a/dkg/dkg_test.go b/dkg/dkg_test.go
index 55c3093b..1d5f1fdc 100644
--- a/dkg/dkg_test.go
+++ b/dkg/dkg_test.go
@@ -57,13 +57,7 @@ const (
 )
 
 func isAnyVersion(version string, list ...string) bool {
-	for _, v := range list {
-		if version == v {
-			return true
-		}
-	}
-
-	return false
+	return slices.Contains(list, version)
 }
 
 func TestDKG(t *testing.T) {
diff --git a/testutil/beaconmock/beaconmock.go b/testutil/beaconmock/beaconmock.go
index 3ebebe63..9ac07ca9 100644
--- a/testutil/beaconmock/beaconmock.go
+++ b/testutil/beaconmock/beaconmock.go
@@ -38,8 +38,6 @@ import (
 
 	"github.com/obolnetwork/charon/app/errors"
 	"github.com/obolnetwork/charon/app/eth2wrap"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
-	"github.com/obolnetwork/charon/eth2util/statecomm"
 )
 
 // Interface assertions.
@@ -193,10 +191,10 @@ type Mock struct {
 	CachedValidatorsFunc                   func(ctx context.Context) (eth2wrap.ActiveValidators, eth2wrap.CompleteValidators, error)
 	AttestationDataFunc                    func(context.Context, eth2p0.Slot, eth2p0.CommitteeIndex) (*eth2p0.AttestationData, error)
 	AttesterDutiesFunc                     func(context.Context, eth2p0.Epoch, []eth2p0.ValidatorIndex) ([]*eth2v1.AttesterDuty, error)
-	BlockAttestationsFunc                  func(ctx context.Context, stateID string) ([]*eth2spec.VersionedAttestation, error)
 	BlockFunc                              func(ctx context.Context, stateID string) (*eth2spec.VersionedSignedBeaconBlock, error)
-	BeaconStateCommitteesFunc              func(ctx context.Context, slot uint64) ([]*statecomm.StateCommittee, error)
-	NodePeerCountFunc                      func(ctx context.Context) (int, error)
+	BeaconBlockAttestationsFunc            func(context.Context, *eth2api.BeaconBlockAttestationsOpts) ([]*eth2spec.VersionedAttestation, error)
+	BeaconCommitteesFunc                   func(ctx context.Context, opts *eth2api.BeaconCommitteesOpts) ([]*eth2v1.BeaconCommittee, error)
+	NodePeerCountFunc                      func(ctx context.Context, opts *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error)
 	ProposalFunc                           func(ctx context.Context, opts *eth2api.ProposalOpts) (*eth2api.VersionedProposal, error)
 	SignedBeaconBlockFunc                  func(ctx context.Context, blockID string) (*eth2spec.VersionedSignedBeaconBlock, error)
 	ProposerDutiesFunc                     func(context.Context, eth2p0.Epoch, []eth2p0.ValidatorIndex) ([]*eth2v1.ProposerDuty, error)
@@ -210,8 +208,8 @@ type Mock struct {
 	NodeSyncingFunc                        func(context.Context, *eth2api.NodeSyncingOpts) (*eth2v1.SyncState, error)
 	SubmitValidatorRegistrationsFunc       func(context.Context, []*eth2api.VersionedSignedValidatorRegistration) error
 	SlotsPerEpochFunc                      func(context.Context) (uint64, error)
-	AggregateBeaconCommitteeSelectionsFunc func(context.Context, []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error)
-	AggregateSyncCommitteeSelectionsFunc   func(context.Context, []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error)
+	BeaconCommitteeSelectionsFunc          func(context.Context, *eth2api.BeaconCommitteeSelectionsOpts) ([]*eth2v1.BeaconCommitteeSelection, error)
+	SyncCommitteeSelectionsFunc            func(context.Context, *eth2api.SyncCommitteeSelectionsOpts) ([]*eth2v1.SyncCommitteeSelection, error)
 	SubmitBeaconCommitteeSubscriptionsFunc func(ctx context.Context, subscriptions []*eth2v1.BeaconCommitteeSubscription) error
 	AggregateAttestationFunc               func(ctx context.Context, slot eth2p0.Slot, attestationDataRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error)
 	SubmitAggregateAttestationsFunc        func(ctx context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error
@@ -222,7 +220,6 @@ type Mock struct {
 	SubmitSyncCommitteeSubscriptionsFunc   func(ctx context.Context, subscriptions []*eth2v1.SyncCommitteeSubscription) error
 	SubmitProposalPreparationsFunc         func(ctx context.Context, preparations []*eth2v1.ProposalPreparation) error
 	ForkScheduleFunc                       func(context.Context, *eth2api.ForkScheduleOpts) ([]*eth2p0.Fork, error)
-	ProposerConfigFunc                     func(context.Context) (*eth2exp.ProposerConfigResponse, error)
 	NodeVersionFunc                        func(context.Context, *eth2api.NodeVersionOpts) (*eth2api.Response[string], error)
 }
 
@@ -356,20 +353,49 @@ func (m Mock) Genesis(ctx context.Context, opts *eth2api.GenesisOpts) (*eth2api.
 	return wrapResponse(genesis), nil
 }
 
-func (m Mock) BlockAttestations(ctx context.Context, stateID string) ([]*eth2spec.VersionedAttestation, error) {
-	return m.BlockAttestationsFunc(ctx, stateID)
+func (m Mock) BeaconBlockAttestations(ctx context.Context, opts *eth2api.BeaconBlockAttestationsOpts) (*eth2api.Response[[]*eth2spec.VersionedAttestation], error) {
+	atts, err := m.BeaconBlockAttestationsFunc(ctx, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return wrapResponse(atts), nil
+}
+
+func (m Mock) BeaconCommitteeSelections(ctx context.Context, opts *eth2api.BeaconCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.BeaconCommitteeSelection], error) {
+	selections, err := m.BeaconCommitteeSelectionsFunc(ctx, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return wrapResponse(selections), nil
 }
 
-func (m Mock) Block(ctx context.Context, stateID string) (*eth2spec.VersionedSignedBeaconBlock, error) {
-	return m.BlockFunc(ctx, stateID)
+func (m Mock) BeaconCommittees(ctx context.Context, opts *eth2api.BeaconCommitteesOpts) (*eth2api.Response[[]*eth2v1.BeaconCommittee], error) {
+	beaconCommittees, err := m.BeaconCommitteesFunc(ctx, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return wrapResponse(beaconCommittees), nil
 }
 
-func (m Mock) BeaconStateCommittees(ctx context.Context, slot uint64) ([]*statecomm.StateCommittee, error) {
-	return m.BeaconStateCommitteesFunc(ctx, slot)
+func (m Mock) SyncCommitteeSelections(ctx context.Context, opts *eth2api.SyncCommitteeSelectionsOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeSelection], error) {
+	selections, err := m.SyncCommitteeSelectionsFunc(ctx, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return wrapResponse(selections), nil
 }
 
-func (m Mock) NodePeerCount(ctx context.Context) (int, error) {
-	return m.NodePeerCountFunc(ctx)
+func (m Mock) NodePeerCount(ctx context.Context, opts *eth2api.NodePeerCountOpts) (*eth2api.Response[*eth2v1.PeerCount], error) {
+	peerCount, err := m.NodePeerCountFunc(ctx, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return wrapResponse(peerCount), nil
 }
 
 func (m Mock) SubmitAttestations(ctx context.Context, opts *eth2api.SubmitAttestationsOpts) error {
@@ -388,14 +414,6 @@ func (m Mock) SubmitValidatorRegistrations(ctx context.Context, registrations []
 	return m.SubmitValidatorRegistrationsFunc(ctx, registrations)
 }
 
-func (m Mock) AggregateBeaconCommitteeSelections(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-	return m.AggregateBeaconCommitteeSelectionsFunc(ctx, selections)
-}
-
-func (m Mock) AggregateSyncCommitteeSelections(ctx context.Context, selections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-	return m.AggregateSyncCommitteeSelectionsFunc(ctx, selections)
-}
-
 func (m Mock) SubmitBeaconCommitteeSubscriptions(ctx context.Context, subscriptions []*eth2v1.BeaconCommitteeSubscription) error {
 	return m.SubmitBeaconCommitteeSubscriptionsFunc(ctx, subscriptions)
 }
@@ -424,10 +442,6 @@ func (m Mock) SlotsPerEpoch(ctx context.Context) (uint64, error) {
 	return m.SlotsPerEpochFunc(ctx)
 }
 
-func (m Mock) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
-	return m.ProposerConfigFunc(ctx)
-}
-
 func (m Mock) NodeVersion(ctx context.Context, opts *eth2api.NodeVersionOpts) (*eth2api.Response[string], error) {
 	return m.NodeVersionFunc(ctx, opts)
 }
diff --git a/testutil/beaconmock/beaconmock_fuzz.go b/testutil/beaconmock/beaconmock_fuzz.go
index db3fad20..cb4e53eb 100644
--- a/testutil/beaconmock/beaconmock_fuzz.go
+++ b/testutil/beaconmock/beaconmock_fuzz.go
@@ -132,11 +132,11 @@ func WithBeaconMockFuzzer() Option {
 			return vals, nil
 		}
 
-		mock.NodePeerCountFunc = func(context.Context) (int, error) {
-			var count int
+		mock.NodePeerCountFunc = func(context.Context, *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error) {
+			var count uint64
 			fuzz.New().Fuzz(&count)
 
-			return count, nil
+			return &eth2v1.PeerCount{Connected: count}, nil
 		}
 
 		mock.ValidatorsByPubKeyFunc = func(_ context.Context, _ string, pubkeys []eth2p0.BLSPubKey) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) {
@@ -179,7 +179,7 @@ func WithBeaconMockFuzzer() Option {
 			return contribution, nil
 		}
 
-		mock.BlockAttestationsFunc = func(context.Context, string) ([]*eth2spec.VersionedAttestation, error) {
+		mock.BeaconBlockAttestationsFunc = func(context.Context, *eth2api.BeaconBlockAttestationsOpts) ([]*eth2spec.VersionedAttestation, error) {
 			var atts []*eth2spec.VersionedAttestation
 			fuzz.New().Fuzz(&atts)
 
diff --git a/testutil/beaconmock/options.go b/testutil/beaconmock/options.go
index d74edd24..ba85804c 100644
--- a/testutil/beaconmock/options.go
+++ b/testutil/beaconmock/options.go
@@ -28,7 +28,6 @@ import (
 	"github.com/obolnetwork/charon/app/eth2wrap"
 	"github.com/obolnetwork/charon/app/log"
 	"github.com/obolnetwork/charon/core"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/testutil"
 )
 
@@ -555,14 +554,14 @@ func defaultMock(httpMock HTTPMock, httpServer *http.Server, clock clockwork.Clo
 		AttesterDutiesFunc: func(context.Context, eth2p0.Epoch, []eth2p0.ValidatorIndex) ([]*eth2v1.AttesterDuty, error) {
 			return []*eth2v1.AttesterDuty{}, nil
 		},
-		BlockAttestationsFunc: func(context.Context, string) ([]*eth2spec.VersionedAttestation, error) {
+		BeaconBlockAttestationsFunc: func(context.Context, *eth2api.BeaconBlockAttestationsOpts) ([]*eth2spec.VersionedAttestation, error) {
 			return []*eth2spec.VersionedAttestation{}, nil
 		},
 		BlockFunc: func(context.Context, string) (*eth2spec.VersionedSignedBeaconBlock, error) {
 			return &eth2spec.VersionedSignedBeaconBlock{}, nil
 		},
-		NodePeerCountFunc: func(context.Context) (int, error) {
-			return 80, nil
+		NodePeerCountFunc: func(context.Context, *eth2api.NodePeerCountOpts) (*eth2v1.PeerCount, error) {
+			return &eth2v1.PeerCount{Connected: 80}, nil
 		},
 		AttestationDataFunc: func(ctx context.Context, slot eth2p0.Slot, index eth2p0.CommitteeIndex) (*eth2p0.AttestationData, error) {
 			return attStore.NewAttestationData(ctx, slot, index)
@@ -626,8 +625,8 @@ func defaultMock(httpMock HTTPMock, httpServer *http.Server, clock clockwork.Clo
 		SubmitValidatorRegistrationsFunc: func(context.Context, []*eth2api.VersionedSignedValidatorRegistration) error {
 			return nil
 		},
-		AggregateBeaconCommitteeSelectionsFunc: func(_ context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) {
-			return selections, nil
+		BeaconCommitteeSelectionsFunc: func(_ context.Context, opts *eth2api.BeaconCommitteeSelectionsOpts) ([]*eth2v1.BeaconCommitteeSelection, error) {
+			return opts.Selections, nil
 		},
 		SubmitAggregateAttestationsFunc: func(context.Context, *eth2api.SubmitAggregateAttestationsOpts) error {
 			return nil
@@ -650,8 +649,8 @@ func defaultMock(httpMock HTTPMock, httpServer *http.Server, clock clockwork.Clo
 		SyncCommitteeDutiesFunc: func(context.Context, eth2p0.Epoch, []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error) {
 			return []*eth2v1.SyncCommitteeDuty{}, nil
 		},
-		AggregateSyncCommitteeSelectionsFunc: func(_ context.Context, selections []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, error) {
-			return selections, nil
+		SyncCommitteeSelectionsFunc: func(_ context.Context, opts *eth2api.SyncCommitteeSelectionsOpts) ([]*eth2v1.SyncCommitteeSelection, error) {
+			return opts.Selections, nil
 		},
 		SubmitSyncCommitteeMessagesFunc: func(context.Context, []*altair.SyncCommitteeMessage) error {
 			return nil
@@ -682,9 +681,6 @@ func defaultMock(httpMock HTTPMock, httpServer *http.Server, clock clockwork.Clo
 
 			return eth2Resp.Data, nil
 		},
-		ProposerConfigFunc: func(context.Context) (*eth2exp.ProposerConfigResponse, error) {
-			return nil, nil
-		},
 		NodeVersionFunc: func(_ context.Context, _ *eth2api.NodeVersionOpts) (*eth2api.Response[string], error) {
 			return &eth2api.Response[string]{Data: "charon/static_beacon_mock"}, nil
 		},
diff --git a/testutil/random.go b/testutil/random.go
index 91e1bbb5..01bdc9dd 100644
--- a/testutil/random.go
+++ b/testutil/random.go
@@ -41,7 +41,6 @@ import (
 	"github.com/obolnetwork/charon/core"
 	"github.com/obolnetwork/charon/eth2util"
 	"github.com/obolnetwork/charon/eth2util/enr"
-	"github.com/obolnetwork/charon/eth2util/eth2exp"
 	"github.com/obolnetwork/charon/tbls"
 )
 
@@ -848,8 +847,8 @@ func RandomVersionedSignedValidatorRegistration(t *testing.T) *eth2api.Versioned
 	}
 }
 
-func RandomBeaconCommitteeSelection() *eth2exp.BeaconCommitteeSelection {
-	return &eth2exp.BeaconCommitteeSelection{
+func RandomBeaconCommitteeSelection() *eth2v1.BeaconCommitteeSelection {
+	return &eth2v1.BeaconCommitteeSelection{
 		ValidatorIndex: RandomVIdx(),
 		Slot:           RandomSlot(),
 		SelectionProof: RandomEth2Signature(),
@@ -945,11 +944,11 @@ func RandomSyncCommittee(t *testing.T) *altair.SyncCommittee {
 	}
 }
 
-func RandomSyncCommitteeSelection() *eth2exp.SyncCommitteeSelection {
-	return &eth2exp.SyncCommitteeSelection{
+func RandomSyncCommitteeSelection() *eth2v1.SyncCommitteeSelection {
+	return &eth2v1.SyncCommitteeSelection{
 		ValidatorIndex:    RandomVIdx(),
 		Slot:              RandomSlot(),
-		SubcommitteeIndex: RandomCommIdx(),
+		SubcommitteeIndex: uint64(rand.Intn(4)),
 		SelectionProof:    RandomEth2Signature(),
 	}
 }
diff --git a/testutil/validatormock/attest.go b/testutil/validatormock/attest.go
index 2106579d..7c10287b 100644
--- a/testutil/validatormock/attest.go
+++ b/testutil/validatormock/attest.go
@@ -25,7 +25,7 @@ import (
 // Type aliases for concise function signatures.
 type (
 	attDuties     []*eth2v1.AttesterDuty
-	attSelections []*eth2exp.BeaconCommitteeSelection
+	attSelections []*eth2v1.BeaconCommitteeSelection
 	attDatas      []*eth2p0.AttestationData
 )
 
@@ -250,7 +250,7 @@ func prepareAggregators(ctx context.Context, eth2Cl eth2wrap.Client, signFunc Si
 	}
 
 	var (
-		partials    []*eth2exp.BeaconCommitteeSelection
+		partials    []*eth2v1.BeaconCommitteeSelection
 		commLengths = make(map[eth2p0.ValidatorIndex]uint64)
 	)
 	for _, duty := range duties {
@@ -266,18 +266,20 @@ func prepareAggregators(ctx context.Context, eth2Cl eth2wrap.Client, signFunc Si
 
 		commLengths[duty.ValidatorIndex] = duty.CommitteeLength
 
-		partials = append(partials, &eth2exp.BeaconCommitteeSelection{
+		partials = append(partials, &eth2v1.BeaconCommitteeSelection{
 			ValidatorIndex: duty.ValidatorIndex,
 			Slot:           duty.Slot,
 			SelectionProof: slotSig,
 		})
 	}
 
-	aggregateSelections, err := eth2Cl.AggregateBeaconCommitteeSelections(ctx, partials)
+	eth2Resp, err := eth2Cl.BeaconCommitteeSelections(ctx, &eth2api.BeaconCommitteeSelectionsOpts{Selections: partials})
 	if err != nil {
 		return nil, err
 	}
 
+	aggregateSelections := eth2Resp.Data
+
 	var selections attSelections
 
 	for _, selection := range aggregateSelections {
diff --git a/testutil/validatormock/component.go b/testutil/validatormock/component.go
index 1b87b39e..eb7433fd 100644
--- a/testutil/validatormock/component.go
+++ b/testutil/validatormock/component.go
@@ -278,22 +278,6 @@ func (m *Component) runDuty(ctx context.Context, duty core.Duty) error {
 		}
 	case core.DutyBuilderProposer:
 		return core.ErrDeprecatedDutyBuilderProposer
-	case core.DutyBuilderRegistration:
-		if !m.builderAPI {
-			return nil
-		}
-
-		regs, err := RegistrationsFromProposerConfig(ctx, eth2Cl)
-		if err != nil {
-			return err
-		}
-
-		for pubshare, reg := range regs {
-			err = Register(ctx, eth2Cl, m.signFunc, reg, pubshare)
-			if err != nil {
-				return err
-			}
-		}
 	case core.DutyPrepareSyncContribution:
 		if syncComm == nil {
 			return errors.New("syncomm is nil", z.Str("duty", duty.String()))
diff --git a/testutil/validatormock/propose.go b/testutil/validatormock/propose.go
index 3df43dc6..72e808f0 100644
--- a/testutil/validatormock/propose.go
+++ b/testutil/validatormock/propose.go
@@ -5,8 +5,6 @@ package validatormock
 
 import (
 	"context"
-	"encoding/hex"
-	"strings"
 
 	eth2api "github.com/attestantio/go-eth2-client/api"
 	eth2v1 "github.com/attestantio/go-eth2-client/api/v1"
@@ -219,59 +217,6 @@ func ProposeBlock(ctx context.Context, eth2Cl eth2wrap.Client, signFunc SignFunc
 	return eth2Cl.SubmitProposal(ctx, &eth2api.SubmitProposalOpts{Proposal: signedBlock})
 }
 
-// RegistrationsFromProposerConfig returns all enabled builder-API registrations from upstream proposer config.
-func RegistrationsFromProposerConfig(ctx context.Context, eth2Cl eth2wrap.Client) (map[eth2p0.BLSPubKey]*eth2api.VersionedValidatorRegistration, error) {
-	confs, err := eth2Cl.ProposerConfig(ctx)
-	if err != nil {
-		return nil, errors.Wrap(err, "get proposer config")
-	}
-
-	resp := make(map[eth2p0.BLSPubKey]*eth2api.VersionedValidatorRegistration)
-
-	for pubshare, conf := range confs.Proposers {
-		if !conf.Builder.Enabled {
-			continue
-		}
-
-		ts, ok, err := conf.Builder.TimestampOverride()
-		if err != nil {
-			return nil, errors.Wrap(err, "get builder timestamp override")
-		} else if !ok {
-			return nil, errors.Wrap(err, "no builder timestamp override")
-		}
-
-		pubkey, ok, err := conf.Builder.PublicKeyOverride()
-		if err != nil {
-			return nil, errors.Wrap(err, "get builder public key override")
-		} else if !ok {
-			return nil, errors.Wrap(err, "no builder public key override")
-		}
-
-		if conf.Builder.GasLimit == 0 {
-			return nil, errors.New("invalid builder gas limit")
-		}
-
-		feeRecipientBytes, err := hex.DecodeString(strings.TrimPrefix(conf.FeeRecipient, "0x"))
-		if err != nil {
-			return nil, errors.Wrap(err, "decode fee recipient")
-		} else if len(feeRecipientBytes) != 20 {
-			return nil, errors.New("invalid fee recipient")
-		}
-
-		resp[pubshare] = &eth2api.VersionedValidatorRegistration{
-			Version: eth2spec.BuilderVersionV1,
-			V1: &eth2v1.ValidatorRegistration{
-				FeeRecipient: bellatrix.ExecutionAddress(feeRecipientBytes),
-				GasLimit:     uint64(conf.Builder.GasLimit),
-				Timestamp:    ts,
-				Pubkey:       pubkey,
-			},
-		}
-	}
-
-	return resp, nil
-}
-
 // Register signs and submits the validator builder registration to the validator API.
 func Register(ctx context.Context, eth2Cl eth2wrap.Client, signFunc SignFunc,
 	registration *eth2api.VersionedValidatorRegistration, pubshare eth2p0.BLSPubKey,
diff --git a/testutil/validatormock/synccomm.go b/testutil/validatormock/synccomm.go
index 6394269f..f123dd5d 100644
--- a/testutil/validatormock/synccomm.go
+++ b/testutil/validatormock/synccomm.go
@@ -23,7 +23,7 @@ import (
 
 type (
 	syncDuties     []*eth2v1.SyncCommitteeDuty
-	syncSelections []*eth2exp.SyncCommitteeSelection
+	syncSelections []*eth2v1.SyncCommitteeSelection
 )
 
 func NewSyncCommMember(eth2Cl eth2wrap.Client, epoch eth2p0.Epoch, signFunc SignFunc, pubkeys []eth2p0.BLSPubKey) *SyncCommMember {
@@ -314,7 +314,7 @@ func prepareSyncSelections(ctx context.Context, eth2Cl eth2wrap.Client, signFunc
 		return nil, err
 	}
 
-	var partials []*eth2exp.SyncCommitteeSelection
+	var partials []*eth2v1.SyncCommitteeSelection
 
 	for _, duty := range duties {
 		subcommIdxs, err := getSubcommittees(ctx, eth2Cl, duty)
@@ -343,20 +343,22 @@ func prepareSyncSelections(ctx context.Context, eth2Cl eth2wrap.Client, signFunc
 				return nil, err
 			}
 
-			partials = append(partials, &eth2exp.SyncCommitteeSelection{
+			partials = append(partials, &eth2v1.SyncCommitteeSelection{
 				ValidatorIndex:    duty.ValidatorIndex,
 				Slot:              slot,
-				SubcommitteeIndex: subcommIdx,
+				SubcommitteeIndex: uint64(subcommIdx),
 				SelectionProof:    sig,
 			})
 		}
 	}
 
-	aggregateSelections, err := eth2Cl.AggregateSyncCommitteeSelections(ctx, partials)
+	eth2Resp, err := eth2Cl.SyncCommitteeSelections(ctx, &eth2api.SyncCommitteeSelectionsOpts{Selections: partials})
 	if err != nil {
 		return nil, err
 	}
 
+	aggregateSelections := eth2Resp.Data
+
 	var selections syncSelections
 
 	for _, selection := range aggregateSelections {
@@ -466,7 +468,7 @@ func aggContributions(ctx context.Context, eth2Cl eth2wrap.Client, signFunc Sign
 		// Query BN to get sync committee contribution.
 		opts := &eth2api.SyncCommitteeContributionOpts{
 			Slot:              selection.Slot,
-			SubcommitteeIndex: uint64(selection.SubcommitteeIndex),
+			SubcommitteeIndex: selection.SubcommitteeIndex,
 			BeaconBlockRoot:   blockRoot,
 		}