diff --git a/category/vm/evm/CMakeLists.txt b/category/vm/evm/CMakeLists.txt
index ed1c950766..3be0d36e4b 100644
--- a/category/vm/evm/CMakeLists.txt
+++ b/category/vm/evm/CMakeLists.txt
@@ -21,6 +21,8 @@ target_sources(monad-vm-evm PRIVATE
"delegation.cpp"
"delegation.hpp"
"explicit_traits.hpp"
+ "message.cpp"
+ "message.hpp"
"opcodes.hpp"
"opcodes_xmacro.hpp"
"revision.cpp"
diff --git a/category/vm/evm/message.cpp b/category/vm/evm/message.cpp
new file mode 100644
index 0000000000..1c4a9256cc
--- /dev/null
+++ b/category/vm/evm/message.cpp
@@ -0,0 +1,86 @@
+// Copyright (C) 2025-26 Category Labs, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+
+#include
+
+#include
+#include
+#include
+
+// Value equality with evmc_call_kind / evmc_flags.
+static_assert(sizeof(monad_call_kind) == sizeof(evmc_call_kind));
+
+#define MONAD_ASSERT_ENUM_EQ(name) \
+ static_assert( \
+ std::to_underlying(MONAD_##name) == std::to_underlying(EVMC_##name))
+
+MONAD_ASSERT_ENUM_EQ(CALL);
+MONAD_ASSERT_ENUM_EQ(DELEGATECALL);
+MONAD_ASSERT_ENUM_EQ(CALLCODE);
+MONAD_ASSERT_ENUM_EQ(CREATE);
+MONAD_ASSERT_ENUM_EQ(CREATE2);
+MONAD_ASSERT_ENUM_EQ(EOFCREATE);
+MONAD_ASSERT_ENUM_EQ(STATIC);
+MONAD_ASSERT_ENUM_EQ(DELEGATED);
+
+#undef MONAD_ASSERT_ENUM_EQ
+
+// Layout equality with evmc_message.
+static_assert(std::is_standard_layout_v);
+static_assert(std::is_trivially_copyable_v);
+static_assert(sizeof(monad_message) == 192);
+static_assert(sizeof(monad_message) == sizeof(evmc_message));
+static_assert(alignof(monad_message) == alignof(evmc_message));
+
+#define MONAD_ASSERT_MESSAGE_FIELD_EQ(field) \
+ static_assert( \
+ offsetof(monad_message, field) == offsetof(evmc_message, field))
+
+MONAD_ASSERT_MESSAGE_FIELD_EQ(kind);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(flags);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(depth);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(gas);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(recipient);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(sender);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(input_data);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(input_size);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(value);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(create2_salt);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(code_address);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(memory_handle);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(memory);
+MONAD_ASSERT_MESSAGE_FIELD_EQ(memory_capacity);
+
+#undef MONAD_ASSERT_MESSAGE_FIELD_EQ
+
+// Same offset does not rule out a narrowed field padded back to the next one;
+// the enum is pinned above, Address/bytes32_t in their own headers.
+#define MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(field) \
+ static_assert(std::is_same_v< \
+ decltype(monad_message::field), \
+ decltype(evmc_message::field)>)
+
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(flags);
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(depth);
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(gas);
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(input_data);
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(input_size);
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(memory_handle);
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(memory);
+MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ(memory_capacity);
+
+#undef MONAD_ASSERT_MESSAGE_FIELD_TYPE_EQ
diff --git a/category/vm/evm/message.hpp b/category/vm/evm/message.hpp
new file mode 100644
index 0000000000..dd1f7f04d4
--- /dev/null
+++ b/category/vm/evm/message.hpp
@@ -0,0 +1,60 @@
+// Copyright (C) 2025-26 Category Labs, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+
+// Mirrors evmc_call_kind 1:1; values asserted equal in message.cpp.
+enum monad_call_kind
+{
+ MONAD_CALL = 0,
+ MONAD_DELEGATECALL = 1,
+ MONAD_CALLCODE = 2,
+ MONAD_CREATE = 3,
+ MONAD_CREATE2 = 4,
+ MONAD_EOFCREATE = 5
+};
+
+// Mirrors evmc_flags 1:1; values asserted equal in message.cpp.
+enum monad_call_flags
+{
+ MONAD_STATIC = 1,
+ MONAD_DELEGATED = 2
+};
+
+// Mirrors the category-labs fork's evmc_message field-for-field (no
+// code/code_size, memory-pool fields present); layout asserted in message.cpp.
+struct monad_message
+{
+ monad_call_kind kind;
+ uint32_t flags;
+ int32_t depth;
+ int64_t gas;
+ monad::Address recipient;
+ monad::Address sender;
+ uint8_t const *input_data;
+ size_t input_size;
+ monad::bytes32_t value;
+ monad::bytes32_t create2_salt;
+ monad::Address code_address;
+ uint8_t *memory_handle;
+ uint8_t *memory;
+ uint32_t memory_capacity;
+};