diff --git a/builder/builder.go b/builder/builder.go
index c4c415c1fe72..1aa197d90040 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -41,6 +41,7 @@ type IBuilder interface {
 }
 
 type Builder struct {
+	ds           IDatabaseService
 	beaconClient IBeaconClient
 	relay        IRelay
 	eth          IEthereumService
@@ -55,12 +56,13 @@ type Builder struct {
 	bestBlockProfit *big.Int
 }
 
-func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
+func NewBuilder(sk *bls.SecretKey, ds IDatabaseService, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
 	pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
 	pk := boostTypes.PublicKey{}
 	pk.FromSlice(pkBytes)
 
 	return &Builder{
+		ds:               ds,
 		beaconClient:     bc,
 		relay:            relay,
 		eth:              eth,
@@ -73,7 +75,7 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin
 	}
 }
 
-func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *types.Block, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
+func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
 	b.bestMu.Lock()
 	defer b.bestMu.Unlock()
 
@@ -88,6 +90,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
 		}
 	}
 
+	executableData := beacon.BlockToExecutableData(block)
 	payload, err := executableDataToExecutionPayload(executableData)
 	if err != nil {
 		log.Error("could not format execution payload", "err", err)
@@ -133,6 +136,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
 
 	b.bestBlockProfit.Set(block.Profit)
 
+	go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
 	return nil
 }
 
@@ -166,20 +170,15 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
 		return errors.New("parent block not found in blocktree")
 	}
 
-	firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
-		executableData, block := b.eth.BuildBlock(attrs)
-		if executableData == nil || block == nil {
-			log.Error("did not receive the payload")
-			return errors.New("did not receive the payload")
-		}
-
-		err := b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs)
+	blockHook := func(block *types.Block, bundles []types.SimulatedBundle) {
+		err := b.onSealedBlock(block, bundles, proposerPubkey, vd.FeeRecipient, attrs)
 		if err != nil {
-			log.Error("could not run block hook", "err", err)
-			return err
+			log.Error("could not run sealed block hook", "err", err)
 		}
+	}
 
