diff --git a/.changeset/good-cameras-serve.md b/.changeset/good-cameras-serve.md
new file mode 100644
index 000000000..1f1895504
--- /dev/null
+++ b/.changeset/good-cameras-serve.md
@@ -0,0 +1,5 @@
+---
+"openzeppelin-solidity": minor
+---
+
+`Calldata`: Library with `emptyBytes` and `emptyString` functions to generate empty `bytes` and `string` calldata types.
diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol
index d13d51939..5cd927c63 100644
--- a/contracts/account/utils/draft-ERC4337Utils.sol
+++ b/contracts/account/utils/draft-ERC4337Utils.sol
@@ -5,6 +5,7 @@ pragma solidity ^0.8.20;
 
 import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
 import {Math} from "../../utils/math/Math.sol";
+import {Calldata} from "../../utils/Calldata.sol";
 import {Packing} from "../../utils/Packing.sol";
 
 /**
@@ -107,7 +108,7 @@ library ERC4337Utils {
 
     /// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted.
     function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
-        return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:];
+        return self.initCode.length < 20 ? Calldata.emptyBytes() : self.initCode[20:];
     }
 
     /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
@@ -157,14 +158,6 @@ library ERC4337Utils {
 
     /// @dev Returns the fourth section of `paymasterAndData` from the {PackedUserOperation}.
     function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
-        return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:];
-    }
-
-    // slither-disable-next-line write-after-write
-    function _emptyCalldataBytes() private pure returns (bytes calldata result) {
-        assembly ("memory-safe") {
-            result.offset := 0
-            result.length := 0
-        }
+        return self.paymasterAndData.length < 52 ? Calldata.emptyBytes() : self.paymasterAndData[52:];
     }
 }
diff --git a/contracts/utils/Calldata.sol b/contracts/utils/Calldata.sol
new file mode 100644
index 000000000..60e0b08b0
--- /dev/null
+++ b/contracts/utils/Calldata.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+/**
+ * @dev Helper library for manipulating objects in calldata.
+ */
+library Calldata {
+    // slither-disable-next-line write-after-write
+    function emptyBytes() internal pure returns (bytes calldata result) {
+        assembly ("memory-safe") {
+            result.offset := 0
+            result.length := 0
+        }
+    }
+
+    // slither-disable-next-line write-after-write
+    function emptyString() internal pure returns (string calldata result) {
+        assembly ("memory-safe") {
+            result.offset := 0
+            result.length := 0
+        }
+    }
+}
diff --git a/test/utils/Calldata.test.js b/test/utils/Calldata.test.js
new file mode 100644
index 000000000..7e9d3d478
--- /dev/null
+++ b/test/utils/Calldata.test.js
@@ -0,0 +1,22 @@
+const { ethers } = require('hardhat');
+const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+
+async function fixture() {
+  const mock = await ethers.deployContract('$Calldata');
+  return { mock };
+}
+
+describe('Calldata utilities', function () {
+  beforeEach(async function () {
+    Object.assign(this, await loadFixture(fixture));
+  });
+
+  it('emptyBytes', async function () {
+    await expect(this.mock.$emptyBytes()).to.eventually.equal('0x');
+  });
+
+  it('emptyString', async function () {
+    await expect(this.mock.$emptyString()).to.eventually.equal('');
+  });
+});