diff --git a/binaries/cuprated/src/rpc/handler.rs b/binaries/cuprated/src/rpc/handler.rs
index a9bbba6..e5f316e 100644
--- a/binaries/cuprated/src/rpc/handler.rs
+++ b/binaries/cuprated/src/rpc/handler.rs
@@ -5,6 +5,7 @@ use std::task::{Context, Poll};
 use anyhow::Error;
 use cuprate_consensus::BlockChainContextService;
 use cuprate_pruning::PruningSeed;
+use cuprate_types::HardFork;
 use futures::future::BoxFuture;
 use monero_serai::block::Block;
 use tower::Service;
@@ -56,6 +57,18 @@ pub enum BlockchainManagerRequest {
 
     /// The height of the next block in the chain.
     TargetHeight,
+
+    /// Calculate proof-of-work for this block.
+    CalculatePow {
+        /// The hardfork of the protocol at this block height.
+        hardfork: HardFork,
+        /// The height of the block.
+        height: usize,
+        /// The block data.
+        block: Block,
+        /// The seed hash for the proof-of-work.
+        seed_hash: [u8; 32],
+    },
 }
 
 /// TODO: use real type when public.
@@ -88,6 +101,9 @@ pub enum BlockchainManagerResponse {
 
     /// Response to [`BlockchainManagerRequest::TargetHeight`]
     TargetHeight { height: usize },
+
+    /// Response to [`BlockchainManagerRequest::CalculatePow`]
+    CalculatePow([u8; 32]),
 }
 
 /// TODO: use real type when public.
diff --git a/binaries/cuprated/src/rpc/json.rs b/binaries/cuprated/src/rpc/json.rs
index 65bc64e..ba42af7 100644
--- a/binaries/cuprated/src/rpc/json.rs
+++ b/binaries/cuprated/src/rpc/json.rs
@@ -734,10 +734,26 @@ async fn prune_blockchain(
 
 /// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2035-L2070>
 async fn calc_pow(
-    state: CupratedRpcHandler,
-    request: CalcPowRequest,
+    mut state: CupratedRpcHandler,
+    mut request: CalcPowRequest,
 ) -> Result<CalcPowResponse, Error> {
-    Ok(CalcPowResponse { pow_hash: todo!() })
+    let hardfork = HardFork::from_version(request.major_version)?;
+    let mut block_blob: Vec<u8> = hex::decode(request.block_blob)?;
+    let block = Block::read(&mut block_blob.as_slice())?;
+    let seed_hash = helper::hex_to_hash(request.seed_hash)?;
+
+    let pow_hash = blockchain_manager::calculate_pow(
+        &mut state.blockchain_manager,
+        hardfork,
+        request.height,
+        block,
+        seed_hash,
+    )
+    .await?;
+
+    let hex = hex::encode(pow_hash);
+
+    Ok(CalcPowResponse { pow_hash: hex })
 }
 
 /// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3542-L3551>
diff --git a/binaries/cuprated/src/rpc/request/blockchain_manager.rs b/binaries/cuprated/src/rpc/request/blockchain_manager.rs
index 96547ba..bd81cc0 100644
--- a/binaries/cuprated/src/rpc/request/blockchain_manager.rs
+++ b/binaries/cuprated/src/rpc/request/blockchain_manager.rs
@@ -1,6 +1,7 @@
 //! Functions for [`BlockchainManagerRequest`] & [`BlockchainManagerResponse`].
 
 use anyhow::Error;
+use cuprate_types::HardFork;
 use monero_serai::block::Block;
 use tower::{Service, ServiceExt};
 
@@ -142,3 +143,28 @@ pub(crate) async fn target_height(
 
     Ok(usize_to_u64(height))
 }
+
+/// [`BlockchainManagerRequest::CalculatePow`]
+pub(crate) async fn calculate_pow(
+    blockchain_manager: &mut BlockchainManagerHandle,
+    hardfork: HardFork,
+    height: u64,
+    block: Block,
+    seed_hash: [u8; 32],
+) -> Result<[u8; 32], Error> {
+    let BlockchainManagerResponse::CalculatePow(hash) = blockchain_manager
+        .ready()
+        .await?
+        .call(BlockchainManagerRequest::CalculatePow {
+            hardfork,
+            height: u64_to_usize(height),
+            block,
+            seed_hash,
+        })
+        .await?
+    else {
+        unreachable!();
+    };
+
+    Ok(hash)
+}