-		return nil
+	firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
+		return b.eth.BuildBlock(attrs, blockHook)
 	})
 
 	return firstBlockResult
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 599352ee5222..8bd98aab7b1f 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -47,7 +47,7 @@ func TestOnPayloadAttributes(t *testing.T) {
 		FeeRecipient: common.Address(feeRecipient),
 		StateRoot:    common.Hash{0x07, 0x16},
 		ReceiptsRoot: common.Hash{0x08, 0x20},
-		LogsBloom:    hexutil.MustDecode("0x000000000000000000000000000000"),
+		LogsBloom:    types.Bloom{}.Bytes(),
 		Number:       uint64(10),
 		GasLimit:     uint64(50),
 		GasUsed:      uint64(100),
@@ -56,13 +56,13 @@ func TestOnPayloadAttributes(t *testing.T) {
 
 		BaseFeePerGas: big.NewInt(16),
 
-		BlockHash:    common.Hash{0x09, 0xff},
+		BlockHash:    common.HexToHash("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407"),
 		Transactions: [][]byte{},
 	}
 
-	testBlock := &types.Block{
-		Profit: big.NewInt(10),
-	}
+	testBlock, err := beacon.ExecutableDataToBlock(*testExecutableData)
+	require.NoError(t, err)
+	testBlock.Profit = big.NewInt(10)
 
 	testPayloadAttributes := &BuilderPayloadAttributes{
 		Timestamp:             hexutil.Uint64(104),
@@ -74,7 +74,7 @@ func TestOnPayloadAttributes(t *testing.T) {
 
 	testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
 
-	builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain, testEthService)
+	builder := NewBuilder(sk, NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService)
 
 	builder.OnPayloadAttribute(testPayloadAttributes)
 
@@ -85,7 +85,6 @@ func TestOnPayloadAttributes(t *testing.T) {
 	expectedMessage := boostTypes.BidTrace{
 		Slot:                 uint64(25),
 		ParentHash:           boostTypes.Hash{0x02, 0x03},
-		BlockHash:            boostTypes.Hash{0x09, 0xff},
 		BuilderPubkey:        builder.builderPublicKey,
 		ProposerPubkey:       expectedProposerPubkey,
 		ProposerFeeRecipient: feeRecipient,
@@ -93,6 +92,7 @@ func TestOnPayloadAttributes(t *testing.T) {
 		GasUsed:              uint64(100),
 		Value:                boostTypes.U256Str{0x0a},
 	}
+	expectedMessage.BlockHash.FromSlice(hexutil.MustDecode("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407")[:])
 
 	require.Equal(t, expectedMessage, *testRelay.submittedMsg.Message)
 
@@ -109,13 +109,13 @@ func TestOnPayloadAttributes(t *testing.T) {
 		Timestamp:     testExecutableData.Timestamp,
 		ExtraData:     hexutil.MustDecode("0x0042fafc"),
 		BaseFeePerGas: boostTypes.U256Str{0x10},
-		BlockHash:     boostTypes.Hash{0x09, 0xff},
+		BlockHash:     expectedMessage.BlockHash,
 		Transactions:  []hexutil.Bytes{},
 	}
 
 	require.Equal(t, expectedExecutionPayload, *testRelay.submittedMsg.ExecutionPayload)
 
-	expectedSignature, err := boostTypes.HexToSignature("0xb086abc231a515559128122a6618ad316a76195ad39aa28195c9e8921b98561ca4fd12e2e1ea8d50d8e22f7e36d42ee1084fef26672beceda7650a87061e412d7742705077ac3af3ca1a1c3494eccb22fe7c234fd547a285ba699ff87f0e7759")
+	expectedSignature, err := boostTypes.HexToSignature("0xad09f171b1da05636acfc86778c319af69e39c79515d44bdfed616ba2ef677ffd4d155d87b3363c6bae651ce1e92786216b75f1ac91dd65f3b1d1902bf8485e742170732dd82ffdf4decb0151eeb7926dd053efa9794b2ebed1a203e62bb13e9")
 
 	require.NoError(t, err)
 	require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)
diff --git a/builder/database.go b/builder/database.go
new file mode 100644
index 000000000000..f4799208e5d3
--- /dev/null
+++ b/builder/database.go
@@ -0,0 +1,136 @@
+package builder
+
+import (
+	"context"
+	"database/sql"
+	"math/big"
+	"time"
+
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/log"
+	boostTypes "github.com/flashbots/go-boost-utils/types"
+	"github.com/jmoiron/sqlx"
+	_ "github.com/lib/pq"
+)
+
+type IDatabaseService interface {
+	ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace)
+}
+
+type NilDbService struct{}
+
+func (NilDbService) ConsumeBuiltBlock(*types.Block, []types.SimulatedBundle, *boostTypes.BidTrace) {}
+
+type DatabaseService struct {
+	db *sqlx.DB
+
+	insertBuiltBlockStmt             *sqlx.NamedStmt
+	insertBlockBuiltBundleNoIdStmt   *sqlx.NamedStmt
+	insertBlockBuiltBundleWithIdStmt *sqlx.NamedStmt
+	insertMissingBundleStmt          *sqlx.NamedStmt
+}
+
+func NewDatabaseService(postgresDSN string) (*DatabaseService, error) {
+	db, err := sqlx.Connect("postgres", postgresDSN)
+	if err != nil {
+		return nil, err
+	}
+
+	insertBuiltBlockStmt, err := db.PrepareNamed("insert into built_blocks (block_number, profit, slot, hash, gas_limit, gas_used, base_fee, parent_hash, proposer_pubkey, proposer_fee_recipient, builder_pubkey, timestamp, timestamp_datetime) values (:block_number, :profit, :slot, :hash, :gas_limit, :gas_used, :base_fee, :parent_hash, :proposer_pubkey, :proposer_fee_recipient, :builder_pubkey, :timestamp, to_timestamp(:timestamp)) returning block_id")
+	if err != nil {
+		return nil, err
+	}
+
+	insertBlockBuiltBundleNoIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, id from bundles where bundle_hash = :bundle_hash and param_block_number = :block_number returning bundle_id")
+	if err != nil {
+		return nil, err
+	}
+
+	insertBlockBuiltBundleWithIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, :bundle_id returning bundle_id")
+	if err != nil {
+		return nil, err
+	}
+
+	insertMissingBundleStmt, err := db.PrepareNamed("insert into bundles (bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase) values (:bundle_hash, :param_signed_txs, :param_block_number, :param_timestamp, :received_timestamp, :param_reverting_tx_hashes, :coinbase_diff, :total_gas_used, :state_block_number, :gas_fees, :eth_sent_to_coinbase) on conflict (bundle_hash, param_block_number) do nothing returning id")
+	if err != nil {
+		return nil, err
+	}
+
+	return &DatabaseService{
+		db:                               db,
+		insertBuiltBlockStmt:             insertBuiltBlockStmt,
+		insertBlockBuiltBundleNoIdStmt:   insertBlockBuiltBundleNoIdStmt,
+		insertBlockBuiltBundleWithIdStmt: insertBlockBuiltBundleWithIdStmt,
+		insertMissingBundleStmt:          insertMissingBundleStmt,
+	}, nil
+}
+
+func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) {
+	tx, err := ds.db.Beginx()
+
+	blockData := BuiltBlock{
+		BlockNumber:          block.NumberU64(),
+		Profit:               new(big.Rat).SetFrac(block.Profit, big.NewInt(1e18)).FloatString(18),
+		Slot:                 bidTrace.Slot,
+		Hash:                 block.Hash().String(),
+		GasLimit:             block.GasLimit(),
+		GasUsed:              block.GasUsed(),
+		BaseFee:              block.BaseFee().Uint64(),
+		ParentHash:           block.ParentHash().String(),
+		ProposerPubkey:       bidTrace.ProposerPubkey.String(),
+		ProposerFeeRecipient: bidTrace.ProposerFeeRecipient.String(),
+		BuilderPubkey:        bidTrace.BuilderPubkey.String(),
+		Timestamp:            block.Time(),
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
+	defer cancel()
+	var blockId uint64
+	if err = tx.NamedStmtContext(ctx, ds.insertBuiltBlockStmt).GetContext(ctx, &blockId, blockData); err != nil {
+		log.Error("could not insert built block", "err", err)
+		tx.Rollback()
+		return
+	}
+
+	for _, bundle := range bundles {
+		bundleData := BuiltBlockBundle{
+			BlockId:     blockId,
+			BundleId:    nil,
+			BlockNumber: blockData.BlockNumber,
+			BundleHash:  bundle.OriginalBundle.Hash.String(),
+		}
+
+		var bundleId uint64
+		err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData)
+		if err == nil {
+			continue
+		}
+
+		if err != sql.ErrNoRows {
+			log.Error("could not insert bundle", "err", err)
+			// Try anyway
+		}
+
+		missingBundleData := SimulatedBundleToDbBundle(&bundle)
+		err = ds.insertMissingBundleStmt.GetContext(ctx, &bundleId, missingBundleData) // not using the tx as it relies on the unique constraint!
+		if err == nil {
+			bundleData.BundleId = &bundleId
+			_, err = tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleWithIdStmt).ExecContext(ctx, bundleData)
+			if err != nil {
+				log.Error("could not insert built block bundle after inserting missing bundle", "err", err)
+			}
+		} else if err == sql.ErrNoRows /* conflict, someone else inserted the bundle before we could */ {
+			if err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData); err != nil {
+				log.Error("could not insert bundle on retry", "err", err)
+				continue
+			}
+		} else {
+			log.Error("could not insert missing bundle", "err", err)
+		}
+	}
+
+	err = tx.Commit()
+	if err != nil {
+		log.Error("could not commit DB trasnaction", "err", err)
+	}
+}
diff --git a/builder/database_test.go b/builder/database_test.go
new file mode 100644
index 000000000000..2ff2c7bb8a27
--- /dev/null
+++ b/builder/database_test.go
@@ -0,0 +1,71 @@
+package builder
+
+import (
+	"math/big"
+	"os"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	boostTypes "github.com/flashbots/go-boost-utils/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDatabaseBlockInsertion(t *testing.T) {
+	dsn := os.Getenv("FLASHBOTS_TEST_POSTGRES_DSN")
+	if dsn == "" {
+		return
+	}
+
+	ds, err := NewDatabaseService(dsn)
+	require.NoError(t, err)
+
+	_, err = ds.db.Exec("insert into bundles (id, param_block_number, bundle_hash) values (10, 20, '0x1078')")
+	require.NoError(t, err)
+
+	block := types.NewBlock(
+		&types.Header{
+			ParentHash: common.HexToHash("0xafafafa"),
+			Number:     big.NewInt(132),
+			GasLimit:   uint64(10000),
+			GasUsed:    uint64(1000),
+			Time:       16000000,
+			BaseFee:    big.NewInt(7),
+		}, nil, nil, nil, nil)
+	block.Profit = big.NewInt(10)
+
+	simBundle1 := types.SimulatedBundle{
+		MevGasPrice:       big.NewInt(9),
+		TotalEth:          big.NewInt(11),
+		EthSentToCoinbase: big.NewInt(10),
+		TotalGasUsed:      uint64(100),
+		OriginalBundle: types.MevBundle{
+			Txs:               types.Transactions{types.NewTransaction(uint64(50), common.Address{0x60}, big.NewInt(19), uint64(67), big.NewInt(43), []byte{})},
+			BlockNumber:       big.NewInt(12),
+			MinTimestamp:      uint64(1000000),
+			RevertingTxHashes: []common.Hash{common.Hash{0x10, 0x17}},
+			Hash:              common.Hash{0x09, 0x78},
+		},
+	}
+	simBundle2 := types.SimulatedBundle{
+		MevGasPrice:       big.NewInt(90),
+		TotalEth:          big.NewInt(110),
+		EthSentToCoinbase: big.NewInt(100),
+		TotalGasUsed:      uint64(1000),
+		OriginalBundle: types.MevBundle{
+			Txs:               types.Transactions{types.NewTransaction(uint64(51), common.Address{0x61}, big.NewInt(109), uint64(167), big.NewInt(433), []byte{})},
+			BlockNumber:       big.NewInt(20),
+			MinTimestamp:      uint64(1000020),
+			RevertingTxHashes: []common.Hash{common.Hash{0x11, 0x17}},
+			Hash:              common.Hash{0x10, 0x78},
+		},
+	}
+
+	bidTrace := &boostTypes.BidTrace{}
+
+	ds.ConsumeBuiltBlock(block, []types.SimulatedBundle{simBundle1, simBundle2}, bidTrace)
+
+	var dbBlock BuiltBlock
+	ds.db.Get(&dbBlock, "select * from built_blocks where hash = '0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004'")
+	t.Logf("block %v", dbBlock)
+}
diff --git a/builder/database_types.go b/builder/database_types.go
new file mode 100644
index 000000000000..96965616b028
--- /dev/null
+++ b/builder/database_types.go
@@ -0,0 +1,77 @@
+package builder
+
+import (
+	"math/big"
+	"strings"
+	"time"
+
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+type BuiltBlock struct {
+	BlockId              uint64    `db:"block_id"`
+	BlockNumber          uint64    `db:"block_number"`
+	Profit               string    `db:"profit"`
+	Slot                 uint64    `db:"slot"`
+	Hash                 string    `db:"hash"`
+	GasLimit             uint64    `db:"gas_limit"`
+	GasUsed              uint64    `db:"gas_used"`
+	BaseFee              uint64    `db:"base_fee"`
+	ParentHash           string    `db:"parent_hash"`
+	ProposerPubkey       string    `db:"proposer_pubkey"`
+	ProposerFeeRecipient string    `db:"proposer_fee_recipient"`
+	BuilderPubkey        string    `db:"builder_pubkey"`
+	Timestamp            uint64    `db:"timestamp"`
+	TimestampDatetime    time.Time `db:"timestamp_datetime"`
+}
+
+type BuiltBlockBundle struct {
+	BlockId     uint64  `db:"block_id"`
+	BundleId    *uint64 `db:"bundle_id"`
+	BlockNumber uint64  `db:"block_number"`
+	BundleHash  string  `db:"bundle_hash"`
+}
+
+type DbBundle struct {
+	DbId       uint64 `db:"id"`
+	BundleHash string `db:"bundle_hash"`
+
+	ParamSignedTxs         string    `db:"param_signed_txs"`
+	ParamBlockNumber       uint64    `db:"param_block_number"`
+	ParamTimestamp         uint64    `db:"param_timestamp"`
+	ReceivedTimestamp      time.Time `db:"received_timestamp"`
+	ParamRevertingTxHashes string    `db:"param_reverting_tx_hashes"`
+
+	CoinbaseDiff      string `db:"coinbase_diff"`
+	TotalGasUsed      uint64 `db:"total_gas_used"`
+	StateBlockNumber  uint64 `db:"state_block_number"`
+	GasFees           string `db:"gas_fees"`
+	EthSentToCoinbase string `db:"eth_sent_to_coinbase"`
+}
+
+func SimulatedBundleToDbBundle(bundle *types.SimulatedBundle) DbBundle {
+	revertingTxHashes := make([]string, len(bundle.OriginalBundle.RevertingTxHashes))
+	for i, rTxHash := range bundle.OriginalBundle.RevertingTxHashes {
+		revertingTxHashes[i] = rTxHash.String()
+	}
+
+	signedTxsStrings := make([]string, len(bundle.OriginalBundle.Txs))
+	for i, tx := range bundle.OriginalBundle.Txs {
+		signedTxsStrings[i] = tx.Hash().String()
+	}
+
+	return DbBundle{
+		BundleHash: bundle.OriginalBundle.Hash.String(),
+
+		ParamSignedTxs:         strings.Join(signedTxsStrings, ","),
+		ParamBlockNumber:       bundle.OriginalBundle.BlockNumber.Uint64(),
+		ParamTimestamp:         bundle.OriginalBundle.MinTimestamp,
+		ParamRevertingTxHashes: strings.Join(revertingTxHashes, ","),
+
+		CoinbaseDiff:      new(big.Rat).SetFrac(bundle.TotalEth, big.NewInt(1e18)).FloatString(18),
+		TotalGasUsed:      bundle.TotalGasUsed,
+		StateBlockNumber:  bundle.OriginalBundle.BlockNumber.Uint64(),
+		GasFees:           new(big.Int).Mul(big.NewInt(int64(bundle.TotalGasUsed)), bundle.MevGasPrice).String(),
+		EthSentToCoinbase: new(big.Rat).SetFrac(bundle.EthSentToCoinbase, big.NewInt(1e18)).FloatString(18),
+	}
+}
diff --git a/builder/eth_service.go b/builder/eth_service.go
index 6ea8b7d3ca61..48490b0ce57d 100644
--- a/builder/eth_service.go
+++ b/builder/eth_service.go
@@ -1,6 +1,7 @@
 package builder
 
 import (
+	"errors"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -11,7 +12,7 @@ import (
 )
 
 type IEthereumService interface {
-	BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block)
+	BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error
 	GetBlockByHash(hash common.Hash) *types.Block
 	Synced() bool
 }
@@ -20,10 +21,12 @@ type testEthereumService struct {
 	synced             bool
 	testExecutableData *beacon.ExecutableDataV1
 	testBlock          *types.Block
+	testBundlesMerged  []types.SimulatedBundle
 }
 
-func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) {
-	return t.testExecutableData, t.testBlock
+func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error {
+	sealedBlockCallback(t.testBlock, t.testBundlesMerged)
+	return nil
 }
 
 func (t *testEthereumService) GetBlockByHash(hash common.Hash) *types.Block { return t.testBlock }
@@ -38,13 +41,13 @@ func NewEthereumService(eth *eth.Ethereum) *EthereumService {
 	return &EthereumService{eth: eth}
 }
 
-func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) {
+func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error {
 	// Send a request to generate a full block in the background.
 	// The result can be obtained via the returned channel.
-	resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false)
+	resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false, sealedBlockCallback)
 	if err != nil {
 		log.Error("Failed to create async sealing payload", "err", err)
-		return nil, nil
+		return err
 	}
 
 	timer := time.NewTimer(4 * time.Second)
