diff --git a/.changeset/forty-pigs-study.md b/.changeset/forty-pigs-study.md
new file mode 100644
index 0000000000000000000000000000000000000000..122311dc65431bfed9fb5b7b402e941cdc2acadc
--- /dev/null
+++ b/.changeset/forty-pigs-study.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/l2geth': patch
+---
+
+Allow zero gas price transactions from the `OVM_GasPriceOracle.owner` when enforce fees is set to true. This is to prevent the need to manage an additional hot wallet as well as prevent any situation where a bug causes the fees to go too high that it is not possible to lower the fee by sending a transaction
diff --git a/l2geth/rollup/sync_service.go b/l2geth/rollup/sync_service.go
index e959219d0539bb355dd30df22bafa2bcd509ea96..d240a12fbc294c811d31b8d85ef70282fb3c24d6 100644
--- a/l2geth/rollup/sync_service.go
+++ b/l2geth/rollup/sync_service.go
@@ -24,47 +24,62 @@ import (
 	"github.com/ethereum/go-ethereum/rollup/fees"
 )
 
-// errShortRemoteTip is an error for when the remote tip is shorter than the
-// local tip
 var (
+	// errBadConfig is the error when the SyncService is started with invalid
+	// configuration options
+	errBadConfig = errors.New("bad config")
+	// errShortRemoteTip is an error for when the remote tip is shorter than the
+	// local tip
 	errShortRemoteTip = errors.New("unexpected remote less than tip")
-	errBadConfig      = errors.New("bad config")
+	// errZeroGasPriceTx is the error for when a user submits a transaction
+	// with gas price zero and fees are currently enforced
+	errZeroGasPriceTx = errors.New("cannot accept 0 gas price transaction")
 	float1            = big.NewFloat(1)
 )
 
-// L2GasPrice slot refers to the storage slot that the execution price is stored
-// in the L2 predeploy contract, the GasPriceOracle
-var l2GasPriceSlot = common.BigToHash(big.NewInt(1))
-var l2GasPriceOracleAddress = common.HexToAddress("0x420000000000000000000000000000000000000F")
+var (
+	// l2GasPriceSlot refers to the storage slot that the L2 gas price is stored
+	// in in the OVM_GasPriceOracle predeploy
+	l2GasPriceSlot = common.BigToHash(big.NewInt(1))
+	// l2GasPriceOracleOwnerSlot refers to the storage slot that the owner of
+	// the OVM_GasPriceOracle is stored in
+	l2GasPriceOracleOwnerSlot = common.BigToHash(big.NewInt(0))
+	// l2GasPriceOracleAddress is the address of the OVM_GasPriceOracle
+	// predeploy
+	l2GasPriceOracleAddress = common.HexToAddress("0x420000000000000000000000000000000000000F")
+)
 
 // SyncService implements the main functionality around pulling in transactions
 // and executing them. It can be configured to run in both sequencer mode and in
 // verifier mode.
 type SyncService struct {
-	ctx                       context.Context
-	cancel                    context.CancelFunc
-	verifier                  bool
-	db                        ethdb.Database
-	scope                     event.SubscriptionScope
-	txFeed                    event.Feed
-	txLock                    sync.Mutex
-	loopLock                  sync.Mutex
-	enable                    bool
-	eth1ChainId               uint64
-	bc                        *core.BlockChain
-	txpool                    *core.TxPool
-	RollupGpo                 *gasprice.RollupOracle
-	client                    RollupClient
-	syncing                   atomic.Value
-	chainHeadSub              event.Subscription
-	OVMContext                OVMContext
-	pollInterval              time.Duration
-	timestampRefreshThreshold time.Duration
-	chainHeadCh               chan core.ChainHeadEvent
-	backend                   Backend
-	enforceFees               bool
-	feeThresholdUp            *big.Float
-	feeThresholdDown          *big.Float
+	ctx                            context.Context
+	cancel                         context.CancelFunc
+	verifier                       bool
+	db                             ethdb.Database
+	scope                          event.SubscriptionScope
+	txFeed                         event.Feed
+	txLock                         sync.Mutex
+	loopLock                       sync.Mutex
+	enable                         bool
+	eth1ChainId                    uint64
+	bc                             *core.BlockChain
+	txpool                         *core.TxPool
+	RollupGpo                      *gasprice.RollupOracle
+	client                         RollupClient
+	syncing                        atomic.Value
+	chainHeadSub                   event.Subscription
+	OVMContext                     OVMContext
+	pollInterval                   time.Duration
+	timestampRefreshThreshold      time.Duration
+	chainHeadCh                    chan core.ChainHeadEvent
+	backend                        Backend
+	gasPriceOracleOwnerAddress     common.Address
+	gasPriceOracleOwnerAddressLock *sync.RWMutex
+	enforceFees                    bool
+	signer                         types.Signer
+	feeThresholdUp                 *big.Float
+	feeThresholdDown               *big.Float
 }
 
 // NewSyncService returns an initialized sync service
