diff --git a/chronik/chronik-http/src/electrum.rs b/chronik/chronik-http/src/electrum.rs
--- a/chronik/chronik-http/src/electrum.rs
+++ b/chronik/chronik-http/src/electrum.rs
@@ -4,9 +4,14 @@
 
 //! Module for [`ChronikElectrumServer`].
 
+use std::str::FromStr;
 use std::{net::SocketAddr, sync::Arc};
 
 use abc_rust_error::Result;
+use bitcoinsuite_core::{
+    hash::{Hashed, Sha256},
+    tx::TxId,
+};
 use futures::future;
 use itertools::izip;
 use karyon_jsonrpc::{RPCError, RPCMethod, RPCService, Server};
@@ -15,7 +20,7 @@
     pem::PemObject,
     {CertificateDer, PrivateKeyDer},
 };
-use serde_json::Value;
+use serde_json::{json, Value};
 use thiserror::Error;
 
 use crate::{
@@ -52,8 +57,8 @@
 #[derive(Debug)]
 pub struct ChronikElectrumServer {
     hosts: Vec<(SocketAddr, ChronikElectrumProtocol)>,
-    _indexer: ChronikIndexerRef,
-    _node: NodeRef,
+    indexer: ChronikIndexerRef,
+    node: NodeRef,
     tls_cert_path: String,
     tls_privkey_path: String,
 }
@@ -108,14 +113,18 @@
 
 struct ChronikElectrumRPCServerEndpoint {}
 
+struct ChronikElectrumRPCBlockchainEndpoint {
+    indexer: ChronikIndexerRef,
+    node: NodeRef,
+}
+
 impl ChronikElectrumServer {
     /// Binds the Chronik server on the given hosts
     pub fn setup(params: ChronikElectrumServerParams) -> Result<Self> {
         Ok(ChronikElectrumServer {
             hosts: params.hosts,
-            // FIXME below params are unused but will be used in the future
-            _indexer: params.indexer,
-            _node: params.node,
+            indexer: params.indexer,
+            node: params.node,
             tls_cert_path: params.tls_cert_path,
             tls_privkey_path: params.tls_privkey_path,
         })
@@ -126,6 +135,11 @@
         // The behavior is to bind the endpoint name to its method name like so:
         // endpoint.method as the name of the RPC
         let server_endpoint = Arc::new(ChronikElectrumRPCServerEndpoint {});
+        let blockchain_endpoint =
+            Arc::new(ChronikElectrumRPCBlockchainEndpoint {
+                indexer: self.indexer,
+                node: self.node,
+            });
 
         let tls_cert_path = self.tls_cert_path.clone();
         let tls_privkey_path = self.tls_privkey_path.clone();
@@ -133,6 +147,7 @@
         let servers = izip!(
             self.hosts,
             std::iter::repeat(server_endpoint),
+            std::iter::repeat(blockchain_endpoint),
             std::iter::repeat(tls_cert_path),
             std::iter::repeat(tls_privkey_path)
         )
@@ -140,6 +155,7 @@
             |(
                 (host, protocol),
                 server_endpoint,
+                blockchain_endpoint,
                 tls_cert_path,
                 tls_privkey_path,
             )| {
@@ -208,6 +224,7 @@
 
                     let server = builder
                         .service(server_endpoint)
+                        .service(blockchain_endpoint)
                         .build()
                         .await
                         .map_err(|err| ServingFailed(err.to_string()))?;
@@ -247,3 +264,114 @@
         Ok(Value::Null)
     }
 }
+
+impl RPCService for ChronikElectrumRPCBlockchainEndpoint {
+    fn name(&self) -> String {
+        "blockchain".to_string()
+    }
+
+    fn get_method(&self, name: &str) -> Option<RPCMethod<'_>> {
+        match name {
+            "transaction.get" => Some(Box::new(move |params: Value| {
+                Box::pin(self.transaction_get(params))
+            })),
+            _ => None,
+        }
+    }
+}
+
+impl ChronikElectrumRPCBlockchainEndpoint {
+    async fn transaction_get(&self, params: Value) -> Result<Value, RPCError> {
+        let txid_hex: Value;
+        let verbose: Value;
+        match params {
+            Value::Array(arr) => {
+                txid_hex = arr
+                    .first()
+                    .ok_or(RPCError::InvalidParams(
+                        "Missing mandatory 'txid' parameter",
+                    ))?
+                    .clone();
+                verbose = match arr.get(1) {
+                    Some(val) => val.clone(),
+                    None => Value::Bool(false),
+                };
+            }
+            Value::Object(obj) => {
+                txid_hex = match obj.get("txid") {
+                    Some(txid) => Ok(txid.clone()),
+                    None => Err(RPCError::InvalidParams(
+                        "Missing mandatory 'txid' parameter",
+                    )),
+                }?;
+                verbose = match obj.get("verbose") {
+                    Some(verbose) => verbose.clone(),
+                    None => Value::Bool(false),
+                };
+            }
+            _ => {
+                return Err(RPCError::InvalidParams(
+                    "'params' must be an array or an object",
+                ))
+            }
+        };
+        let txid_hex = match txid_hex {
+            Value::String(s) => Ok(s),
+            _ => Err(RPCError::InvalidParams(
+                "'txid' must be a hexadecimal string",
+            )),
+        }?;
+        let txid = TxId::from_str(&txid_hex)
+            .or(Err(RPCError::InvalidParams("Failed to parse txid")))?;
+
+        let verbose = match verbose {
+            Value::Bool(v) => Ok(v),
+            _ => Err(RPCError::InvalidParams("'verbose' must be a boolean")),
+        }?;
+
+        let indexer = self.indexer.read().await;
+        let query_tx = indexer.txs(&self.node);
+        let raw_tx = hex::encode(
+            query_tx
+                .raw_tx_by_id(&txid)
+                .or(Err(RPCError::InvalidRequest("Unknown transaction id")))?
+                .raw_tx,
+        );
+        if !verbose {
+            return Ok(Value::String(raw_tx));
+        }
+
+        let tx = query_tx
+            .tx_by_id(txid)
+            // The following error should be unreachable, unless raw_tx_by_id
+            // and tx_by_id are inconsistent
+            .or(Err(RPCError::InvalidRequest("Unknown transaction id")))?;
+        let blockchaininfo = indexer.blocks(&self.node).blockchain_info();
+        if blockchaininfo.is_err() {
+            return Err(RPCError::InternalError);
+        }
+        if tx.block.is_none() {
+            // mempool transaction
+            return Ok(json!({
+                "confirmations": 0,
+                "hash": txid_hex,
+                "hex": raw_tx,
+                "time": tx.time_first_seen,
+            }));
+        }
+        let block = tx.block.unwrap();
+        let blockhash = Sha256::from_le_slice(block.hash.as_ref()).unwrap();
+        let confirmations =
+            blockchaininfo.ok().unwrap().tip_height - block.height + 1;
+        // TODO: more verbose fields, inputs, outputs
+        //      (but for now only "confirmations" is used in Electrum ABC)
+        Ok(json!({
+            "blockhash": blockhash.hex_be(),
+            "blocktime": block.timestamp,
+            "confirmations": confirmations,
+            "hash": txid_hex,
+            "hex": raw_tx,
+            "time": tx.time_first_seen,
+        }))
+    }
+}
diff --git a/test/functional/chronik_electrum_blockchain.py b/test/functional/chronik_electrum_blockchain.py
new file mode 100644
--- /dev/null
+++ b/test/functional/chronik_electrum_blockchain.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2024-present The Bitcoin developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""
+Test Chronik's electrum interface: blockchain.* methods
+"""
+from test_framework.blocktools import (
+    GENESIS_BLOCK_HASH,
+    GENESIS_CB_SCRIPT_PUBKEY,
+    GENESIS_CB_SCRIPT_SIG,
+    GENESIS_CB_TXID,
+    TIME_GENESIS_BLOCK,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+COINBASE_TX_HEX = (
+    "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d"
+    + GENESIS_CB_SCRIPT_SIG.hex()
+    + "ffffffff0100f2052a0100000043"
+    + GENESIS_CB_SCRIPT_PUBKEY.hex()
+    + "00000000"
+)
+
+
+class ChronikElectrumBlockchain(BitcoinTestFramework):
+    def set_test_params(self):
+        self.setup_clean_chain = True
+        self.num_nodes = 1
+        self.extra_args = [["-chronik"]]
+
+    def skip_test_if_missing_module(self):
+        self.skip_if_no_chronik()
+
+    def run_test(self):
+        self.client = self.nodes[0].get_chronik_electrum_client()
+
+        for response in (
+            self.client.blockchain.transaction.get(GENESIS_CB_TXID),
+            self.client.blockchain.transaction.get(GENESIS_CB_TXID, False),
+            self.client.blockchain.transaction.get(txid=GENESIS_CB_TXID),
+            self.client.blockchain.transaction.get(txid=GENESIS_CB_TXID, verbose=False),
+        ):
+            assert_equal(response.result, COINBASE_TX_HEX)
+
+        for response in (
+            self.client.blockchain.transaction.get(GENESIS_CB_TXID, True),
+            self.client.blockchain.transaction.get(txid=GENESIS_CB_TXID, verbose=True),
+        ):
+            assert_equal(
+                response.result,
+                {
+                    "blockhash": GENESIS_BLOCK_HASH,
+                    "blocktime": TIME_GENESIS_BLOCK,
+                    "confirmations": 1,
+                    "hash": GENESIS_CB_TXID,
+                    "hex": COINBASE_TX_HEX,
+                    "time": 0,
+                },
+            )
+        self.generate(self.nodes[0], 2)
+        assert_equal(
+            self.client.blockchain.transaction.get(
+                txid=GENESIS_CB_TXID, verbose=True
+            ).result["confirmations"],
+            3,
+        )
+
+
+if __name__ == "__main__":
+    ChronikElectrumBlockchain().main()