@@ -53,13 +56,12 @@ func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.E
 	select {
 	case block := <-resCh:
 		if block == nil {
-			log.Error("received nil block from sealing work")
-			return nil, nil
+			return errors.New("received nil block from sealing work")
 		}
-		return beacon.BlockToExecutableData(block), block
+		return nil
 	case <-timer.C:
 		log.Error("timeout waiting for block", "parent hash", attrs.HeadHash, "slot", attrs.Slot)
-		return nil, nil
+		return errors.New("timeout waiting for block result")
 	}
 }
 
diff --git a/builder/eth_service_test.go b/builder/eth_service_test.go
index d3e0daad8eea..9443a19f0820 100644
--- a/builder/eth_service_test.go
+++ b/builder/eth_service_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/beacon"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/eth"
@@ -90,13 +91,18 @@ func TestBuildBlock(t *testing.T) {
 	}
 
 	service := NewEthereumService(ethservice)
-	executableData, block := service.BuildBlock(testPayloadAttributes)
+	service.eth.APIBackend.Miner().SetEtherbase(common.Address{0x05, 0x11})
 
-	//require.Equal(t, common.Address{0x04, 0x10}, executableData.FeeRecipient)
-	require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
-	require.Equal(t, parent.Hash(), executableData.ParentHash)
-	require.Equal(t, parent.Time()+1, executableData.Timestamp)
-	require.Equal(t, block.ParentHash(), parent.Hash())
-	require.Equal(t, block.Hash(), executableData.BlockHash)
-	require.Equal(t, block.Profit.Uint64(), uint64(0))
+	err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, _ []types.SimulatedBundle) {
+		executableData := beacon.BlockToExecutableData(block)
+		require.Equal(t, common.Address{0x05, 0x11}, executableData.FeeRecipient)
+		require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
+		require.Equal(t, parent.Hash(), executableData.ParentHash)
+		require.Equal(t, parent.Time()+1, executableData.Timestamp)
+		require.Equal(t, block.ParentHash(), parent.Hash())
+		require.Equal(t, block.Hash(), executableData.BlockHash)
+		require.Equal(t, block.Profit.Uint64(), uint64(0))
+	})
+
+	require.NoError(t, err)
 }
diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go
index 3e706f5bfedc..b101029b4816 100644
--- a/builder/local_relay_test.go
+++ b/builder/local_relay_test.go
@@ -29,7 +29,7 @@ func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block
 	beaconClient := &testBeaconClient{validator: validator}
 	localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
 	ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block}
-	backend := NewBuilder(sk, beaconClient, localRelay, bDomain, ethService)
+	backend := NewBuilder(sk, NilDbService{}, beaconClient, localRelay, bDomain, ethService)
 	// service := NewService("127.0.0.1:31545", backend)
 
 	return backend, localRelay, validator
@@ -115,15 +115,16 @@ func TestGetHeader(t *testing.T) {
 	forkchoiceData := &beacon.ExecutableDataV1{
 		ParentHash:    common.HexToHash("0xafafafa"),
 		FeeRecipient:  common.Address{0x01},
-		BlockHash:     common.HexToHash("0xbfbfbfb"),
+		LogsBloom:     types.Bloom{0x00, 0x05, 0x10}.Bytes(),
+		BlockHash:     common.HexToHash("0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004"),
 		BaseFeePerGas: big.NewInt(12),
 		ExtraData:     []byte{},
-		LogsBloom:     []byte{0x00, 0x05, 0x10},
-	}
-	forkchoiceBlock := &types.Block{
-		Profit: big.NewInt(10),
 	}
 
+	forkchoiceBlock, err := beacon.ExecutableDataToBlock(*forkchoiceData)
+	require.NoError(t, err)
+	forkchoiceBlock.Profit = big.NewInt(10)
+
 	backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock)
 
 	path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