@@ -122,23 +137,26 @@ func NewSyncService(ctx context.Context, cfg Config, txpool *core.TxPool, bc *co
 	}
 
 	service := SyncService{
-		ctx:                       ctx,
-		cancel:                    cancel,
-		verifier:                  cfg.IsVerifier,
-		enable:                    cfg.Eth1SyncServiceEnable,
-		syncing:                   atomic.Value{},
-		bc:                        bc,
-		txpool:                    txpool,
-		chainHeadCh:               make(chan core.ChainHeadEvent, 1),
-		eth1ChainId:               cfg.Eth1ChainId,
-		client:                    client,
-		db:                        db,
-		pollInterval:              pollInterval,
-		timestampRefreshThreshold: timestampRefreshThreshold,
-		backend:                   cfg.Backend,
-		enforceFees:               cfg.EnforceFees,
-		feeThresholdDown:          cfg.FeeThresholdDown,
-		feeThresholdUp:            cfg.FeeThresholdUp,
+		ctx:                            ctx,
+		cancel:                         cancel,
+		verifier:                       cfg.IsVerifier,
+		enable:                         cfg.Eth1SyncServiceEnable,
+		syncing:                        atomic.Value{},
+		bc:                             bc,
+		txpool:                         txpool,
+		chainHeadCh:                    make(chan core.ChainHeadEvent, 1),
+		eth1ChainId:                    cfg.Eth1ChainId,
+		client:                         client,
+		db:                             db,
+		pollInterval:                   pollInterval,
+		timestampRefreshThreshold:      timestampRefreshThreshold,
+		backend:                        cfg.Backend,
+		gasPriceOracleOwnerAddress:     cfg.GasPriceOracleOwnerAddress,
+		gasPriceOracleOwnerAddressLock: new(sync.RWMutex),
+		enforceFees:                    cfg.EnforceFees,
+		signer:                         types.NewEIP155Signer(chainID),
+		feeThresholdDown:               cfg.FeeThresholdDown,
+		feeThresholdUp:                 cfg.FeeThresholdUp,
 	}
 
 	// The chainHeadSub is used to synchronize the SyncService with the chain.
@@ -234,8 +252,12 @@ func (s *SyncService) Start() error {
 		return nil
 	}
 	log.Info("Initializing Sync Service", "eth1-chainid", s.eth1ChainId)