@@ -147,7 +148,7 @@ func TestGetHeader(t *testing.T) {
 	require.Equal(t, http.StatusOK, rr.Code)
 
 	bid := new(boostTypes.GetHeaderResponse)
-	err := json.Unmarshal(rr.Body.Bytes(), bid)
+	err = json.Unmarshal(rr.Body.Bytes(), bid)
 	require.NoError(t, err)
 
 	executionPayload, err := executableDataToExecutionPayload(forkchoiceData)
@@ -174,13 +175,15 @@ func TestGetPayload(t *testing.T) {
 	forkchoiceData := &beacon.ExecutableDataV1{
 		ParentHash:    common.HexToHash("0xafafafa"),
 		FeeRecipient:  common.Address{0x01},
-		BlockHash:     common.HexToHash("0xbfbfbfb"),
+		LogsBloom:     types.Bloom{}.Bytes(),
+		BlockHash:     common.HexToHash("0xc4a012b67027b3ab6c00acd31aeee24aa1515d6a5d7e81b0ee2e69517fdc387f"),
 		BaseFeePerGas: big.NewInt(12),
 		ExtraData:     []byte{},
 	}
-	forkchoiceBlock := &types.Block{
-		Profit: big.NewInt(10),
-	}
+
+	forkchoiceBlock, err := beacon.ExecutableDataToBlock(*forkchoiceData)
+	require.NoError(t, err)
+	forkchoiceBlock.Profit = big.NewInt(10)
 
 	backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock)
 
@@ -192,7 +195,7 @@ func TestGetPayload(t *testing.T) {
 	require.Equal(t, http.StatusOK, rr.Code)
 
 	bid := new(boostTypes.GetHeaderResponse)
-	err := json.Unmarshal(rr.Body.Bytes(), bid)
+	err = json.Unmarshal(rr.Body.Bytes(), bid)
 	require.NoError(t, err)
 
 	// Create request payload
diff --git a/builder/service.go b/builder/service.go
index 0e459decd9f6..131b8d5640e9 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"os"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
@@ -159,7 +160,15 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
 
 	ethereumService := NewEthereumService(backend)
 
-	builderBackend := NewBuilder(builderSk, beaconClient, relay, builderSigningDomain, ethereumService)
+	// TODO: move to proper flags
+	var ds IDatabaseService
+	ds, err = NewDatabaseService(os.Getenv("FLASHBOTS_POSTGRES_DSN"))
+	if err != nil {
+		log.Error("could not connect to the DB", "err", err)
+		ds = NilDbService{}
+	}
+
+	builderBackend := NewBuilder(builderSk, ds, beaconClient, relay, builderSigningDomain, ethereumService)
 	builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)
 	builderService.Start()
 
diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go
index a02a6117dcfd..5dccb489c275 100644
--- a/core/txpool/txpool.go
+++ b/core/txpool/txpool.go
@@ -36,6 +36,7 @@ import (
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
 	"github.com/ethereum/go-ethereum/params"
+	"golang.org/x/crypto/sha3"
 )
 
 const (
@@ -620,6 +621,12 @@ func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]t
 
 // AddMevBundle adds a mev bundle to the pool
 func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+	bundleHasher := sha3.NewLegacyKeccak256()
+	for _, tx := range txs {
+		bundleHasher.Write(tx.Hash().Bytes())
+	}
+	bundleHash := common.BytesToHash(bundleHasher.Sum(nil))
+
 	pool.mu.Lock()
 	defer pool.mu.Unlock()
 
@@ -629,6 +636,7 @@ func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, m
 		MinTimestamp:      minTimestamp,
 		MaxTimestamp:      maxTimestamp,
 		RevertingTxHashes: revertingTxHashes,
+		Hash:              bundleHash,
 	})
 	return nil
 }
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 70d1106344e5..1ec1470b4a0e 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -664,4 +664,13 @@ type MevBundle struct {
 	MinTimestamp      uint64
 	MaxTimestamp      uint64
 	RevertingTxHashes []common.Hash
+	Hash              common.Hash
+}
+
+type SimulatedBundle struct {
+	MevGasPrice       *big.Int
+	TotalEth          *big.Int
+	EthSentToCoinbase *big.Int
+	TotalGasUsed      uint64
+	OriginalBundle    MevBundle
 }
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index 961e0911815e..98f8646c029e 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -210,7 +210,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
 }
 
 func assembleBlock(api *BlockValidationAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
-	block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false)
+	block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/go.mod b/go.mod
index cb0e7769e5e6..091be76c0d10 100644
--- a/go.mod
+++ b/go.mod
@@ -42,8 +42,10 @@ require (
 	github.com/influxdata/influxdb-client-go/v2 v2.4.0
 	github.com/jackpal/go-nat-pmp v1.0.2
 	github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e
+	github.com/jmoiron/sqlx v1.3.5
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/karalabe/usb v0.0.2
+	github.com/lib/pq v1.2.0
 	github.com/mattn/go-colorable v0.1.9
 	github.com/mattn/go-isatty v0.0.14
 	github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
diff --git a/go.sum b/go.sum
index 361198127593..294b59dfa147 100644
--- a/go.sum
+++ b/go.sum
@@ -270,6 +270,8 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+U
 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
+github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -309,6 +311,8 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
 github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
 github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
@@ -330,6 +334,7 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index cd0068c1e901..4ab9a3619c1c 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -91,11 +91,11 @@ type resChPair struct {
 	errCh chan error
 }
 
-func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (chan *types.Block, error) {
+func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) {
 	resChans := []resChPair{}
 
 	for _, worker := range append(w.workers, w.regularWorker) {
-		resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra)
+		resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook)
 		if err != nil {
 			log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "#bundles", worker.flashbots.maxMergedBundles)
 			continue
@@ -128,8 +128,8 @@ func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64,
 	return resCh, nil
 }
 
-func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (*types.Block, error) {
-	resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra)
+func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (*types.Block, error) {
+	resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook)
 	if err != nil {
 		return nil, err
 	}
diff --git a/miner/worker.go b/miner/worker.go
index 9ee5a9f2c3a8..4c326ebea3f8 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1275,8 +1275,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
 // fillTransactions retrieves the pending transactions from the txpool and fills them
 // into the given sealing block. The transaction selection and ordering strategy can
 // be customized with the plugin in the future.
-
-func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) error {
+func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) (error, []types.SimulatedBundle) {
 	// Split the pending transactions into locals and remotes
 	// Fill the block with all available pending transactions.
 	pending := w.eth.TxPool().Pending(true)
@@ -1294,43 +1293,47 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
 	if validatorCoinbase != nil {
 		builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase)
 		if err := env.gasPool.SubGas(params.TxGas); err != nil {
-			return err
+			return err, nil
 		}
 	}
+
+	var blockBundles []types.SimulatedBundle
 	if w.flashbots.isFlashbots {
 		bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
 		if err != nil {
 			log.Error("Failed to fetch pending transactions", "err", err)
-			return err
+			return err, nil
 		}
 
-		bundleTxs, bundle, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending)
+		bundleTxs, bundle, mergedBundles, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending)
 		if err != nil {
 			log.Error("Failed to generate flashbots bundle", "err", err)
-			return err
+			return err, nil
 		}