-	s.updateL2GasPrice(nil)
-	s.updateL1GasPrice()
+	if err := s.updateGasPriceOracleCache(nil); err != nil {
+		return err
+	}
+	if err := s.updateL1GasPrice(); err != nil {
+		return err
+	}
 
 	if s.verifier {
 		go s.VerifierLoop()
@@ -361,7 +383,7 @@ func (s *SyncService) VerifierLoop() {
 		if err := s.verify(); err != nil {
 			log.Error("Could not verify", "error", err)
 		}
-		if err := s.updateL2GasPrice(nil); err != nil {
+		if err := s.updateGasPriceOracleCache(nil); err != nil {
 			log.Error("Cannot update L2 gas price", "msg", err)
 		}
 	}
@@ -398,7 +420,7 @@ func (s *SyncService) SequencerLoop() {
 		}
 		s.txLock.Unlock()
 
-		if err := s.updateL2GasPrice(nil); err != nil {
+		if err := s.updateGasPriceOracleCache(nil); err != nil {
 			log.Error("Cannot update L2 gas price", "msg", err)
 		}
 		if err := s.updateContext(); err != nil {
@@ -462,25 +484,68 @@ func (s *SyncService) updateL1GasPrice() error {
 	return nil
 }
 
-// updateL2GasPrice accepts a state root and reads the gas price from the gas
-// price oracle at the state that corresponds to the state root. If no state
-// root is passed in, then the tip is used.
-func (s *SyncService) updateL2GasPrice(hash *common.Hash) error {
-	var state *state.StateDB
+// updateL2GasPrice accepts a state db and reads the gas price from the gas
+// price oracle at the state that corresponds to the state db. If no state db
+// is passed in, then the tip is used.
+func (s *SyncService) updateL2GasPrice(statedb *state.StateDB) error {
+	var err error
+	if statedb == nil {
+		statedb, err = s.bc.State()
+		if err != nil {
+			return err
+		}
+	}
+	result := statedb.GetState(l2GasPriceOracleAddress, l2GasPriceSlot)
+	s.RollupGpo.SetL2GasPrice(result.Big())
+	return nil
+}
+
+// cacheGasPriceOracleOwner accepts a statedb and caches the gas price oracle
+// owner address locally
+func (s *SyncService) cacheGasPriceOracleOwner(statedb *state.StateDB) error {
+	var err error
+	if statedb == nil {
+		statedb, err = s.bc.State()
+		if err != nil {
+			return err
+		}
+	}
+	s.gasPriceOracleOwnerAddressLock.Lock()
+	defer s.gasPriceOracleOwnerAddressLock.Unlock()
+	result := statedb.GetState(l2GasPriceOracleAddress, l2GasPriceOracleOwnerSlot)
+	s.gasPriceOracleOwnerAddress = common.BytesToAddress(result.Bytes())
+	return nil
+}
+
+// updateGasPriceOracleCache caches the owner as well as updating the
+// the L2 gas price from the OVM_GasPriceOracle
+func (s *SyncService) updateGasPriceOracleCache(hash *common.Hash) error {
+	var statedb *state.StateDB
 	var err error
 	if hash != nil {
-		state, err = s.bc.StateAt(*hash)
+		statedb, err = s.bc.StateAt(*hash)
 	} else {
-		state, err = s.bc.State()
+		statedb, err = s.bc.State()
 	}
 	if err != nil {
 		return err
 	}
-	result := state.GetState(l2GasPriceOracleAddress, l2GasPriceSlot)
-	s.RollupGpo.SetL2GasPrice(result.Big())
+	if err := s.cacheGasPriceOracleOwner(statedb); err != nil {
+		return err
+	}
+	if err := s.updateL2GasPrice(statedb); err != nil {
+		return err
+	}
 	return nil
 }
 
+// A thread safe getter for the gas price oracle owner address
+func (s *SyncService) GasPriceOracleOwnerAddress() *common.Address {
+	s.gasPriceOracleOwnerAddressLock.RLock()
+	defer s.gasPriceOracleOwnerAddressLock.RUnlock()
+	return &s.gasPriceOracleOwnerAddress
+}
+
 /// Update the execution context's timestamp and blocknumber
 /// over time. This is only necessary for the sequencer.
 func (s *SyncService) updateContext() error {
@@ -755,9 +820,21 @@ func (s *SyncService) applyBatchedTransaction(tx *types.Transaction) error {
 // verifyFee will verify that a valid fee is being paid.
 func (s *SyncService) verifyFee(tx *types.Transaction) error {
 	if tx.GasPrice().Cmp(common.Big0) == 0 {
+		// Allow 0 gas price transactions only if it is the owner of the gas
+		// price oracle
+		gpoOwner := s.GasPriceOracleOwnerAddress()
+		if gpoOwner != nil {
+			from, err := types.Sender(s.signer, tx)
+			if err != nil {
+				return fmt.Errorf("invalid transaction: %w", core.ErrInvalidSender)
+			}
+			if from == *gpoOwner {
+				return nil
+			}
+		}
 		// Exit early if fees are enforced and the gasPrice is set to 0
 		if s.enforceFees {
-			return errors.New("cannot accept 0 gas price transaction")
+			return errZeroGasPriceTx
 		}
 		// If fees are not enforced and the gas price is 0, return early
 		return nil
@@ -832,8 +909,7 @@ func (s *SyncService) ValidateAndApplySequencerTransaction(tx *types.Transaction
 	if qo != types.QueueOriginSequencer {
 		return fmt.Errorf("invalid transaction with queue origin %d", qo)
 	}
-	err := s.txpool.ValidateTx(tx)
-	if err != nil {
+	if err := s.txpool.ValidateTx(tx); err != nil {
 		return fmt.Errorf("invalid transaction: %w", err)
 	}
 	return s.applyTransaction(tx)
diff --git a/l2geth/rollup/sync_service_test.go b/l2geth/rollup/sync_service_test.go
index 448f7ff3bc3c54429dce2ea6e95d208d57336eb8..8d5b57dfd16a7d6ebe23c9a6dc8f639d05c60530 100644
--- a/l2geth/rollup/sync_service_test.go
+++ b/l2geth/rollup/sync_service_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth/gasprice"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
@@ -533,9 +534,9 @@ func TestSyncServiceL2GasPrice(t *testing.T) {
 	}
 	l2GasPrice := big.NewInt(100000000000)
 	state.SetState(l2GasPriceOracleAddress, l2GasPriceSlot, common.BigToHash(l2GasPrice))
-	root, _ := state.Commit(false)
+	_, _ = state.Commit(false)
 
-	service.updateL2GasPrice(&root)
+	service.updateL2GasPrice(state)
 
 	post, err := service.RollupGpo.SuggestL2GasPrice(context.Background())
 	if err != nil {
@@ -547,6 +548,95 @@ func TestSyncServiceL2GasPrice(t *testing.T) {
 	}
 }
 
+func TestSyncServiceGasPriceOracleOwnerAddress(t *testing.T) {
+	service, _, _, err := newTestSyncService(true)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// newTestSyncService doesn't set the initial owner address
+	// so it initializes to the zero value
+	owner := service.GasPriceOracleOwnerAddress()
+	if *owner != (common.Address{}) {
+		t.Fatal("address not initialized to 0")
+	}
+
+	state, err := service.bc.State()
+	if err != nil {
+		t.Fatal("cannot get state db")
+	}
+
+	// Update the owner in the state to a non zero address
+	updatedOwner := common.HexToAddress("0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8")
+	state.SetState(l2GasPriceOracleAddress, l2GasPriceOracleOwnerSlot, updatedOwner.Hash())
+	hash, _ := state.Commit(false)
+
+	// Update the cache based on the latest state root
+	if err := service.updateGasPriceOracleCache(&hash); err != nil {
+		t.Fatal(err)
+	}
+	got := service.GasPriceOracleOwnerAddress()
+	if *got != updatedOwner {
+		t.Fatalf("mismatch:\ngot %s\nexpected %s", got.Hex(), updatedOwner.Hex())
+	}
+}
+
+// Only the gas price oracle owner can send 0 gas price txs
+// when fees are enforced
+func TestFeeGasPriceOracleOwnerTransactions(t *testing.T) {
+	service, _, _, err := newTestSyncService(true)
+	if err != nil {
+		t.Fatal(err)
+	}
+	signer := types.NewEIP155Signer(big.NewInt(420))
+
+	// Fees must be enforced for this test
+	service.enforceFees = true
+	// Generate a key
+	key, _ := crypto.GenerateKey()
+	owner := crypto.PubkeyToAddress(key.PublicKey)
+	// Set as the owner on the SyncService
+	service.gasPriceOracleOwnerAddress = owner
+	if owner != *service.GasPriceOracleOwnerAddress() {
+		t.Fatal("owner mismatch")
+	}
+	// Create a mock transaction and sign using the
+	// owner's key
+	tx := mockTx()
+	// Make sure the gas price is 0 on the dummy tx
+	if tx.GasPrice().Cmp(common.Big0) != 0 {
+		t.Fatal("gas price not 0")
+	}
+	// Sign the dummy tx with the owner key
+	signedTx, err := types.SignTx(tx, signer, key)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Verify the fee of the signed tx, ensure it does not error
+	if err := service.verifyFee(signedTx); err != nil {
+		t.Fatal(err)
+	}
+	// Generate a new random key that is not the owner
+	badKey, _ := crypto.GenerateKey()
+	// Ensure that it is not the owner
+	if owner == crypto.PubkeyToAddress(badKey.PublicKey) {
+		t.Fatal("key mismatch")
+	}
+	// Sign the transaction with the bad key
+	badSignedTx, err := types.SignTx(tx, signer, badKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Attempt to verify the fee of the bad tx
+	// It should error and be a errZeroGasPriceTx
+	if err := service.verifyFee(badSignedTx); err != nil {
+		if !errors.Is(errZeroGasPriceTx, err) {
+			t.Fatal(err)
+		}
+	} else {
+		t.Fatal("err is nil")
+	}
+}
+
 // Pass true to set as a verifier
 func TestSyncServiceSync(t *testing.T) {
 	service, txCh, sub, err := newTestSyncService(true)