-		log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.totalEth), "gasUsed", bundle.totalGasUsed, "bundleScore", bundle.mevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
+		log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.TotalEth), "gasUsed", bundle.TotalGasUsed, "bundleScore", bundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
 		if len(bundleTxs) == 0 {
-			return errors.New("no bundles to apply")
+			return errors.New("no bundles to apply"), nil
 		}
 		if err := w.commitBundle(env, bundleTxs, interrupt); err != nil {
-			return err
+			return err, nil
 		}
-		env.profit.Add(env.profit, bundle.ethSentToCoinbase)
+		blockBundles = mergedBundles
+		env.profit.Add(env.profit, bundle.EthSentToCoinbase)
 	}
 
 	if len(localTxs) > 0 {
 		txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
 		if err := w.commitTransactions(env, txs, interrupt); err != nil {
-			return err
+			return err, nil
 		}
 	}
 	if len(remoteTxs) > 0 {
 		txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee)
 		if err := w.commitTransactions(env, txs, interrupt); err != nil {
-			return err
+			return err, nil
 		}
 	}
+
 	if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
 		builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
 		log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
@@ -1341,7 +1344,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
 			tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
 			if err != nil {
 				log.Error("Proposer payout create tx failed", "err", err)
-				return fmt.Errorf("proposer payout create tx failed - %v", err)
+				return fmt.Errorf("proposer payout create tx failed - %v", err), nil
 			}
 			if tx != nil {
 				log.Info("Proposer payout create tx succeeded, proceeding to commit tx")
@@ -1349,20 +1352,21 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
 				_, err = w.commitTransaction(env, tx)
 				if err != nil {
 					log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err)
-					return fmt.Errorf("proposer payout commit tx failed - %v", err)
+					return fmt.Errorf("proposer payout commit tx failed - %v", err), nil
 				}
 				log.Info("Proposer payout commit tx succeeded", "hash", tx.Hash().String())
 				env.tcount++
 			} else {
-				return errors.New("proposer payout create tx failed due to tx is nil")
+				return errors.New("proposer payout create tx failed due to tx is nil"), nil
 			}
 		} else {
 			log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String())
-			return errors.New("proposer payout create tx failed due to not enough balance")
+			return errors.New("proposer payout create tx failed due to not enough balance"), nil
 		}
 
 	}
-	return nil
+
+	return nil, blockBundles
 }
 
 // generateWork generates a sealing block based on the given parameters.
@@ -1373,6 +1377,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e
 	}
 	defer work.discard()
 
+	var blockBundles []types.SimulatedBundle
 	if !params.noTxs {
 		interrupt := new(int32)
 		timer := time.AfterFunc(w.newpayloadTimeout, func() {
@@ -1384,6 +1389,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e
 		if errors.Is(err, errBlockInterruptedByTimeout) {
 			log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout))
 		}
+		blockBundles = mergedBundles
 	}
 	block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, params.withdrawals)
 	if err != nil {
@@ -1535,28 +1541,23 @@ func (w *worker) isTTDReached(header *types.Header) bool {
 	return td != nil && ttd != nil && td.Cmp(ttd) >= 0
 }
 
-type simulatedBundle struct {
-	mevGasPrice       *big.Int
-	totalEth          *big.Int
-	ethSentToCoinbase *big.Int
-	totalGasUsed      uint64
-	originalBundle    types.MevBundle
-}
+type simulatedBundle = types.SimulatedBundle
 
-func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) {
+func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) {
 	simulatedBundles, err := w.simulateBundles(env, bundles, pendingTxs)
 	if err != nil {
-		return nil, simulatedBundle{}, 0, err
+		return nil, simulatedBundle{}, nil, 0, err
 	}
 
 	sort.SliceStable(simulatedBundles, func(i, j int) bool {
-		return simulatedBundles[j].mevGasPrice.Cmp(simulatedBundles[i].mevGasPrice) < 0
+		return simulatedBundles[j].MevGasPrice.Cmp(simulatedBundles[i].MevGasPrice) < 0
 	})
 
 	return w.mergeBundles(env, simulatedBundles, pendingTxs)
 }
 
-func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) {
+func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) {
+	mergedBundles := []types.SimulatedBundle{}
 	finalBundle := types.Transactions{}
 
 	currentState := env.state.Copy()
@@ -1566,8 +1567,8 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
 	var prevGasPool *core.GasPool
 
 	mergedBundle := simulatedBundle{
-		totalEth:          new(big.Int),
-		ethSentToCoinbase: new(big.Int),
+		TotalEth:          new(big.Int),
+		EthSentToCoinbase: new(big.Int),
 	}
 
 	count := 0
@@ -1576,22 +1577,22 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
 		prevGasPool = new(core.GasPool).AddGas(gasPool.Gas())
 
 		// the floor gas price is 99/100 what was simulated at the top of the block
-		floorGasPrice := new(big.Int).Mul(bundle.mevGasPrice, big.NewInt(99))
+		floorGasPrice := new(big.Int).Mul(bundle.MevGasPrice, big.NewInt(99))
 		floorGasPrice = floorGasPrice.Div(floorGasPrice, big.NewInt(100))
 
-		simmed, err := w.computeBundleGas(env, bundle.originalBundle, currentState, gasPool, pendingTxs, len(finalBundle))
-		if err != nil || simmed.mevGasPrice.Cmp(floorGasPrice) <= 0 {
+		simmed, err := w.computeBundleGas(env, bundle.OriginalBundle, currentState, gasPool, pendingTxs, len(finalBundle))
+		if err != nil || simmed.MevGasPrice.Cmp(floorGasPrice) <= 0 {
 			currentState = prevState
 			gasPool = prevGasPool
 			continue
 		}
 
-		log.Info("Included bundle", "ethToCoinbase", ethIntToFloat(simmed.totalEth), "gasUsed", simmed.totalGasUsed, "bundleScore", simmed.mevGasPrice, "bundleLength", len(simmed.originalBundle.Txs), "worker", w.flashbots.maxMergedBundles)
-
-		finalBundle = append(finalBundle, bundle.originalBundle.Txs...)
-		mergedBundle.totalEth.Add(mergedBundle.totalEth, simmed.totalEth)
-		mergedBundle.ethSentToCoinbase.Add(mergedBundle.ethSentToCoinbase, simmed.ethSentToCoinbase)
-		mergedBundle.totalGasUsed += simmed.totalGasUsed
+		log.Info("Included bundle", "ethToCoinbase", ethIntToFloat(simmed.TotalEth), "gasUsed", simmed.TotalGasUsed, "bundleScore", simmed.MevGasPrice, "bundleLength", len(simmed.OriginalBundle.Txs), "worker", w.flashbots.maxMergedBundles)
+		mergedBundles = append(mergedBundles, simmed)
+		finalBundle = append(finalBundle, bundle.OriginalBundle.Txs...)
+		mergedBundle.TotalEth.Add(mergedBundle.TotalEth, simmed.TotalEth)
+		mergedBundle.EthSentToCoinbase.Add(mergedBundle.EthSentToCoinbase, simmed.EthSentToCoinbase)
+		mergedBundle.TotalGasUsed += simmed.TotalGasUsed
 		count++
 
 		if count >= w.flashbots.maxMergedBundles {
@@ -1600,15 +1601,15 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
 	}
 
 	if len(finalBundle) == 0 || count != w.flashbots.maxMergedBundles {
-		return nil, simulatedBundle{}, count, nil
+		return nil, simulatedBundle{}, nil, count, nil
 	}
 
 	return finalBundle, simulatedBundle{
-		mevGasPrice:       new(big.Int).Div(mergedBundle.totalEth, new(big.Int).SetUint64(mergedBundle.totalGasUsed)),
-		totalEth:          mergedBundle.totalEth,
-		ethSentToCoinbase: mergedBundle.ethSentToCoinbase,
-		totalGasUsed:      mergedBundle.totalGasUsed,
-	}, count, nil
+		MevGasPrice:       new(big.Int).Div(mergedBundle.TotalEth, new(big.Int).SetUint64(mergedBundle.TotalGasUsed)),
+		TotalEth:          mergedBundle.TotalEth,
+		EthSentToCoinbase: mergedBundle.EthSentToCoinbase,
+		TotalGasUsed:      mergedBundle.TotalGasUsed,
+	}, mergedBundles, count, nil
 }
 
 func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) {
@@ -1716,11 +1717,11 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat
 	totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
 
 	return simulatedBundle{
-		mevGasPrice:       new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
-		totalEth:          totalEth,
-		ethSentToCoinbase: ethSentToCoinbase,
-		totalGasUsed:      totalGasUsed,
-		originalBundle:    bundle,
+		MevGasPrice:       new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
+		TotalEth:          totalEth,
+		EthSentToCoinbase: ethSentToCoinbase,
+		TotalGasUsed:      totalGasUsed,
+		OriginalBundle:    bundle,
 	}, nil
 }