{"mips":[{"number":"0018","numberShort":"18","title":"Call Stack Introspection Opcodes","description":"Add extension opcodes to inspect the depth and caller chain of the current call stack","status":"Review","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-18-callstackdepth-and-callern-call-stack-introspection-opcodes/556","forumTopicId":556,"created":"2026-09-24","requires":["0007"],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-18.md","contentHash":"9d7b6c4fa955ae5fa4b5b6a53c8a131bd36e2193c7e16846645654a1fdf11911","abstract":"This MIP allocates two extension opcodes under the `EXTENSION` (`0xAE`) mechanism defined in [MIP-7](./MIP-7.md). `CALLSTACKDEPTH` pushes the depth of the current call frame in the EVM call stack. `CALLERN` generalizes `CALLER`: it pops an index `n` and pushes the caller address `n` frames above the current frame. Both opcodes take no immediate arguments and have constant-time implementations.","motivation":"These opcodes are intended to support future Monad protocol upgrades under development at Category Labs.","specification":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.html) and [RFC 8174](https://www.ietf.org/rfc/rfc8174.html).\n\n### Parameters\n\n| Name                      | Value  |\n| ------------------------- | ------ |\n| `CALLSTACKDEPTH_SELECTOR` | `0x00` |\n| `CALLERN_SELECTOR`        | `0x01` |\n| `G_CALLSTACKDEPTH`        | 2      |\n| `G_CALLERN`               | 2      |\n\n### Extension Selectors\n\n| Extended Opcode | Mnemonic         | Immediates | Stack Input | Stack Output |\n| --------------- | ---------------- | ---------- | ----------- | ------------ |\n| `0xAE 0x00`     | `CALLSTACKDEPTH` | None       | —           | `depth`      |\n| `0xAE 0x01`     | `CALLERN`        | None       | `n`         | `caller`     |\n\nNeither extended opcode is followed by argument bytes. The byte following the selector is decoded as the next instruction.\n\n### Call Frames\n\nA **call frame** is created for each message executed by the EVM: the top-level message of a transaction, and each message created by `CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`, `CREATE` and `CREATE2`. Calls to precompiled contracts create a frame for the purpose of depth accounting, but no code in that frame can observe it.\n\nLet the frames active during execution be `F_0, F_1, ..., F_d`, where `F_0` is the frame of the transaction's top-level message and `F_d` is the currently executing frame. `d` is the **depth** of the current frame.\n\nLet `sender(F_i)` be the value that `CALLER` returns when executed in frame `F_i`. In particular, `sender(F_0)` is the transaction origin, and for a frame created by `DELEGATECALL` or `CALLCODE`, `sender(F_i)` follows the existing semantics of `CALLER` in those frames.\n\n### Opcode Semantics\n\n#### `CALLSTACKDEPTH`\n\n`CALLSTACKDEPTH` pushes `d` onto the stack.\n\n#### `CALLERN`\n\n`CALLERN` pops a 256-bit unsigned integer `n` from the stack, then:\n\n- If `n <= d`, it pushes `sender(F_{d - n})` onto the stack, zero-extended to 32 bytes.\n- Otherwise, it pushes `0` onto the stack.\n\nAs a result:\n\n- `CALLERN(0)` is equivalent to `CALLER`.\n- `CALLERN(CALLSTACKDEPTH)` is equivalent to `ORIGIN`.\n\n### Gas Costs\n\n`CALLSTACKDEPTH` costs `G_CALLSTACKDEPTH` gas. `CALLERN` costs `G_CALLERN` gas, regardless of the value of `n`. These costs apply to the complete extended opcode; no additional gas is charged for the `EXTENSION` prefix.\n\n### Exceptional Conditions\n\nBoth opcodes are subject to the standard exceptional halting conditions for stack underflow, stack overflow and insufficient gas. `CALLERN` MUST NOT halt exceptionally because `n` is out of range.","rationale":"### Frame Indexing\n\n`CALLERN` is indexed so that `CALLERN(0)` coincides with `CALLER`, which makes the new opcode a strict generalization of the existing one. `CALLSTACKDEPTH` is 0-based, so that `CALLERN(CALLSTACKDEPTH)` coincides with `ORIGIN`, and no index is wasted.\n\n### Out-of-Range Indices\n\nAn index above the current depth returns `0` rather than halting, following the precedent of `BLOCKHASH` for out-of-range lookups. This lets contracts probe the call stack without first reading its depth.\n\n### Delegated Frames\n\n`CALLERN` reports the same value that `CALLER` would report in each ancestor frame. As a result, `DELEGATECALL` and `CALLCODE` frames can cause the same address to appear at consecutive indices. This definition is the simplest one to specify and implement, and it ensures that every value returned by `CALLERN` was also observable by `CALLER` in some ancestor frame.\n\n### Gas Costs\n\nBoth opcodes read state that an implementation already holds in memory for the duration of the transaction, and they can be implemented in constant time. Their cost therefore matches `CALLER` and `ADDRESS`.\n\n### Encoding\n\nNeither opcode needs immediate arguments, so neither uses the argument encodings defined in MIP-7. `CALLERN` takes its index from the stack instead of an immediate so that it can be computed at runtime (for example, from `CALLSTACKDEPTH`).","backwardsCompatibility":"Under MIP-7, the extended opcodes `0xAE 0x00` and `0xAE 0x01` currently behave like `INVALID`. Existing code that executes either sequence halts exceptionally today and will execute successfully after this MIP. Such code is not expected to exist in practice, because the sequences have never had defined behavior on Monad or Ethereum.\n\nA trailing `EXTENSION` byte has no selector and must halt exceptionally, as [MIP-7](./MIP-7.md) specifies.\n\n`JUMPDEST` analysis is unaffected.","securityConsiderations":"### Authorization\n\n`CALLERN` makes the identity of every ancestor caller available to a contract, not just its immediate caller. Using `CALLERN` for authorization inherits the weaknesses of authorizing with `ORIGIN`: an attacker who persuades a privileged account to call an attacker-controlled contract can relay calls that appear to originate from that account. Contracts should not use `CALLERN(n)` for `n > 0` for authorization.\n\n### Context-Dependent Behavior\n\nBoth opcodes let a contract behave differently depending on how it was reached. Tools that assume a contract's behavior depends only on its immediate caller, calldata and state, such as transaction simulation, account abstraction validation and static analysis, may need to account for this.\n\n### Call Depth\n\n`CALLSTACKDEPTH` exposes information that was previously only observable indirectly, by approaching the call depth limit. The 63/64 gas forwarding rule already makes the depth limit unreachable in practice, so this is not expected to create new griefing vectors.","subsystems":["execution","evm","staking"],"createdAt":"2026-09-29T14:49:03+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0017","numberShort":"17","title":"PropAMM Router Interface","description":"Minimal routing interface for proprietary AMMs","status":"Draft","type":"Standards Track","category":"MRC","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mrc-17-propamm-routing-interface/","created":"2026-09-28","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MRCs/MRC-17.md","contentHash":"c6992d807f315909fb359ee6d77998338953f81cb1ddfee92aacc589d933f12e","abstract":"This MRC defines a standard routing interface for proprietary automated market makers (“propAMMs”). A compatible propAMM or its adapter contract exposes functions for exact input quoting and swap execution. Quotes may return opaque venue-specific data that is passed back during execution.\n\nThe standard defines functions that are mainly used by onchain aggregators. The propAMM internal mechanisms are not enforced by this proposal.","motivation":"PropAMMs derive prices following different mechanisms: from venue-specific logic, oracle updates, offchain market-making systems, signed price commitments, inventory constraints, or other custom mechanisms rather than a pre-defined onchain curve.\n\nThis flexibility creates fragmentation for routers and onchain aggregators as existing venues expose different interfaces. Without a common interface, every router must implement and maintain an integration code specific to a single propAMM.\n\nCommon integration frictions include:\n\n1. **Non-standard quote functions:** propAMMs expose different input conventions, return values, and some of them do state mutation during asset quoting.\n2. **Non-standard swap functions:** swap interfaces vary in direction encoding, recipient semantics, slippage checks, and callbacks, etc.\n3. **Inconsistent token transfer:** some venues transfer the input token from addresses via allowance, while others expect tokens to be explicitly transferred before swap execution.\n4. **Requirement of supplementary data for swap execution:** certain propAMMs require signed prices or quote identifiers to execute a swap.\n5. **Different deployment architectures:** propAMMs may use per-pair contracts, others follow the singleton design.\n\nThis MRC allows onchain aggregators to quote and execute swaps via any propAMM contract or adapter through one interface without implementing any venue-specific logic.","specification":"The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.\n\n### Interface\n\nEvery compatible propAMM contract MUST implement the following interface:\n\n```solidity\n/// @title Proprietary AMM Router Interface\n/// @dev A contract may serve one or more markets.\n///      `tokenIn` and `tokenOut` specify the requested assets and direction.\ninterface IPropAMMRouter {\n    /// @notice Emitted after a successful swap.\n    /// @param sender The caller that funded the swap.\n    /// @param to The recipient credited with the output token.\n    /// @param tokenIn The input token transferred from `sender`.\n    /// @param tokenOut The output token credited to `to`.\n    /// @param amountIn The exact input amount pulled from `sender`.\n    /// @param amountOut The actual output amount credited to `to`.\n    event PropAMMSwap(\n        address indexed sender,\n        address to,\n        address indexed tokenIn,\n        address indexed tokenOut,\n        uint256 amountIn,\n        uint256 amountOut\n    );\n\n    /// @notice Quotes an exact input swap.\n    /// @param tokenIn The ERC-20 input token.\n    /// @param tokenOut The ERC-20 output token.\n    /// @param amountIn The exact input amount in base units of `tokenIn`.\n    /// @param quoteData Opaque venue specific quote input, may be empty.\n    /// @return amountOut The expected output amount in base units of `tokenOut`.\n    /// @return swapData Opaque venue specific data to supply to `swap()`, may be empty.\n    /// @dev This function is intentionally non-view. Routers that integrate\n    ///      with `IPropAMMRouter` must execute it in a call frame whose state\n    ///      changes are reverted.\n    function getAmountOut(\n        address tokenIn,\n        address tokenOut,\n        uint256 amountIn,\n        bytes calldata quoteData\n    ) external returns (uint256 amountOut, bytes memory swapData);\n\n    /// @notice Executes an exact input swap.\n    /// @dev Before calling, `msg.sender` must grant this contract an ERC-20\n    ///      allowance of at least `amountIn` for `tokenIn`. The contract\n    ///      pulls exactly `amountIn` of `tokenIn` during this call and credits\n    ///      the actual output in `tokenOut` to `to` before returning.\n    /// @param tokenIn The ERC-20 input token.\n    /// @param tokenOut The ERC-20 output token.\n    /// @param to The recipient of the output token.\n    /// @param amountIn The exact amount of `tokenIn` pulled from `msg.sender`.\n    /// @param amountOutMin The minimum amount of `tokenOut` credited to `to`.\n    /// @param deadline The timestamp after which the swap must revert.\n    /// @param swapData Opaque venue specific execution data, may be empty.\n    /// @return amountOut The actual amount of `tokenOut` credited to `to`.\n    function swap(\n        address tokenIn,\n        address tokenOut,\n        address to,\n        uint256 amountIn,\n        uint256 amountOutMin,\n        uint256 deadline,\n        bytes calldata swapData\n    ) external returns (uint256 amountOut);\n}\n```\n\n### Interface Scope and Asset Selection\n\nA compatible `IPropAMMRouter` MAY be a native propAMM contract or an adapter and MAY represent one or more markets. `tokenIn` and `tokenOut` specify the market’s assets and swap direction.\n\nMarket discovery is outside the scope of this MRC. Implementations MAY expose additional getters, but routers SHALL NOT require such extensions to use the interface defined here. Discovery MAY be standardized separately.\n\n### Caller and Taker Semantics\n\nWhen an MRC-17 compatible contract is called through a router, `msg.sender` is the router and is the address from which the contract pulls input tokens. It is not necessarily the original token owner or the output token recipient.\n\nGiven that the interface does not include a separate taker input, A propAMM that have a dynamic pricing and swapping mechanism based on a taker input MAY use `quoteData` and `swapData` fields to encode the taker address.\n\n### Quoting\n\nThe `getAmountOut()` returns the expected output for an exact input swap and any opaque data required to execute that quote.\n\nIf `swap()` is called by the same `msg.sender`, with the `tokenIn`, `tokenOut` and `amountIn`, using the returned `swapData`, before its expiry and without relevant venue state changes, the MRC-17 compatible contract MUST be capable of delivering at least the quoted `amountOut`.\n\n`getAmountOut()` is intentionally non-view and is not required to be called using the `STATICCALL` opcode. An MRC-17-compatible propAMM MAY apply state changes before returning a quote, including executing a swap that deliberately reverts and bubble up its result through revert data.\n\nIf the `swapData` returned by `getAmountOut()` is relied on during `swap()` execution, then it MUST be valid when `swap()` is invoked as long as it’s not expiring and not impacted by state changes.\n\n### Swap Execution\n\n`swap()` executes an exact input swap through the conforming contract.\n\nRequirements:\n\n- `amountIn` MUST be interpreted as the exact input amount.\n- `amountOutMin` MUST be interpreted as the minimum actual output amount credited to `to`.\n- `to` MUST NOT be the zero address.\n- `deadline` MUST be enforced by the conforming contract. If `block.timestamp > deadline`, the call MUST revert.\n- `swapData` is opaque to the router and MAY contain venue specific execution data.\n- Before calling `swap()`, `msg.sender` MUST grant the conforming contract an ERC-20 allowance of at least `amountIn` for the input token.\n- During `swap()`, the conforming contract MUST transfer exactly `amountIn` of the input token from `msg.sender` using ERC-20 `transferFrom` semantics, with the contract acting as the approved spender.\n- The conforming contract MUST NOT require input tokens to be transferred before `swap()` is called.\n- The conforming contract MUST NOT use a pre existing token balance as a substitute for transferring `amountIn` from `msg.sender` for the current invocation.\n- The actual `amountOut` MUST be at least `amountOutMin`, otherwise the conforming contract MUST revert.\n\nRecipient and deadline validation are under the MRC obligations. A compatible propAMM contract MUST enforce them independently for every caller and MUST NOT rely on the router to validate either value.\n\n### Token Semantics\n\nThis MRC defines behavior in terms of ERC-20 allowances and transfers. Native assets MUST be represented by an ERC-20 compatible wrapped token.\n\nFee on transfer, rebasing, or otherwise non standard tokens are compatible only if the contract normalizes their behavior and still satisfies every exact input and actual output requirement in this specification.\n\n### Exact Input Only\n\nThis interface is a standard for exact input quoting and execution only. Exact output swaps are excluded from the base interface because they are not supported by every propAMM venue.\n\nImplementations MAY expose exact output methods outside this MRC.\n\n### Events\n\nA compatible contract SHOULD emit the `PropAMMSwap` event after each successful `swap()`.\n\nIn `PropAMMSwap`:\n\n- `sender` MUST equal the `msg.sender` that invoked `swap()`\n- `to` MUST equal the output recipient passed to `swap()`\n- `tokenIn` and `tokenOut` MUST equal the corresponding arguments passed to `swap()` and identify the actual input and output assets.\n- `amountIn` MUST equal the exact input amount transferred from `sender`\n- `amountOut` MUST equal the actual output amount credited to `to`\n\nFor routed swaps, `sender` is normally the integrating router rather than the actual taker. A propAMM contract MAY emit additional venue specific events containing the taker identity.\n\n### Quote Isolation\n\nRouters MUST treat every quote path as state changing and untrusted, even when a contract declares `getAmountOut` as `view`. Each quote MUST execute in an isolated child frame that reverts unconditionally after its success status and returndata have been captured.\n\nWhen an integration leaves quote gas and returned data size unrestricted onchain, a candidate can exhaust the transaction’s available gas or cause the complete router request to fail. The offchain route or calldata generator MUST simulate the exact complete router call and enforce its resource policy before submitting the candidate set.\n\n### Allowances and Token Custody\n\nRouters integrating MRC-17 contracts SHOULD approve only the selected contract and SHOULD limit the allowance to the exact `amountIn` required for the current swap.\n\n### Chain Specifics\n\nThis MRC defines an application layer interface and does not require a precompile, canonical deployment address, registry, or privileged implementation.","rationale":"### Router Interface\n\nStandardizing propAMM contracts would force venues to redesign their core architecture.\nA routing interface permits native propAMM contracts to conform directly, while a thin adapter gives existing venues a common integration target without changing their core mechanisms.\n\nA popular design that aggregators rely on is directly using  the venue’s swap logic to retrieve the quote amount, by calling the swap function, reverting any state change in the call frame, decoding, and returning the swapped amount as a quote. Even for such aggregators that don’t rely on a quoting method, this proposal unifies the swap function and allows those aggregators to use the revert pattern without embedding any venue-specific logic.\n\n### Explicit Input and Output Tokens\n\nExplicit token addresses allow the same interface to support per-pair contracts and singleton venues that serve multiple markets. Routers specify the assets and direction directly, without requiring token-ordering getters or a separate adapter for every pair.\n\n### Opaque `quoteData` and `swapData`\n\npropAMMs have different requirements across the quote to execution boundary. Some require no additional data, while others may require data such as signed prices or quote identifiers.\n\nOpaque byte fields support these designs without requiring the standard to understand every venue specific format.\n\n### `getAmountOut()` as a Non-View Function\n\nSome venues derive quotes by using the same state changing logic used for execution and deliberately reverting after the output is known. These quote paths work under RPC `eth_call` but fail under EVM `STATICCALL`, even though their effects are intended to be discarded.\n\nMaking `getAmountOut` non-view permits aggregators to integrate those venues without reimplementing their pricing logic. Static compatible contracts remain valid and are still called successfully through an ordinary `CALL`.\n\n### Pair Discovery Outside the Interface\n\nDiscovery differs substantially between venues. Some use factories or singleton registries, while others rely on deterministic deployment. This MRC focuses only on the router facing quote and execution boundary.","backwardsCompatibility":"This MRC introduces a new application layer interface and does not modify existing contracts, the EVM, consensus or any existing token standard.\n\nExisting propAMMs can integrate by deploying a wrapper for their quote and swap logic following the `IPropAMMRouter` interface.","securityConsiderations":"The [quote isolation](#quote-isolation) requirements address state changes and resource exhaustion during quoting.\nThe [allowance and token custody](#allowances-and-token-custody) requirements limit approvals to the selected contract and the current swap's exact input.","subsystems":["rpc","consensus","evm","db","economics","execution"],"createdAt":"2026-10-06T15:02:43+04:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0016","numberShort":"16","title":"JSON-RPC Query Methods","description":"JSON-RPC methods that enable efficient queries for raw chain history data","status":"Draft","type":"Standards Track","category":"Interface","authors":[{"name":"Kevin Koste","github":"typedarray"},{"name":"Kyle Scott","github":"kyscott18"},{"name":"Jay Miller"},{"name":"Andre Benedito"}],"discussionsTo":"https://forum.monad.xyz/t/draft-mip-json-rpc-query-methods/546","forumTopicId":546,"created":"2026-09-21","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-16.md","contentHash":"8efcd2f36cd81e7017b292863ac3e9e4573a578bcb82de3875be4f3441cff37e","abstract":"This MIP introduces five new JSON-RPC methods that enable efficient queries for raw chain history data:\n\n- `eth_queryBlocks`\n- `eth_queryTransactions`\n- `eth_queryLogs`\n- `eth_queryTraces`\n- `eth_queryTransfers`\n\nAll five methods share a common request and response shape. A request specifies a block range, a traversal order, a target page size, an optional filter, and an optional field selection that also controls which related objects are joined into the response. A response returns normalized, deduplicated result arrays keyed by object type, along with three block references that clients use for pagination and reorg detection.","motivation":"The standard Ethereum JSON-RPC interface provides inadequate primitives for querying chain history. Many useful queries are not possible, and the queries that are supported suffer from overfetching and inefficient pagination.\n\nThis proposal aims to address four specific shortcomings.\n\n- **Filtering.** The `eth_getLogs` method supports log filtering, but there is no way to filter transactions or traces. To query all transactions sent to a specific address, the user must fetch entire blocks and filter client-side.\n- **Relations.** There is no way to join related objects in a single request. To fetch a set of logs and related transaction inputs, the user must make N+1 RPC requests (one to fetch logs, then one per unique transaction).\n- **Field selection.** Every RPC method returns a fixed object schema. Users that only need block number and timestamp have no choice but to fetch large unrelated fields like `logsBloom`, only to immediately discard them.\n- **Pagination.** The `eth_getLogs` pagination design causes frequent timeouts and client-side workarounds. The RPC methods for blocks, transactions, and traces don't support range queries at all.\n\nThese shortcomings impose unnecessary compute, memory, and bandwidth costs on both users and node operators.","specification":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.html) and [RFC 8174](https://www.ietf.org/rfc/rfc8174.html).\n\n### Conventions\n\n#### Value types\n\nThis document uses the value type conventions of the Ethereum JSON-RPC interface:\n\n- `QUANTITY` — an unsigned integer, encoded as a `0x`-prefixed, big-endian hexadecimal string with no leading zeroes. Zero MUST be encoded as `\"0x0\"`.\n- `DATA` — a byte sequence, encoded as a `0x`-prefixed hexadecimal string with two hex digits per byte, and therefore an even number of digits. The empty byte sequence MUST be encoded as `\"0x\"`.\n- `TAG` — one of the block tag strings accepted by `fromBlock` and `toBlock`; see [Block range](#block-range).\n\nAll other types named in this document (`string`, `number`, `boolean`, `object`, and array forms such as `DATA[]`, `number[]`, and `string[]`) are the corresponding JSON types.\n\n#### Field availability\n\nEach method's response table has an Availability column describing when a field is present in an object.\n\n| Value | Meaning |\n| --- | --- |\n| Required | Present in every object of that type. |\n| Fork-dependent | Present only when the feature that introduced the field is active for the block being returned. |\n| Type-dependent | Present only for the transaction types that carry the field, such as `maxFeePerGas` on EIP-1559 transactions. |\n| Optional | MAY be absent even when selected, either because it does not apply to the object — `error` on a call trace that succeeded, for example — or because the server does not populate it. |\n\n### Request and response\n\n#### Request\n\n For all methods, `params` MUST be a single-element array whose first element is the request object defined below.\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `fromBlock` | `QUANTITY` or `TAG` | No | Inclusive range start. See [Block range](#block-range). |\n| `toBlock` | `QUANTITY` or `TAG` | No | Inclusive range end. See [Block range](#block-range). |\n| `order` | `string` | No | Traversal direction, `asc` (default) or `desc`. See [Block range](#block-range). |\n| `target` | `QUANTITY` | No | Target number of primary objects per page. See [Block-aligned pagination](#block-aligned-pagination). |\n| `filter` | `object` | No | Method-specific filter object. See [Filters](#filters) and each method's Filter section. |\n| `fields` | `object` | No | Method-specific selection of fields to include and relations to join. See [Fields and relations](#fields-and-relations) and each method's Fields section. |\n\n#### Response\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `data` | `object` | Method-specific query result object, keyed by object name. Each value is an array of objects containing the requested fields. |\n| `fromBlock` | `{ number: QUANTITY, hash: DATA, parentHash: DATA }` | The resolved starting block at query execution time. If `fromBlock` in the request was a tag, this reflects the block that tag resolved to. |\n| `toBlock` | `{ number: QUANTITY, hash: DATA, parentHash: DATA }` | The resolved ending block at query execution time. If `toBlock` was `\"latest\"` or omitted in `asc` mode, this reflects the block the node considered latest at query execution time. |\n| `cursorBlock` | `{ number: QUANTITY, hash: DATA, parentHash: DATA }` | The final block in the page (inclusive). The response contains every matching primary object from `fromBlock` through `cursorBlock`. See [Block-aligned pagination](#block-aligned-pagination). |\n\n### Query semantics\n\n#### Block range\n\nEach request scans a contiguous, inclusive range of blocks from `fromBlock` to `toBlock`, in the direction given by `order`:\n\n- `asc` (default): scan from `fromBlock` upward, returning results oldest-first. `fromBlock` is the lower bound and `toBlock` the upper bound.\n- `desc`: scan from `fromBlock` downward, returning results newest-first. `fromBlock` is the upper bound and `toBlock` the lower bound.\n\n`fromBlock` and `toBlock` each accept a hex-encoded block number (for example `\"0xF4240\"`) or a tag: `\"latest\"`, `\"earliest\"`, `\"safe\"`, `\"finalized\"`. Tags MUST be resolved server-side at query execution time. If omitted, `fromBlock` defaults to `\"earliest\"` in `asc` mode or `\"latest\"` in `desc` mode, and `toBlock` defaults to `\"latest\"` in `asc` mode or `\"earliest\"` in `desc` mode. An `asc` scan with `toBlock` omitted therefore runs to chain tip, and a `desc` scan with `toBlock` omitted runs to genesis.\n\nA resolved range is inverted if `fromBlock` is greater than `toBlock` in `asc` mode, or less than `toBlock` in `desc` mode. The server MUST fail a request with an inverted range with `-32602`.\n\nA node is not required to hold the entire chain history. Each method has an *availability window*: the contiguous range of blocks for which that node can serve that method's objects. The window MAY differ between methods on the same node, since a node may retain every block back to genesis while retaining traces only for recent history.\n\nAfter resolving `fromBlock` and `toBlock` to block numbers, the server MUST compare the resolved range against the availability window for the method being called. If any block in that range falls outside the window — whether because the data was pruned, was never indexed, or the block does not yet exist — the server MUST fail the request with `-32001` rather than returning a truncated result.\n\n#### Block-aligned pagination\n\nEach response covers a *page*: a contiguous run of blocks from `fromBlock` through `cursorBlock` in traversal order. Pages are block-aligned: the response contains every matching primary object from every block in the page, and none from blocks outside it. A page never contains part of a block.\n\nThe `target` request field sets the target number of primary objects per page. It MUST be at least `0x1`, and related objects MUST NOT count toward it.\n\nThe server builds a page by scanning the resolved range one block at a time, starting at `fromBlock`, and ends the page at the first of the following conditions:\n\n1. **Range end.** The scan reaches `toBlock`. `cursorBlock` is `toBlock`, and no blocks remain.\n2. **Target reached.** The scan reaches a block that brings the page's primary object count to `target` or greater. `cursorBlock` MUST be set to this final block, and the response MUST include every matching primary object in it, even if the count then exceeds `target`. This condition does not apply if `target` is omitted.\n3. **Budget reached.** The server exhausts its *budget* — any server-imposed bound on the work or size of a single response, such as execution time, response size, or number of primary objects — before completing a block. The server MUST discard that block's partial results and end the page at the previous block.\n\nEvery successful response MUST include at least the `fromBlock` block, so each page advances by at least one block. If the server exhausts its budget before completing `fromBlock`, no block-aligned page exists, and the server MUST fail the request with `-32005`. Once the server has completed at least one block, it MUST return the page rather than fail with `-32005`.\n\nAs a result, a page contains more than `target` primary objects when its final block holds more matches than needed, and fewer — possibly zero — when the range ends or the budget is reached first. The number of primary objects in a response therefore does not indicate whether more data remains; only `cursorBlock` does.\n\nFor example, consider an `asc` request with `fromBlock` 10, `toBlock` 20, and `target` 100 (decimal for readability), where blocks 10 through 13 contain 40, 0, 55, and 30 matching primary objects:\n\n| Scenario | Ending condition | `cursorBlock` | Primary objects returned |\n| --- | --- | --- | --- |\n| Budget not exhausted | Target reached at block 13 (40 + 0 + 55 + 30 = 125) | 13 | 125 |\n| Budget exhausted while scanning block 13 | Budget reached | 12 | 95 |\n| Budget exhausted while scanning block 10 | — | — | Request fails with `-32005` |\n\n#### Chain consistency\n\nEvery object and block reference in a response MUST come from the same canonical chain, as seen by the server at query execution time. A response MUST NOT mix blocks from before and after a reorg.\n\nThis guarantee applies within a single response. Consecutive pages MAY reflect different chains if a reorg occurs between requests; see [Reorg detection](#reorg-detection).\n\n#### Filters\n\nEach method defines its own set of filter fields; see that method's Filter section. The rules below apply to all of them.\n\nAll conditions within a `filter` object are combined with AND semantics. Except where a method's Filter section states otherwise, each filter field accepts either a single value or an array of values; an array matches if the field equals any element of the array (OR within the field). An omitted filter field places no constraint on the result. If `filter` is omitted, every object of the method's primary type within the block range is returned.\n\n#### Fields and relations\n\nThe `fields` object selects what the response includes. Each key names an object schema, and each value is either an array of field names to include from that schema or `true` to include every field of that schema. The key naming the method's primary object type selects fields on the primary objects; every other key names a relation to join. If `fields` is omitted, all fields of the primary object are included and no relations are joined. See each method's Fields section for the keys it accepts.\n\nA relation is a reference from a primary object to a single object of another type. Only many-to-one relations are joinable. Each method MUST reject a `fields` key that names a relation it does not support.\n\nResults MUST be returned in normalized form. Related objects appear under `data` in their own array, keyed by the same name that selected them in `fields`. A related array MUST contain only objects referenced by a primary object in the same response, and a related object referenced by more than one primary object MUST appear only once.\n\nRelated objects use the schema defined in the response section of the corresponding method: `blocks` objects as defined under `eth_queryBlocks`, and `transactions` objects as defined under `eth_queryTransactions`.\n\n#### Ordering\n\nEach object type has an ordering key, defined in the Ordering section of the method that returns it as its primary type. The primary and related object arrays in `data` MUST be sorted by these ordering keys — ascending in `asc` mode and descending in `desc` mode. The sort applies across the entire array, not only at block granularity: in `desc` mode the objects within a single block are returned in reverse order as well.\n\n### `eth_queryBlocks`\n\nQuery for block headers.\n\n#### Filter\n\n| Field | Accepted Type | Description |\n| --- | --- | --- |\n| `miner` | `DATA` or `DATA[]` | Block miner / coinbase address. |\n\n#### Fields\n\n| Key | Type | Description |\n| --- | --- | --- |\n| `blocks` | `string[]` or `true` | Primary. Fields to include from the `blocks` schema. |\n\n`eth_queryBlocks` supports no relations.\n\n#### Response\n\n| Field | Type | Description | Availability |\n| --- | --- | --- | --- |\n| `number` | `QUANTITY` | Block number. | Required |\n| `hash` | `DATA` | Block hash. | Required |\n| `parentHash` | `DATA` | Parent block hash. | Required |\n| `timestamp` | `QUANTITY` | Block timestamp. | Required |\n| `nonce` | `DATA` | Block nonce. | Required |\n| `sha3Uncles` | `DATA` | Ommers hash. | Required |\n| `logsBloom` | `DATA` | Logs bloom filter. | Required |\n| `transactionsRoot` | `DATA` | Transactions root. | Required |\n| `stateRoot` | `DATA` | State root. | Required |\n| `receiptsRoot` | `DATA` | Receipts root. | Required |\n| `miner` | `DATA` | Coinbase address. | Required |\n| `difficulty` | `QUANTITY` | Block difficulty. | Fork-dependent |\n| `totalDifficulty` | `QUANTITY` | Total difficulty of the chain up to this block. | Fork-dependent |\n| `extraData` | `DATA` | Extra data. | Required |\n| `size` | `QUANTITY` | Block size. | Required |\n| `gasLimit` | `QUANTITY` | Block gas limit. | Required |\n| `gasUsed` | `QUANTITY` | Gas used by transactions in the block. | Required |\n| `baseFeePerGas` | `QUANTITY` | Base fee per gas. | Fork-dependent |\n| `blobGasUsed` | `QUANTITY` | Blob gas used. | Fork-dependent |\n| `excessBlobGas` | `QUANTITY` | Excess blob gas. | Fork-dependent |\n| `withdrawalsRoot` | `DATA` | Withdrawals root. | Fork-dependent |\n| `parentBeaconBlockRoot` | `DATA` | Parent beacon block root. | Fork-dependent |\n\n#### Ordering\n\nBlock objects are ordered by `number`.\n\n### `eth_queryTransactions`\n\nQuery for transactions included in blocks.\n\n#### Filter\n\n| Field | Accepted Type | Description |\n| --- | --- | --- |\n| `from` | `DATA` or `DATA[]` | Sender address. |\n| `to` | `DATA` or `DATA[]` | Recipient address. |\n| `selector` | `DATA` or `DATA[]` | 4-byte function selector (first 4 bytes of `input`). Transactions with `input` shorter than 4 bytes MUST NOT match a `selector` filter. |\n\n#### Fields\n\n| Key | Type | Description |\n| --- | --- | --- |\n| `transactions` | `string[]` or `true` | Primary. Fields to include from the `transactions` schema. |\n| `blocks` | `string[]` or `true` | Relation. Fields to include from the `blocks` schema. |\n\n#### Response\n\nThe `transactions` objects combine transaction fields with receipt fields. The receipt `logs` field is not included.\n\n| Field | Type | Description | Availability |\n| --- | --- | --- | --- |\n| `hash` | `DATA` | Transaction hash. | Required |\n| `blockHash` | `DATA` | Hash of the containing block. | Required |\n| `blockNumber` | `QUANTITY` | Number of the containing block. | Required |\n| `transactionIndex` | `QUANTITY` | Transaction index in the block. | Required |\n| `from` | `DATA` | Sender address. | Required |\n| `to` | `DATA` or `null` | Recipient address, or `null` for contract creation. | Required |\n| `nonce` | `QUANTITY` | Sender nonce. | Required |\n| `input` | `DATA` | Calldata. | Required |\n| `value` | `QUANTITY` | Transferred value. | Required |\n| `gas` | `QUANTITY` | Gas limit. | Required |\n| `gasPrice` | `QUANTITY` | Gas price for legacy and EIP-2930 transactions. | Type-dependent |\n| `type` | `QUANTITY` | Transaction type, such as `0x0` or `0x2`. | Required |\n| `chainId` | `QUANTITY` | Chain ID. | Type-dependent |\n| `accessList` | `object[]` | Access list for typed transactions. | Type-dependent |\n| `maxFeePerGas` | `QUANTITY` | EIP-1559 maximum fee per gas. | Type-dependent |\n| `maxPriorityFeePerGas` | `QUANTITY` | EIP-1559 maximum priority fee. | Type-dependent |\n| `maxFeePerBlobGas` | `QUANTITY` | Maximum blob fee per gas. | Fork-dependent |\n| `blobVersionedHashes` | `DATA[]` | Versioned blob hashes. | Fork-dependent |\n| `v` | `QUANTITY` | ECDSA signature recovery value. | Required |\n| `yParity` | `QUANTITY` | ECDSA signature parity for typed transactions. | Type-dependent |\n| `r` | `DATA` | ECDSA signature `r` value. | Required |\n| `s` | `DATA` | ECDSA signature `s` value. | Required |\n| `blockTimestamp` | `QUANTITY` | Timestamp of the containing block. | Optional |\n| `contractAddress` | `DATA` or `null` | Created contract address, or `null`. | Required |\n| `cumulativeGasUsed` | `QUANTITY` | Cumulative gas used in the block. | Required |\n| `gasUsed` | `QUANTITY` | Gas used by the transaction. | Required |\n| `effectiveGasPrice` | `QUANTITY` | Effective gas price paid. | Required |\n| `logsBloom` | `DATA` | Receipt logs bloom filter. | Required |\n| `status` | `QUANTITY` | `0x1` for success or `0x0` for reverted. | Required |\n| `root` | `DATA` | Post-state root. | Fork-dependent |\n| `blobGasUsed` | `QUANTITY` | Blob gas used. | Fork-dependent |\n| `blobGasPrice` | `QUANTITY` | Blob gas price. | Fork-dependent |\n\n#### Ordering\n\nTransaction objects are ordered by `(blockNumber, transactionIndex)`.\n\n### `eth_queryLogs`\n\nQuery for event logs emitted during transaction execution.\n\n#### Filter\n\n`topics` does not follow the general array rule; see below.\n\n| Field | Accepted Type | Description |\n| --- | --- | --- |\n| `address` | `DATA` or `DATA[]` | Contract address that emitted the log. |\n| `topics` | see below | Positional topic filter. |\n\nThe `topics` filter MUST follow the same matching semantics as `eth_getLogs`. The value is an array of up to 4 positional entries. Each entry may be:\n\n- A single topic hash (`DATA`): matches logs where `topics[i]` equals that hash.\n- An array of topic hashes (`DATA[]`): matches logs where `topics[i]` equals any hash in the array.\n- `null`: wildcard — matches any value at position `i`.\n\nTrailing `null` entries MAY be omitted.\n\n#### Fields\n\n| Key | Type | Description |\n| --- | --- | --- |\n| `logs` | `string[]` or `true` | Primary. Fields to include from the `logs` schema. |\n| `transactions` | `string[]` or `true` | Relation. Fields to include from the `transactions` schema. |\n| `blocks` | `string[]` or `true` | Relation. Fields to include from the `blocks` schema. |\n\n#### Response\n\n| Field | Type | Description | Availability |\n| --- | --- | --- | --- |\n| `address` | `DATA` | Address that emitted the log. | Required |\n| `blockHash` | `DATA` | Hash of the containing block. | Required |\n| `blockNumber` | `QUANTITY` | Number of the containing block. | Required |\n| `blockTimestamp` | `QUANTITY` | Timestamp of the containing block. | Optional |\n| `transactionHash` | `DATA` | Hash of the containing transaction. | Required |\n| `transactionIndex` | `QUANTITY` | Transaction index in the block. | Required |\n| `logIndex` | `QUANTITY` | Log index in the block, matching `eth_getLogs`. | Required |\n| `topics` | `DATA[]` | Indexed event topics. | Required |\n| `data` | `DATA` | Non-indexed event data. | Required |\n| `removed` | `boolean` | Whether the log was removed by a reorg. Always `false` in these responses, since results are read from the canonical chain; retained for compatibility with `eth_getLogs` consumers. | Required |\n\n#### Ordering\n\nLog objects are ordered by `(blockNumber, logIndex)`.\n\n### `eth_queryTraces`\n\nQuery for internal call traces.\n\n#### Filter\n\n`isTopLevel` accepts only a single boolean; the other fields follow the general array rule.\n\n| Field | Accepted Type | Description |\n| --- | --- | --- |\n| `from` | `DATA` or `DATA[]` | Sender address. |\n| `to` | `DATA` or `DATA[]` | Recipient address. |\n| `selector` | `DATA` or `DATA[]` | 4-byte function selector (first 4 bytes of `input`). Traces with `input` shorter than 4 bytes MUST NOT match a `selector` filter. |\n| `isTopLevel` | `boolean` | If `true`, only top-level traces (those with an empty `traceAddress`) are returned; if `false`, only traces made by an internal call (those with a non-empty `traceAddress`). If omitted, both are returned. |\n\n#### Fields\n\n| Key | Type | Description |\n| --- | --- | --- |\n| `traces` | `string[]` or `true` | Primary. Fields to include from the `traces` schema. |\n| `transactions` | `string[]` or `true` | Relation. Fields to include from the `transactions` schema. |\n| `blocks` | `string[]` or `true` | Relation. Fields to include from the `blocks` schema. |\n\n#### Response\n\nTrace objects are flattened `callTracer` frames, omitting nested calls and trace logs.\n\n| Field | Type | Description | Availability |\n| --- | --- | --- | --- |\n| `type` | `string` | Call type: `CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`, `CREATE`, `CREATE2`, or `SELFDESTRUCT`. | Required |\n| `from` | `DATA` | Address initiating the call. | Required |\n| `to` | `DATA` | Target address receiving the call. | Optional |\n| `value` | `QUANTITY` | Amount of native token transferred. | Optional |\n| `gas` | `QUANTITY` | Gas provided for the call. | Required |\n| `gasUsed` | `QUANTITY` | Gas used during the call. | Required |\n| `input` | `DATA` | Call data. | Required |\n| `output` | `DATA` | Return data. | Optional |\n| `error` | `string` | Call failure information. | Optional |\n| `revertReason` | `string` | Solidity revert reason. | Optional |\n| `blockHash` | `DATA` | Hash of the containing block. | Required |\n| `blockNumber` | `QUANTITY` | Number of the containing block. | Required |\n| `transactionHash` | `DATA` | Hash of the containing transaction. | Required |\n| `transactionIndex` | `QUANTITY` | Transaction index in the block. | Required |\n| `traceAddress` | `number[]` | Path through the nested call tree. | Required |\n| `status` | `QUANTITY` | `0x1` for success or `0x0` for reverted. | Required |\n\n#### Ordering\n\n Trace objects are ordered by `(blockNumber, transactionIndex, traceAddress)`, where `traceAddress` is compared lexicographically (element-wise), with shorter arrays sorting before longer arrays when one is a prefix of the other.\n\n### `eth_queryTransfers`\n\nQuery for native token transfers. A transfer is any call frame whose `value` is greater than zero. The set of transfers in a block range is therefore exactly the subset of the traces in that range for which `value > 0`, and each transfer object carries the same trace context as the corresponding `eth_queryTraces` object.\n\n#### Filter\n\n`isTopLevel` accepts only a single boolean; the other fields follow the general array rule.\n\n| Field | Accepted Type | Description |\n| --- | --- | --- |\n| `from` | `DATA` or `DATA[]` | Address initiating the transfer. |\n| `to` | `DATA` or `DATA[]` | Address receiving the transfer. |\n| `isTopLevel` | `boolean` | If `true`, only top-level transfers (those with an empty `traceAddress`) are returned; if `false`, only transfers made by an internal call (those with a non-empty `traceAddress`). If omitted, both are returned. |\n\n#### Fields\n\n| Key | Type | Description |\n| --- | --- | --- |\n| `transfers` | `string[]` or `true` | Primary. Fields to include from the `transfers` schema. |\n| `transactions` | `string[]` or `true` | Relation. Fields to include from the `transactions` schema. |\n| `blocks` | `string[]` or `true` | Relation. Fields to include from the `blocks` schema. |\n\n#### Response\n\nTransfer objects have the same fields as the `eth_queryTraces` response, except that `to` and `value` are Required rather than Optional.\n\n| Field | Type | Description | Availability |\n| --- | --- | --- | --- |\n| `type` | `string` | Call type that produced the transfer. | Required |\n| `from` | `DATA` | Address initiating the transfer. | Required |\n| `to` | `DATA` | Target address receiving the transfer. | Required |\n| `value` | `QUANTITY` | Amount of native token transferred. | Required |\n| `gas` | `QUANTITY` | Gas provided for the call. | Required |\n| `gasUsed` | `QUANTITY` | Gas used during the call. | Required |\n| `input` | `DATA` | Call data. | Required |\n| `output` | `DATA` | Return data. | Optional |\n| `error` | `string` | Call failure information. | Optional |\n| `revertReason` | `string` | Solidity revert reason. | Optional |\n| `blockHash` | `DATA` | Hash of the containing block. | Required |\n| `blockNumber` | `QUANTITY` | Number of the containing block. | Required |\n| `transactionHash` | `DATA` | Hash of the containing transaction. | Required |\n| `transactionIndex` | `QUANTITY` | Transaction index in the block. | Required |\n| `traceAddress` | `number[]` | Path through the nested call tree. | Required |\n| `status` | `QUANTITY` | `0x1` for success or `0x0` for reverted. | Required |\n\n#### Ordering\n\nTransfer objects are ordered by `(blockNumber, transactionIndex, traceAddress)`, where `traceAddress` is compared element-wise.\n\n### Errors\n\nThe methods use standard JSON-RPC error codes plus application-specific codes that follow the conventions established by EIP-1474. The codes whose boundaries matter for these methods are:\n\n| Code | Message | Description |\n| --- | --- | --- |\n| `-32601` | Method not found | The node does not recognize the method, because it runs software that predates this MIP. This is the standard JSON-RPC code and is listed here only to distinguish it from `-32004`. |\n| `-32004` | Method not supported | The node recognizes the method but is not configured to serve it, and so cannot serve it for any block range. A node that does not index traces, for example, returns this code for `eth_queryTraces` and `eth_queryTransfers` while still serving the other three methods. |\n| `-32602` | Invalid params | Malformed request: unknown `fields` keys, invalid filter fields, a `fields` key naming an unrecognized or unsupported relation for this method, a `target` of `0x0`, or a block range that is inverted for the requested `order`. |\n| `-32001` | Resource not found | The node serves this method, but the resolved block range falls partly or wholly outside its availability window for the method. See [Block range](#block-range). |\n| `-32005` | Limit exceeded | The request exceeded a server-imposed resource limit. For the response budget, this occurs only when `fromBlock` alone exceeds the budget; see [Block-aligned pagination](#block-aligned-pagination). |\n\nExample `-32005` error response:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"error\": {\n    \"code\": -32005,\n    \"message\": \"Limit exceeded\",\n    \"data\": \"Execution time greater than 5 seconds\"\n  }\n}\n```","rationale":"**JSON-RPC interface.** These methods extend the existing JSON-RPC interface rather than introduce a new transport or query language. Node operators and client libraries can adopt them without new infrastructure, and existing tooling such as authentication, load balancing, and retries works unchanged.\n\n**`eth_` namespace.** The methods use the `eth_` namespace because nothing in the interface is specific to Monad. They query objects common to every EVM chain — blocks, transactions, logs, traces, and transfers — and reuse the value types, block tags, and error code conventions of existing `eth_` methods.\n\n**Block range and traversal direction.** Chain history is ordered by block, so a contiguous block range is the natural unit for scanning and resuming a query. Supporting both `asc` and `desc` lets the same method serve forward scans, such as backfilling an index from genesis, and backward scans, such as fetching the most recent N events from `\"latest\"`, without the client guessing a block window and widening it until enough results arrive.\n\n**Block-aligned pagination.** The block is the atomic unit of chain history: a reorg replaces whole blocks, and indexers typically commit and roll back data one block at a time. The downside is that page size cannot be a hard limit. A page must include all of its final block, so the final block can push a page past `target`. For the same reason, if a single block has more matches than the server's budget allows, the server cannot return that block at all.\n\n**Block references instead of an opaque cursor.** Because pages end on block boundaries, a block reference is a complete cursor. The response exposes it directly as `fromBlock`, `toBlock`, and `cursorBlock`, rather than wrapping it in an opaque token that would require either server-side state or an encoding shared by every implementation. With block numbers and hashes in every response, clients can resume pagination, check for completion, and detect reorgs without extra requests.\n\n**Many-to-one relations only.** One-to-many relations, such as all transactions in a block, would make response sizes unpredictable, since a single block can contain thousands of transactions. Restricting relations to many-to-one, such as each log's parent block, guarantees that a response never contains more related objects of a given type than primary objects. For the same reason, related objects do not count toward `target`: their number is already bounded by the primary count, and counting them would make page boundaries depend on which relations the client requested.\n\n**Normalized responses.** Related objects are returned in their own arrays, and each appears once even when several primary objects reference it. Embedding them instead would repeat the same block once for every log it contains. Separate arrays also match how clients typically store and index the data.","backwardsCompatibility":"There are no backwards compatibility issues. These are five new JSON-RPC methods; no existing method's request or response shape is changed. Clients that do not support these methods are unaffected, and a node that does not recognize them responds with the standard JSON-RPC \"method not found\" error (`-32601`). A node that recognizes the methods but is not configured to serve all of them responds with `-32004` for the methods it does not serve; see [Errors](#errors).","securityConsiderations":"These methods increase server-side workload by enabling high-volume historical queries. Implementations are expected to enforce request limits, such as a maximum response size or execution time, and to apply fair-use controls such as rate limiting and per-API-key quotas, to mitigate denial-of-service and cost-amplification risks.\n\nResults near the chain tip are not final. A reorganization can invalidate pages a client has already consumed, so a client that persists query results needs a recovery strategy built on the block references in the response; see [Reorg detection](#reorg-detection).\n\nBecause the new RPC methods support joins and field projection, the worst-case work for a single request depends on the requested relations and fields as well as on the block range. Implementations should validate relations and fields strictly and bound that worst case.","subsystems":["rpc","consensus","evm","staking","db","economics","execution"],"createdAt":"2026-09-23T14:19:20-04:00","updatedAt":"2026-09-23T14:19:20-04:00"},{"number":"0015","numberShort":"15","title":"Glamsterdam EIP Activation","description":"Activate selected EIPs from Ethereum's Glamsterdam upgrade.","status":"Review","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-15-glamsterdam-eip-activation/540","forumTopicId":540,"created":"2026-08-18","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-15.md","contentHash":"0d814ef115417c0b0335aa2c8813c7b4586840b58636ae2d570264c216293a78","abstract":"Activate six EIPs from Ethereum's Glamsterdam upgrade ([EIP-7773]):\n[EIP-7708], [EIP-7843], [EIP-7981], [EIP-7997], [EIP-8024] and [EIP-8246].","specification":"This MIP activates the following EIPs:\n\n- [EIP-7708] — ETH transfers emit a log\n- [EIP-7843] — SLOTNUM opcode\n- [EIP-7981] — Increase Access List Cost (modified, see below)\n- [EIP-7997] — Deterministic Factory Contract\n- [EIP-8024] — Backward compatible SWAPN, DUPN, EXCHANGE\n- [EIP-8246] — Remove SELFDESTRUCT Burn\n\n[EIP-7981] is adopted with `access_list_data_cost = 40 * access_list_bytes`\n(800 gas per address and 1280 gas per storage key) rather than 64 gas per byte.\n\nAll other EIPs included in Glamsterdam ([EIP-7773]) are not activated; see\n[Rationale](#rationale).\n\n### Chain Specifics\n\n[EIP-7997] has no effect on Monad Mainnet or Testnet: the deterministic factory\ncontract has already been deployed on those networks via the usual keyless\ntransaction method. Adopting the EIP therefore only affects local development\nnetworks.","rationale":"Block-level access lists are incompatible with Monad's asynchronous execution\nmodel. Proposers do not execute blocks, and so cannot reliably populate the\naccess lists. [EIP-7928] is therefore not adopted by Monad:\n\n- [EIP-7928] — Block-Level Access Lists\n\nThe following EIPs deal with the Ethereum consensus layer and are therefore not adopted by Monad:\n\n- [EIP-8045] — Exclude slashed validators from proposing\n- [EIP-8061] — Increase exit and consolidation churn\n- [EIP-8282] — Builder Execution Requests\n- [EIP-7688] — Forward compatible consensus data structures\n- [EIP-7732] — Enshrined Proposer-Builder Separation\n\nThe following EIPs establish a new two-dimensional gas model for Ethereum. Monad\nmay make different choices regarding gas metering in the future, and so at this\ntime the protocol will not adopt the Ethereum changes in these EIPs:\n\n- [EIP-2780] — Resource-based intrinsic transaction gas\n- [EIP-7778] — Block Gas Accounting without Refunds\n- [EIP-7976] — Increase Calldata Floor Cost\n- [EIP-8037] — State Creation Gas Cost Increase\n- [EIP-8038] — State-access gas cost update\n\nSince [EIP-7976] is not adopted, the access list data cost in [EIP-7981] is set\nto match Monad's existing calldata floor cost of 40 gas per byte, rather than the\nincreased Ethereum floor of 64.\n\nOn Monad, the maximum contract size is already 128KB. The increase in [EIP-7954]\nis therefore not adopted:\n\n- [EIP-7954] — Increase Maximum Contract Size\n\nThe networking EIPs in [EIP-7773] concern Ethereum's devp2p protocols, which\nMonad does not use, and its informational EIPs specify no protocol changes;\nneither group is adopted.","backwardsCompatibility":"No backwards compatibility issues beyond those in the original EIP specifications.","securityConsiderations":"No security considerations beyond those in the original EIP specifications.","subsystems":["execution","consensus","evm","staking","db"],"createdAt":"2026-09-08T09:53:12+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0014","numberShort":"14","title":"Account Attestation Registry","description":"An on-chain registry where an account publishes self-attestations about itself, authorized by proof-of-control","status":"Draft","type":"Standards Track","category":"MRC","authors":[{"name":"Mohsen Ahmadvand","github":"mr-ma"}],"discussionsTo":"https://forum.monad.xyz/t/account-attestation-registry-mrc/513","forumTopicId":513,"created":"2026-07-21","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MRCs/MRC-14.md","contentHash":"a8d63b3fe64a34384484de6a568a68eb69f50cc7ae0c0a7921cc7b90124cdddb","abstract":"This MRC defines an on-chain registry where an account can publish facts about itself that the chain does not otherwise show, for example the key-management scheme behind the address (MPC, TSS, a TEE-held key, or an off-chain multisig) or who operates it. Only the account can write its own entries, and it proves that by writing from the address it controls.\n\nEach entry is stored under a topic the account chooses, kept exactly as written (a list of key/value pairs) and never interpreted by the registry, so the registry fixes no list of topics and judges no claim. Any service that needs to know something about an address it cannot read from the chain (a risk dashboard, a wallet or custodian directory, a validator explorer, a compliance tool) can read these entries directly, without a separate identity system.","motivation":"Many onchain accounts present as a plain externally-owned account: one address, no code, controlled by one key. On-chain, that is all they ever appear to be. In practice the single address is often the front for a key-management scheme that produces one ordinary signature yet is invisible on-chain:\n\n- an MPC wallet, where the key is split across parties and never reconstructed;\n- a TSS (threshold-signature) wallet, where an m-of-n quorum jointly produces one signature;\n- a key held inside a TEE, an attested secure enclave;\n- an off-chain multisig, where an m-of-n approval is coordinated off-chain and settled as a single signature.\n\nEach produces a standard ECDSA signature that encodes none of this structure, so the address is byte-for-byte indistinguishable from a single-key EOA. None of the backing is observable from outside: there is no code to inspect, and the quorum, enclave, or approval policy leaves no on-chain trace. The strongest fact provable on-chain is control of the address itself, demonstrated trivially by signing or transacting from it.\n\nThis registry keys a self-attestation to the address itself: because its writer is always the subject, the write needs only a local `msg.sender` check with no external identity lookup, which keeps the contract minimal and reusable by any service.","specification":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.html) and [RFC 8174](https://www.ietf.org/rfc/rfc8174.html).\n\n### Overview\n\nCompliant implementations MUST deploy a single contract that conforms to the `IAccountRegistry` interface below and expose `string public constant VERSION = \"MRC-14/1.0.0\"`. A subject's own entry is identified by a `metadataId` derived from its subject; the contract MUST authorize each `setMetadata` write against the subject account (see [Authorization](#authorization)). The contract exposes write and read methods for the stored metadata.\n\n### Metadata identity\n\nMetadata entries are keyed by the account and the fact the entry is about:\n\n```\nmetadataId = keccak256(abi.encode(account, topic, index))\n```\n\n- `account`: the subject address, i.e. the account the metadata makes claims about; only the account can write its own entry.\n- `topic`: a service-defined label for what the entry covers (e.g. `\"custody\"`, `\"operator\"`, `\"security-contact\"`). The registry treats it as an opaque string.\n- `index`: distinguishes multiple entries under the same `(account, topic)`, e.g. successive dated attestations. It MUST be `0` for single-valued topics.\n\n### Interface\n\n```solidity\ninterface IAccountRegistry {\n    /// The subject an entry is keyed to: `(account, topic, index)`. See\n    /// Metadata identity.\n    struct Subject {\n        address account;\n        string  topic;\n        uint256 index;\n    }\n\n    /// One key/value pair of an entry's `data`, stored verbatim. See Field Semantics.\n    struct Field {\n        string key;\n        string value;\n    }\n\n    /// Emitted on every write. `topicKey` is `keccak256(bytes(topic))`,\n    /// indexed for `(account, topic)` filtering. See Events.\n    event MetadataUpdated(bytes32 indexed metadataId, address indexed account, bytes32 indexed topicKey, string topic, uint256 index);\n    /// Emitted on every reference write, attributed to `attester` (`msg.sender`). See References.\n    event ReferenceUpdated(bytes32 indexed refId, address indexed account, address indexed attester, string topic, uint256 index);\n\n    /// MUST return \"MRC-14/1.0.0\".\n    function VERSION() external view returns (string memory);\n    /// Pure derivation of the metadata id for a subject.\n    function metadataId(Subject calldata subject) external pure returns (bytes32);\n    /// Pure derivation of the reference id for a subject and attester.\n    function referenceId(Subject calldata subject, address attester) external pure returns (bytes32);\n\n    /// Set or replace a subject's own entry. `data` is stored verbatim. See\n    /// Authorization, Write Preconditions, and Field Semantics.\n    function setMetadata(Subject calldata subject, Field[] calldata data) external;\n    /// File or replace a third-party reference about a subject, attributed to the\n    /// caller (`msg.sender != subject.account`). See References.\n    function setReference(Subject calldata subject, Field[] calldata data) external;\n\n    /// Read the subject's own entry, or an empty array if none exists.\n    function getMetadata(Subject calldata subject) external view returns (Field[] memory data);\n    /// Read the reference `attester` filed about `subject`, or an empty array if none.\n    function getReference(Subject calldata subject, address attester) external view returns (Field[] memory data);\n    /// True iff a subject's own entry has been written.\n    function hasMetadata(Subject calldata subject) external view returns (bool);\n}\n```\n\n### Authorization\n\nAuthority over a subject's own entry is the subject account itself. A `setMetadata` write MUST be gated by `msg.sender == subject.account`. This is the proof-of-control the trust model rests on: only the party that controls the address may state facts about itself. References are an optional exception, attributed to their writer rather than gated to the subject (see [References](#references)).\n\nThe check is a single equality against `msg.sender`, which holds whether the account is an EOA that signs the transaction itself or a contract account that executes the write from its own context. For an MPC, TSS, or off-chain-multisig account the quorum's own signing ceremony produces that call, so the multi-party structure is accommodated with no delegation primitive. Because a self-attestation's writer is always the subject account, no ownership or admin model and no external contract call is needed to establish authority for it. The revert reason for unauthorized callers is implementation-defined but SHOULD be a custom error (e.g. `Unauthorized()`).\n\n### Field Semantics\n\n- `data` is a list of `(key, value)` pairs; by convention each `key` is a topic field name. A service defines its own field set and MAY add auxiliary pairs (evidence or document URIs, content hashes, signed attestations, or payloads defined by a later MRC); this MRC defines no field vocabulary and reserves no keys. The same shape applies to references.\n- The registry MUST NOT interpret the keys or values and MUST return them as written on read. The pairs are ABI-encoded, so a consumer decodes them structurally with no free-form parsing; the field set is a convention enforced by writers and consumers, not by the registry.\n\n### Write Preconditions\n\n`setMetadata` MUST revert when `data` or `topic` is empty. A stored entry is therefore always non-empty, so `hasMetadata(subject)` is `true` exactly for a subject that `setMetadata` has written. To correct or retract an attestation the account overwrites `data` with a new value; this MRC defines no separate deletion function.\n\n### Events\n\nExactly one `MetadataUpdated` MUST be emitted on every successful `setMetadata`, signalling that the metadata entry for `metadataId` changed. The event carries the entry's `topic` and `index` but not its `data`; a consumer reads the contents via `getMetadata`, which MUST return the entry as of that write. Because `metadataId` is a one-way hash of `(account, topic, index)`, emitting `topic` and `index` lets a consumer reconstruct the full subject of the changed entry directly from the log, with no need to brute-force `metadataId` over candidate indices.\n\nThe event indexes `metadataId`, `account`, and `topicKey` (= `keccak256(bytes(topic))`), the three indexed topics the EVM permits besides the event signature. Indexing `account` lets a consumer stream every change for an address; indexing `topicKey` lets it filter by `(account, topic)` without scanning unrelated entries.\n\n### Read Semantics\n\n- `getMetadata`, `getReference`, and `hasMetadata` MUST NOT call any external contract.\n- `getMetadata(subject)` MUST return the subject's own stored `data`, or an empty array if no entry exists.\n- `hasMetadata(subject)` MUST return `true` iff the subject's own entry has been written.\n- `VERSION()` MUST return `\"MRC-14/1.0.0\"`.\n- Implementations MAY expose additional view functions but MUST NOT alter the semantics of any function defined here.\n\n### References\n\nReferences are an optional path for the case where a subject's key cannot itself make a normal call to self-attest, for example a deploy key restricted to `CREATE`. Any account other than the subject MAY file a reference about a subject with `setReference`, keyed by `(account, attester, topic, index)` with `attester = msg.sender`:\n\n```\nrefId = keccak256(abi.encode(account, attester, topic, index))\n```\n\nA reference is stored separately from the subject's own entry and can never overwrite it, is read with `getReference(subject, attester)`, and emits exactly one `ReferenceUpdated`. `setReference` MUST revert when `data` or `topic` is empty, and when `msg.sender == subject.account` (the subject uses `setMetadata` for its own entry).\n\nA reference carries no proof-of-control: it is attributed to its writer, and its trust rests entirely on that attester, which a consumer weighs or ignores on the attester's own merits.\n\n### Example topics\n\nThe registry defines no topics; the following are illustrative conventions a consuming service might adopt. They are non-normative. A service publishes its own field schema for each topic, and the registry stores whatever is written.\n\n| topic (example) | `data` keys (example) |\n|---|---|\n| `custody` | scheme (mpc / tss / tee / offchain-multisig), threshold(m,n), provider, attestationUri |\n| `operator` | operator, identityDisclosed, affiliation, proofUri |\n| `security-contact` | contactUri, disclosurePolicy |\n\n### Chain Specifics\n\nA registry contract conforming to this MRC is deployed at an ordinary contract address chosen at deployment time. It depends on no precompile: a self-attestation's authority is the subject address itself, checked by an equality against `msg.sender`, with no external call. A conformant registry functions on any Monad-EVM network. This MRC defines an interface and a behavioural spec, not a specific bytecode or address. Independently-deployed conformant implementations may coexist, and integrators choose which to write to and read from.","rationale":"**Why key metadata by the account address?** The address is the one identity an account can prove control of on-chain, so it is the only anchor that needs no second identity system. A reader resolves every attestation against an address it already tracks.\n\n**Why `(account, topic, index)`?** `topic` partitions the distinct facts an account may state about itself; `index` lets the account keep multiple entries under the same `(account, topic)` (for example a sequence of dated attestations), each with its own `metadataId`, so assigning a fresh index does not overwrite earlier ones. The registry does not enforce append-only or index monotonicity; keeping past entries immutable is a convention, not an on-chain guarantee. Single-valued topics simply use `index = 0`.\n\n**Why a key/value array instead of typed fields or a JSON string?** The set of facts differs by service and evolves over time, so fixed typed columns would force a schema and an ABI break whenever fields change. A raw JSON string stays flexible but pushes malformed-data handling onto every consumer. An array of `(key, value)` pairs keeps both properties: it is schema-free, and it is ABI-encoded, so consumers decode it structurally with no free-form parsing.\n\n**Why proof-of-control as the authority?** An MPC, TSS, TEE, or off-chain-multisig backing produces a standard ECDSA signature that encodes none of its structure, so the signing address is the strongest authority observable on-chain. Re-deriving identity any other way would introduce a divergent identity system that a reader would have to trust separately.\n\n**Why is a stored value never authoritative?** A self-attestation's `data` is a self-reported statement, not a proof, and a reference is a third-party claim. A consumer must be free to grade either against supporting evidence and independent observation; the registry storing it does not make it true.\n\n**Prior art / alternatives considered.** Several existing designs cover adjacent ground, and this MRC deliberately diverges from each:\n\n- **ERC-780 (Ethereum Claims Registry)** keys every claim by `(issuer, subject, key)`. It does support self-attestation (`setSelfClaim`), but even a self-claim stores the issuer in the key (`registry[issuer][subject][key]`), so the issuer dimension is always present. This MRC has no issuer dimension: an entry is keyed by the subject alone, and authority is proof-of-control of that address.\n- **EAS (Ethereum Attestation Service)** is a general attestation layer with an on-chain schema registry, arbitrary attester→recipient attestations, resolver hooks, and revocation. This MRC is intentionally narrower: no schema registry (the `data` field is an opaque, unparsed convention) and no resolver extension points; self-attestation is the default, with references only as an optional attributed path rather than a general attester graph. The \"why not just use EAS\" answer is that a consumer here depends on the chain and the account's own statements, with no schema registry or resolver to depend on, which keeps the contract small enough to be reused by any service.\n- **ENS text records** store key→value strings under a *name*, resolving identity through the ENS namespace and its ownership model. This MRC keys metadata directly on the account address, the one identity provable on-chain without a name-resolution system, so a reader resolves attestations against an address it already tracks.\n\nThe common thread is that a self-attestation here is proof-of-control of the subject address (not an issuer or name owner), and the registry carries no schema registry and no external identity resolution, which keeps it minimal and reusable.","backwardsCompatibility":"This MRC is purely additive: it specifies a new application-layer contract and changes no precompile, EVM, or consensus behaviour. Tools that consume off-chain metadata may continue to do so, both for accounts that have not yet filed and to corroborate the self-attestations of those that have.","securityConsiderations":"A self-attestation is authorized only by control of the subject address, not a proof of the fact it asserts; a reference is weaker still, a third-party claim whose trust rests on its attester (see References). A consumer must treat each value as a claim, weigh it against off-chain evidence, and must not render it as verified fact or let it override anything observable on-chain. Whoever controls the account's key can change its metadata unilaterally, so the integrity of an entry rests on that account's own key-security assumptions.","subsystems":["rpc","consensus","evm","staking","db","economics","execution"],"createdAt":"2026-08-25T12:53:33+02:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0013","numberShort":"13","title":"Validator Metadata Registry","description":"An on-chain registry for human-readable Monad validator metadata","status":"Final","type":"Standards Track","category":"MRC","authors":[{"name":"Dorde Mijovic (@mijovic)"},{"name":"Jackson Lewis"}],"discussionsTo":"https://forum.monad.xyz/t/validator-metadata-registry/497","forumTopicId":497,"created":"2026-06-15","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MRCs/MRC-13.md","contentHash":"e7f77dcecfbb9a16504ec01a7936c7f1371ba1279917fad67fb472c9830e2abc","abstract":"This MRC specifies an on-chain registry contract that augments the Monad staking precompile (at `0x0000000000000000000000000000000000001000`) with human-readable validator metadata: name, website, description, logo URL, a JSON `socials` field, and a JSON `additionalInfo` field for forward-compatible extensions. At minimum, a validator's own authority address — as reported by the staking precompile — must be able to write metadata for that validator; implementations are free to grant write access to additional callers under their own authorization model. The registry exposes both full-record writes and per-field updates, plus read methods for the stored record.","motivation":"The Monad staking precompile is the source of truth for validator identity at the consensus layer, but it intentionally exposes only the data required for consensus and reward accounting (authority address, flags, stake, commission, public keys, etc.). It does not provide any human-readable identity for a validator.\n\nWallets, block explorers, staking dashboards, governance UIs, and delegation tools all need to render validators by a recognizable name and logo rather than by a numeric `validatorId`. Today each integrator solves this independently — typically by maintaining an off-chain `metadata.json` file or a centrally-curated list — which produces inconsistent naming across the ecosystem, broken or stale links, and a centralization risk where one curator gates how validators are labeled to users.\n\nA permissionless, validator-controlled on-chain registry eliminates the curation bottleneck: the same authority key that already controls staking parameters is the one allowed to publish a validator's metadata, and any integrator can read it directly from a registry contract conforming to this standard.","specification":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.html) and [RFC 8174](https://www.ietf.org/rfc/rfc8174.html).\n\n### Overview\n\nCompliant implementations MUST deploy a single contract that conforms to the `IValidatorMetadata` interface defined below. The contract MUST query the Monad staking precompile at `0x0000000000000000000000000000000000001000` to determine the current authority address of any given `validatorId`, and MUST accept write calls from that authority address at the time of the call. Implementations MAY additionally accept writes from other callers under their own authorization rules; see [Authorization](#authorization).\n\n### Interface\n\n```solidity\ninterface IValidatorMetadata {\n    /// Emitted on every successful write to a validator's metadata.\n    event MetadataUpdated(uint64 indexed validatorId, address indexed authority, Metadata metadata);\n\n    /// Validator metadata record. See Field Semantics for write rules and the JSON\n    /// conventions for the `socials` and `additionalInfo` fields.\n    struct Metadata {\n        string name;\n        string website;\n        string description;\n        string logo;\n        string socials;\n        string additionalInfo;\n    }\n\n    /// Field selector for `updateMetadataField`.\n    enum Field {\n        NAME,\n        WEBSITE,\n        DESCRIPTION,\n        LOGO,\n        SOCIALS,\n        ADDITIONAL_INFO\n    }\n\n    /// Address of the Monad staking precompile used for authority resolution.\n    /// Implementations MUST return `0x0000000000000000000000000000000000001000`.\n    function STAKING_PRECOMPILE() external view returns (address);\n\n    /// Set or replace the full metadata record for a validator. Authorized callers\n    /// and revert conditions are defined in Authorization and Field Semantics.\n    function setMetadata(uint64 validatorId, Metadata calldata metadata) external;\n\n    /// Update a single field, leaving other fields untouched. Reverts if no record\n    /// exists yet for `validatorId` — `setMetadata` is the only entry point that may\n    /// create a record. For `field == SOCIALS` or `ADDITIONAL_INFO`, callers SHOULD\n    /// pass a UTF-8 JSON object; the registry stores `value` verbatim.\n    function updateMetadataField(uint64 validatorId, Field field, string calldata value) external;\n\n    /// Read the full stored metadata record for `validatorId`, or an all-default\n    /// `Metadata` struct if no record exists. Use `hasMetadata` to disambiguate.\n    function getMetadata(uint64 validatorId) external view returns (Metadata memory metadata);\n\n    /// True iff a metadata record has been written for `validatorId`. Equivalent to\n    /// the stored `name` being non-empty, since `name` is required on every write.\n    function hasMetadata(uint64 validatorId) external view returns (bool);\n\n    /// Read just the validator's stored name, or the empty string if no metadata is set.\n    function getValidatorName(uint64 validatorId) external view returns (string memory);\n}\n```\n\n### Authorization\n\nFor every write entry point (`setMetadata`, `updateMetadataField`), implementations MUST resolve the validator's authority by calling `getValidator(validatorId).authority` on the staking precompile at `STAKING_PRECOMPILE()` at the time of the call, and MUST accept the call when `msg.sender` equals that address. Implementations MUST NOT cache or shadow the authority address: a change of authority in the staking precompile MUST take effect immediately.\n\nBeyond this baseline, implementations are free to define their own authorization model and MAY accept writes from additional callers — for example, a delegated operator key, a multisig wrapper, a governance contract, or a designated metadata manager — provided that any caller not granted access under the chosen scheme is rejected. The revert reason for unauthorized callers is implementation-defined but SHOULD be a custom error (e.g. `Unauthorized()`) rather than a string.\n\n### Field Semantics\n\n- `name` is REQUIRED and MUST be non-empty for any validator that has any metadata at all. `setMetadata` MUST revert when given an empty `name`. `updateMetadataField` MUST revert when called with `Field.NAME` and an empty value. As above, the revert reason is implementation-defined but SHOULD be a custom error (e.g. `ValidatorNameEmpty()`).\n- `website`, `description`, `logo`, `socials`, and `additionalInfo` are OPTIONAL strings. The registry MUST NOT validate their content; integrators SHOULD treat them as untrusted input and sanitize before rendering.\n- `socials`, when non-empty, SHOULD be a UTF-8 JSON object whose keys are lowercase platform identifiers and whose values are the corresponding profile URLs or handles, for example:\n\n  ```json\n  {\n    \"x\": \"https://x.com/monad_xyz\",\n    \"telegram\": \"https://t.me/monad\",\n    \"discord\": \"https://discord.gg/monad\",\n    \"github\": \"https://github.com/monad-developers\"\n  }\n  ```\n\n  The set of platform keys is not enumerated by this MRC — integrators SHOULD treat unknown keys as opaque and pass them through. Encoding socials as a JSON object (rather than naming individual social platforms in the struct) keeps the registry usable as the ecosystem's social-media landscape shifts over time.\n- `additionalInfo`, when non-empty, SHOULD be a UTF-8 JSON object. It is reserved for forward-compatible metadata extensions (e.g. delegation policies, signed attestations, content hashes, future MRC payloads) and has no top-level schema defined by this MRC; downstream MRCs MAY define reserved key namespaces inside it.\n- Both `socials` and `additionalInfo` are stored as raw strings. The registry MUST NOT attempt to parse them, MUST NOT reject syntactically invalid JSON, and MUST return them byte-for-byte on read. JSON conformance is therefore a convention enforced by writers and consumers, not by the registry.\n\n### Write Preconditions\n\n`setMetadata` is the only entry point that may create a new record. `updateMetadataField` MUST revert when invoked for a `validatorId` that has no existing record (i.e. one for which `hasMetadata(validatorId)` is `false`). This invariant is what makes the \"`name` is REQUIRED\" rule enforceable: a record can never exist with an empty `name`, because the only way to create one — `setMetadata` — rejects empty names, and field updates cannot run before a record exists.\n\n### Events\n\nExactly one `MetadataUpdated` event MUST be emitted on every successful write. The `metadata` field MUST reflect the complete record as observable immediately after the write — for `updateMetadataField` this means the previously stored fields plus the newly updated one.\n\n### Read Semantics\n\n- `getMetadata`, `getValidatorName`, and `hasMetadata` MUST NOT call the staking precompile.\n- `STAKING_PRECOMPILE()` MUST return `0x0000000000000000000000000000000000001000`.\n- `hasMetadata` MUST return `true` iff the stored `name` is non-empty. Because `name` cannot be set to the empty string except as part of an all-default uninitialised slot, this is equivalent to \"any metadata has ever been written for this validator and not subsequently nulled\".\n- Implementations MAY expose additional view functions outside this interface (e.g. a combined \"staking info + metadata\" reader), but MUST NOT alter the semantics of any function defined here.\n\n### Chain Specifics\n\nA registry contract conforming to this MRC is deployed at an ordinary contract address chosen at deployment time — not at the precompile address — and the two are unrelated other than that the registry calls into the precompile at `0x0000000000000000000000000000000000001000` for authority resolution.\n\nThis MRC defines an interface and a behavioural spec, not a specific bytecode or address. Any number of independently-deployed conformant implementations may coexist on any Monad-EVM network; this MRC does not designate any of them as canonical and does not record an address. Integrators and validators choose which conformant deployment(s) to write to and read from on the basis of their own trust and operational preferences.\n\nAny deployment is valid only on networks that expose the staking precompile at `0x0000000000000000000000000000000000001000`; on networks where the precompile is absent, write methods would revert and read methods that proxy through the precompile would return the zero record, so a conformant registry cannot function on such networks.","rationale":"**Why anchor authorization to the staking precompile?** The precompile already holds the canonical mapping from `validatorId` to authority address and is the only mechanism by which authority rotations are recognized by consensus. Re-deriving identity from any other source (e.g. an ECDSA signature over a name) would introduce a second, divergent identity system.\n\n**Why separate `setMetadata` from `updateMetadataField`?** A validator that only wishes to change, say, a logo URL would otherwise have to resubmit the entire record (including potentially long `description` and `additionalInfo` fields) just to mutate one short string. Per-field updates reduce gas and calldata costs and reduce the chance of an authority accidentally clobbering other fields.\n\n**Why is `name` the only required field?** A validator without a name is indistinguishable from an unset record (see `hasMetadata`). All other fields have defensible empty defaults — a validator may legitimately have no website, no logo, or no social presence.\n\n**Why a JSON `additionalInfo` field?** Extending the struct is an ABI- breaking change. A free-form text field lets future MRCs layer structured extensions (delegation policies, signed attestations, IPFS CIDs, etc.) without a new registry deployment or storage migration. JSON was chosen over an opaque `bytes` blob because the rest of the record is already human-readable text, every off-chain consumer in this space already speaks JSON, and binary payloads can be hex-encoded inside a JSON value the few times they're needed.\n\n**Why JSON for `socials` (instead of one column per platform)?** Naming specific platforms in the struct would lock the registry to today's social-media landscape. A JSON object keyed by platform identifier is structured enough that tooling can pick out a known key (`\"x\"`, `\"telegram\"`, …) without prescribing the set; new platforms can be added by validators without any change to the registry, and platforms can be dropped without leaving dead struct fields behind. The registry deliberately does not parse JSON — that would be expensive on-chain and would force the spec to pin a specific JSON dialect — so conformance is enforced socially at the writer and reader.\n\n**Why is the authority address only a baseline, not the sole writer?** The authority address is the one identity that every validator demonstrably controls today, so anchoring on it gives a consistent, consensus-aligned default. But validators have real reasons to delegate metadata management — hot-key/cold-key separation, team operators rotating the brand without touching the staking key, multisig-gated changes for security-conscious operators — and forcing those flows to share the authority key would either expand its blast radius or push validators into off-chain curation again. Letting implementations extend the writer set above the authority baseline preserves the auditability of the default path (anyone can verify the authority is at least permitted) while leaving room for operationally realistic policies on top.\n\n**Why store data on-chain rather than just a content hash?** Storing names on-chain is cheap relative to the gas budget of validators, removes a dependence on external content-addressed storage availability for first-class fields like name and website, and lets light integrators read metadata without running an IPFS or HTTP fetcher. Heavier extension payloads may use `additionalInfo` to hold a content hash.","backwardsCompatibility":"This MRC is purely additive: it specifies a new application-layer contract and does not change the staking precompile, the EVM, or any existing consensus or networking behaviour. It introduces no backwards-incompatible changes.\n\nEcosystem tools that currently consume off-chain `metadata.json` files may continue to do so. Tools should migrate to reading from the registry when available, treating off-chain files as a fallback for validators that have not yet registered metadata.","securityConsiderations":"**Authority key compromise:** A compromised authority key can both drain validator stake (via the staking precompile) and post arbitrary metadata (via this registry). The registry does not amplify the impact of a compromise beyond what the staking precompile already allows. Validators should protect the authority key accordingly and should treat rotation of the authority as the canonical recovery path.\n\n**Phishing and impersonation via metadata:** Because `name`, `website`, `logo`, and `socials` are free-form and unverified, a malicious validator may copy the branding of another validator to siphon delegations. Integrators displaying registry data should:\n\n- Always render `validatorId` and the validator's authority address alongside human-readable fields, so that two distinct validators with identical names remain distinguishable.\n- Treat all string fields as untrusted: HTML-escape on render, refuse to auto-load remote images by default, and validate URL schemes.\n- Consider maintaining a curated allow-list of authority addresses for any feature that grants elevated trust (e.g. featured validators) — the registry's job is to provide self-attested data, not to attest its truth.\n\n**Storage griefing:** Strings and `additionalInfo` are unbounded in length. A validator may pay to store an arbitrarily large record. This affects only that validator's own gas cost (an authorized caller must sign the transaction) and the SLOAD cost of `getMetadata` for that validator. Implementations that grant write access beyond the authority address should ensure the additional callers cannot impose costs the validator did not themselves agree to bear. Integrators concerned about read gas should prefer the field-scoped getters (`getValidatorName`, `hasMetadata`) where they suffice, and should impose their own client-side display limits on string lengths.\n\n**Authority resolution race:** Authorization is resolved by calling the staking precompile inside the same transaction as the write. There is no TOCTOU window: the precompile cannot change authority mid-transaction.\n\n**Reorgs:** Like any on-chain state, registry contents are subject to reorganisation. Integrators that cache the registry should respect the chain's finality guarantees before treating an update as durable.\n\n**`STATICCALL` and `DELEGATECALL` restrictions:** Monad's staking precompile permits only standard `CALL`s. The registry must invoke the precompile via ordinary `CALL` (no `delegatecall`, no `staticcall` on write paths). View methods that proxy to the precompile must be marked `view`, which forces `STATICCALL` semantics — implementations relying on this must verify the precompile's view methods are STATICCALL-compatible on the target network.\n\n**No upgrade path:** This MRC specifies an immutable, non-upgradeable contract. A future MRC that changes the storage layout or interface must take effect through new deployments at new addresses, not by mutating existing ones. Integrators must treat each registry deployment as fixed and independently track which deployment(s) they consider current, rather than assume the standard guarantees a stable address over time.","subsystems":["rpc","consensus","evm","staking","db","execution"],"createdAt":"2026-06-16T16:43:54+02:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0012","numberShort":"12","title":"Decrease Block Time","description":"Decrease consensus vote pace from 400ms to 300ms","status":"Final","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-12-decrease-vote-pace/488","forumTopicId":488,"created":"2026-06-01","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-12.md","contentHash":"63c93da7dd00f65a6167f384b9c95a387a0fce47a45ada5fe7626e2ffe5086d8","abstract":"Decrease the block time by decreasing the consensus vote pace from 400ms to 300ms. Proportionally, decrease the following block parameters: transaction limit, proposal gas limit and proposal byte limit. Proportionally, also decrease the block reward.","specification":"We propose the following changes to the chain parameters on the consensus client:\n\n### Chain Parameters\n\n| Chain Parameter | Current Value | Proposed Value |\n| --- | --- | --- |\n| vote_pace (ms)  | 400 | 300 |\n| tx_limit | 5,000 | 3,750 |\n| proposal_gas_limit | 200,000,000 | 150,000,000 |\n| proposal_byte_limit | 2,000,000 | 1,500,000 |\n\nWe also propose the following change to the staking parameters, to roughly account for the more frequent block times:\n\n### Staking Parameters\n\n| Staking Parameter | Current Value | Proposed Value |\n| --- | --- | --- |\n| block_reward (MON)| 25 | 18 |\n\n### Consensus and Execution Layer Impact\n\nThis change should not affect the execution client in any way.\n\nThe consensus client will vote on proposals 100 milliseconds faster than it does currently, leading to faster quorums and faster block times.","rationale":"-","backwardsCompatibility":"This change is backward compatible with the execution client.\n\nHowever, since the block parameters are used for consensus block validation, this change is not backward compatible with the consensus client and will require a hard fork on a round.","securityConsiderations":"-","subsystems":["execution","consensus","evm","staking"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0011","numberShort":"11","title":"Automatic Priority Fee Distribution","description":"Automatically distribute priority fees to delegators.","status":"Draft","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-11-automatic-priority-fee-distribution/419","forumTopicId":419,"created":"2026-04-01","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-11.md","contentHash":"49cb6fe23ab598ea0b425f281a4be180a93d1726ce1169f1a0b65db375e58cd8","abstract":"This MIP automatically distributes priority fees to delegators rather than crediting them solely to the validator's beneficiary address.","motivation":"Currently, priority fees are credited directly to a validator's beneficiary address. In principle, a validator could forward these fees to its delegators by calling `externalReward` on the staking contract, but doing so is operationally cumbersome — it requires each validator to run additional infrastructure to sweep the beneficiary balance, manage gas for the forwarding transaction, and handle the `dust_threshold` minimum. In practice, most validators do not do this today, so priority fees accrue to the beneficiary rather than flowing through to delegators.\n\nTo ensure consistent compensation for all stakers without relying on per-validator tooling, this proposal introduces a mechanism that automatically distributes priority fees to delegators at the protocol level.","specification":"### Overview\n\nAutomated priority fee distribution has two components:\n\n1. A new account that captures priority fees, referred to as the `distribution account`. The address for the distribution account will be `0xfee5fee5fee5fee5fee5fee5fee5fee5fee5fee5`. \n2. End-of-block execution logic that calls `external_rewards` on the corresponding validator pool with the priority fees accumulated in the `distribution account`.\n\nThe `beneficiary` remains settable by the block proposer, and within the execution context, `block.coinbase` continues to refer to the beneficiary address.\n\n### Execution Flow\n\nThe following changes are applied during block execution:\n\n1. The beneficiary is still set by the block proposer and is still represented by `block.coinbase` in the execution context.\n2. For each transaction, the priority fee is credited to the distribution account rather than to the beneficiary balance.\n3. At the end of block execution, the system calls `syscall_distribute` on the distribution account. This function forwards the full accumulated balance to the staking contract via `external_rewards`.\n\nThe distribution account has the following logic:\n\n```python\nclass distribution_account:\n\n    # This function is only callable via execution; no transaction can call it.\n    def syscall_distribute(address block_leader):\n        priority_fees = get_balance(address(this))\n\n        # Same value as the val_id used by syscall_reward for block_leader.\n        val_id = staking_contract.val_id(block_leader)\n\n        # Sub-threshold fees are filtered by external_rewards (see dust_threshold).\n        staking_contract.external_rewards(val_id){msg.value = priority_fees}\n```","rationale":"Priority fees are a component of validator revenue, and delegators should receive a share of this allocation in return for helping secure the network.\n\nThis design ensures:\n\n1. Native inclusion of priority fees within staking rewards.\n2. Elimination of any reliance on off-chain or external distribution logic.","backwardsCompatibility":"This change modifies the flow of priority fees: they will no longer appear in the balance of `block.coinbase` as a direct credit.\n\nBecause priority fees are now distributed to all delegators within a validator's pool, third-party delegation contracts may be affected. Such contracts will experience a dilution in their share of priority fees if users bypass the external contract and stake directly with the validator pool.\n\nTo be amenable to this update `external_rewards` will be modified so that it removes the commission fee when called.","securityConsiderations":"The primary consideration is the precision of the reward accumulator.\n\n`external_rewards` requires a minimum input amount, defined as the `dust_threshold`, in order to guarantee a certain decimal accuracy within the accumulator. The minimum threshold for non-zero priority fees must align with this `external_rewards` minimum-balance requirement, and any fees below this threshold will not be distributed.\n\nEdge cases to consider:\n\n1. Blocks with zero priority fees result in a no-op distribution.\n2. Small fee amounts must be validated against `dust_threshold`; sub-threshold fees are burned.","subsystems":["execution","evm","staking","economics"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0010","numberShort":"10","title":"Deterministic RaptorCast","description":"Canonical RaptorCast encoding that removes author control over chunk placement and binds each proposal to a per-round commitment","status":"Draft","type":"Standards Track","category":"Networking","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-10-deterministic-raptorcast/453","forumTopicId":453,"created":"2026-04-28","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-10.md","contentHash":"dbab280cc14e23fd719d3c86569b0de6893b6c8d9ca8e9c2cd9b451a6142b6dc","abstract":"Deterministic RaptorCast (v1) pins each canonical encoded symbol to a fixed position in a single Merkle tree spanning the whole message, derives the assignment of chunks to validators from a publicly recomputable seed, and binds the author to a per-round `EncodingCommitment` covering the message-level Merkle root `R`, encoding scheme, payload length, and timestamp.\n\nA validator authenticates the packet header and author identity, recomputes the seed, derives the same assignment from its view of the epoch validator set, verifies each chunk against the message-level root, and rejects any chunk whose commitment conflicts with the one already recorded for that round. Having reconstructed the payload, the validator re-encodes it and checks that the resulting Merkle root matches the committed root. Given the payload, author, round metadata, and epoch validator set, v1 deterministically fixes the encoding, root, and chunk assignment; each correct validator accepts at most one `EncodingCommitment` per round.","motivation":"MonadBFT uses RaptorCast to disseminate block proposals across the validator set. The implementation distinguishes Primary RaptorCast, which carries a proposal within the validator set, from Secondary RaptorCast, which carries it onward to full nodes. This MIP specifies Primary RaptorCast. Secondary RaptorCast also uses the deterministic encoding introduced here, but its routing and assignment logic is outside the scope of this specification. The author is the validator selected for the round that constructs, signs, and disseminates the RaptorCast proposal.\n\nThroughout this MIP, v0 refers to the existing RaptorCast proposal format (see [RaptorCast: Designing a Messaging Layer](https://www.category.xyz/blogs/raptorcast-designing-a-messaging-layer)) and v1 to the deterministic proposal format introduced by this MIP.\n\nAn Encoding Symbol ID (ESI) is the integer identifier of a Raptor-encoded symbol. Under v0 an ESI may sit at any position in the Merkle tree. The author also chooses which chunks go to which validator and writes that choice into each chunk. No node can check the choice, because there is no other answer to compare it against. In v1, ESI `i` is also the canonical chunk index and determines that symbol's position in the Merkle tree.\n\n**A canonical root.** In v0, the encoded chunks of a message are grouped into batches of up to `32`; each batch has its own Merkle tree, and the author signs each batch root separately. That authenticates a chunk on arrival, which is what dissemination needs, but it does not name the message: the batches stand alone, and the chunk header carries the ESI and the leaf index separately, so one block can be encoded in more than one way. In v1, the payload, encoding scheme, and corresponding validator set determine one canonical encoding and hence one root; the authenticated round metadata and elected author additionally determine the canonical chunk assignment.\n\nOne effect is immediate: the message-level root is carried in the signed packet header, while individual chunk contents are authenticated by their Merkle proofs against that root. Implementations can cache successful packet-header authentication and reuse it across chunks carrying the same authenticated header, avoiding redundant signature verification. In contrast, v0 requires authentication of each independently signed batch.\n\nA canonical encoding also enables further improvements. With further protocol changes, beyond those proposed here, validators could vote on the root after verifying a single chunk, avoiding reconstruction on the critical path. Historical blocks could be synchronized as verifiable chunks regenerated by any node holding the block, rather than fetched as whole payloads from one source. And because the commitment is bound to the round, two incompatible commitments signed by the elected author provide direct evidence of equivocation. These extensions are described under Future directions.\n\n**Preventing temporary asymmetric liveness.** Raptor codes decode most efficiently when the decoder receives at least some degree-1 (singleton) ESIs, since these provide useful starting points for the Belief Propagation decoder. Without a deterministic assignment, a Byzantine author can exploit its control over chunk placement to give targeted validators a deliberately unfavorable set of ESIs while giving others an easier set, creating a significant difference in reconstruction time.\n\nThis proposal removes that direct source of asymmetry by deriving the chunk assignment deterministically from public information, so the author no longer chooses which ESIs are assigned to which validators but has only a limited choice among the small number of timestamp buckets permitted by the admission window. A Byzantine author may still selectively deliver useful chunks to some validators, but this provides a substantially weaker timing advantage than directly constructing pathological assigned sets. Temporary asymmetric liveness will allow Byzantine leaders to deliberately slow some validators but it doesn't affect the protocol's eventual liveness guarantee.","specification":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.html) and [RFC 8174](https://www.ietf.org/rfc/rfc8174.html).\n\n### Parameters\n\nThe broadcast-mode/depth field and `encoding_scheme_variant` are each one byte. The encoding scheme is the parameter set that determines the canonical encoding, and `encoding_scheme_variant = 0x1` names the scheme defined by this MIP: R10 encoding, segment length, redundancy factor, permitted depth range, chunk assignment procedure, and assignment seed derivation. The v1 packet version separately fixes the packet-format parameters. Because `symbol_len` is the space a segment leaves after the packet header, chunk header, and Merkle proof, the canonical encoding depends on both the scheme and the packet format: a change to either changes every canonical root, so a new scheme variant or packet version is REQUIRED for either.\n\n| Encoding scheme parameter | Value |\n| --- | --- |\n| `encoding_scheme_variant` | `0x1` |\n| Encoding | Raptor 10 (monad-raptor) |\n| Redundancy | 2.5 |\n| Segment length | 1,440 bytes |\n| Seed timestamp bucket | 2048 ms |\n| Permitted Merkle tree depth | 3–15 |\n\n| V1 packet-format parameter | Value |\n| --- | --- |\n| Packet header length | 117 bytes |\n| Chunk header length | 4 bytes |\n| Merkle hash size | 20 bytes |\n\nThe reference implementation also applies local admission limits of a 10,000 ms timestamp tolerance and a ±100-round acceptance window. These are implementation-policy defaults rather than canonical encoding or packet-format parameters; implementations MAY vary them without changing the v1 encoding or wire format.\n\n### Encoding\n\nThe author encodes payload `B` into `n` chunks, where `n` denotes the total number of encoded chunks produced for this proposal:\n\n$$\n\\begin{aligned}\nc_i    &= \\mathrm{RaptorEnc}(B).\\mathrm{symbol}(i), \\qquad 0 \\le i < n \\\\\n\\ell_i &= H(\\mathtt{chunk}\\rule{0.5em}{0.06em}\\mathtt{header}_i \\mathbin{\\Vert} c_i) \\\\\nR      &= \\mathrm{MerkleRoot}(\\ell_0, \\ldots, \\ell_{n-1})\n\\end{aligned}\n$$\n\n`RaptorEnc` is the R10 encoder of monad-raptor.\n\nThe 4-byte chunk header consists of two reserved bytes followed by the ESI encoded as an unsigned 16-bit little-endian integer:\n\n$$\n\\mathtt{chunk}\\rule{0.5em}{0.06em}\\mathtt{header}_i = \\mathtt{0x00} \\mathbin{\\Vert} \\mathtt{0x00} \\mathbin{\\Vert} \\mathrm{LE16}(i)\n$$\n\nThe reserved bytes MUST be zero in v1.\n\nLeaf `i` of the Merkle tree MUST hold the chunk with `ESI = i`.\n\nEvery populated leaf and every internal node of the tree is a BLAKE3 digest truncated to its first 20 bytes. A leaf is `H(chunk_header_i ‖ c_i)` — the 4-byte chunk header and the symbol, excluding the packet header and the proof — and an internal node is `H(left ‖ right)`. The tree has a fixed `2^(d-1)` leaf slots, where `d` is the Merkle tree depth derived below; the slots beyond `n` hold the literal 20-byte zero value, inserted as raw leaves, and a re-encoder MUST reproduce this leaf padding exactly.\n\nThe symbol length and the chunk count both follow from the payload length, the validator set, and the tree depth, where `r` is the redundancy factor:\n\n$$\n\\begin{aligned}\n\\mathrm{symbol}\\rule{0.5em}{0.06em}\\mathrm{len}(d) &= \\mathrm{segment}\\rule{0.5em}{0.06em}\\mathrm{len} - \\mathrm{header}\\rule{0.5em}{0.06em}\\mathrm{len} - \\mathrm{chunk}\\rule{0.5em}{0.06em}\\mathrm{header}\\rule{0.5em}{0.06em}\\mathrm{len} - 20 \\cdot (d-1) \\\\\nK(d) &= \\left\\lceil \\frac{\\mathrm{app}\\rule{0.5em}{0.06em}\\mathrm{message}\\rule{0.5em}{0.06em}\\mathrm{len}}{\\mathrm{symbol}\\rule{0.5em}{0.06em}\\mathrm{len}(d)} \\right\\rceil \\\\\nN(d) &= \\left\\lceil K(d) \\cdot r \\right\\rceil\n\\end{aligned}\n$$\n\nSource-block padding: the encoder input is the payload zero-padded to `K(d) × symbol_len(d)` bytes. Every encoded symbol is therefore exactly `symbol_len(d)` bytes, so every populated leaf hashes the same `chunk_header_len + symbol_len(d)` bytes and no short chunk is carried on the wire. This padding is distinct from the leaf padding above, which fills the unused leaf slots of the tree.\n\nThe depth is the smallest `d ∈ [3,15]` such that `2^(d-1) ≥ N(d) + |V|`, where `V` is the epoch validator set excluding the proposal author. The `+|V|` term reserves space for the surplus introduced by rounding each validator's stake-proportional chunk allocation upward; the assignment below guarantees `N(d) ≤ n < N(d) + |V|`. Once `d` is fixed, the assignment below determines the exact chunk count `n`. Each chunk carries a Merkle proof of `20 × (d − 1)` bytes, at most `280` bytes. A v1 packet MUST be rejected if `app_message_len` is zero or exceeds the maximum proposal size fixed by the consensus protocol, if its depth is outside `[3,15]`, if its depth is not the smallest permitted value satisfying the condition above, or if no depth in `[3,15]` satisfies that condition.\n\n**Packet-header canonicalization.** A packet accepted as Primary v1 MUST carry the v1 packet version and Primary RaptorCast mode. The two reserved bits of the broadcast-mode/depth byte MUST be zero. The epoch MUST equal the epoch derived from the round, and the Merkle tree depth MUST equal the deterministically derived depth. A packet violating any of these conditions MUST be rejected.\n\n**Packet authentication.** Let `P` be the RaptorCast-chunk signing-domain prefix `0x19 || \"monad/raptorcast-chunk/1\\n\"`. Let `header` be the 52-byte packet header excluding the signature field, serialized in wire order. Its fields are, in order: version (2 bytes), broadcast-mode/depth (1 byte), encoding-scheme variant (1 byte), round (8 bytes), epoch (8 bytes), Unix timestamp in milliseconds (8 bytes), global Merkle root (20 bytes), and application-message length (4 bytes). All multi-byte integer fields are little-endian. The author signs the full 32-byte BLAKE3 digest `BLAKE3(P ‖ header)`. Thus the packet signature authenticates the complete packet header, including fields that are not part of the `EncodingCommitment`.\n\n**EncodingCommitment.** The `EncodingCommitment` `C` is\n\n$$\nC = R \\mathbin{\\Vert} \\mathtt{encoding}\\rule{0.5em}{0.06em}\\mathtt{scheme}\\rule{0.5em}{0.06em}\\mathtt{variant}\\ \\mathbin{\\Vert}\\  \\mathrm{LE64}(\\mathrm{round}) \\mathbin{\\Vert} \\mathrm{LE32}(\\mathrm{app}\\rule{0.5em}{0.06em}\\mathrm{message}\\rule{0.5em}{0.06em}\\mathrm{len})\\newline\\mathbin{\\Vert}\\ \\mathrm{LE64}(\\mathrm{unix}\\rule{0.5em}{0.06em}\\mathrm{ts}\\rule{0.5em}{0.06em}\\mathrm{ms})\n$$\n\nThe serialization of `C` is defined independently of the packet-header wire layout. Here `R` is the 20-byte message-level Merkle root and `encoding_scheme_variant` is one byte. The round identifies the commitment instance. Together with the elected author and corresponding validator set, these fields identify the signed proposal claim; the timestamp bucket derived from `unix_ts_ms` is used for deterministic chunk assignment, while `R` commits to the resulting encoded chunks.\n\nThe packet signature authenticates the header fields from which `C` is constructed. The signature is not part of `C`. Different valid signatures authenticating the same `C` represent the same `EncodingCommitment`.\n\nTwo Primary v1 packets are commitment-compatible exactly when their authenticated `EncodingCommitment`s `C` are identical. The elected author MUST NOT authenticate more than one distinct Primary v1 `EncodingCommitment` for the same round. The `EncodingCommitment` identifies the author's signed proposal claim, not only the resulting encoding or chunk assignment. Consequently, two authenticated headers for the same round that differ in `unix_ts_ms` represent distinct `EncodingCommitment`s even if their timestamps fall within the same assignment-seed bucket.\n\n**Encoding context.** The authenticated packet-header fields comprising `C`, together with the elected author for the committed round and the corresponding validator set, constitute the encoding context. The `EncodingCommitment` `C` is the canonical concatenation of those header fields. Fields outside `C` that are part of the packet context, including version, mode, epoch, reserved bits, and Merkle tree depth, MUST satisfy the packet-header canonicalization rules above. Together, the encoding context determines the canonical encoding, chunk count, Merkle tree depth, and first-hop assignment.\n\n### Chunk assignment and seed derivation\n\nThe seed determines the canonical first-hop recipient of each chunk.\n\nThe seed is deterministically computed from the authenticated packet metadata and elected author. Let `b = floor(unix_ts_ms / 2048)`. The seed is the 32-byte concatenation of `LE64(round)`, `LE64(b)`, and bytes with zero-based indices 1 through 16 of the elected author's 33-byte compressed public key.\n\nThe partition takes `K`, the source-symbol count for the chosen depth; the redundancy factor `r` fixed by the encoding scheme; and `V`, the epoch validator set excluding the proposal author, ordered ascending by the validator's 33-byte compressed public key, compared byte-lexicographically.\n\nWith `N = ceil(K × r)` and `total_stake` the sum of stakes over `V`, each validator's obligation and the canonical chunk count are\n\n$$\n\\mathrm{obligation}(v) = \\left\\lceil \\frac{\\mathrm{stake}(v) \\cdot N}{\\mathrm{total}\\rule{0.5em}{0.06em}\\mathrm{stake}} \\right\\rceil,\n\\qquad\nn = \\sum_{v \\in V} \\mathrm{obligation}(v)\n$$\n\nso that $$N \\le n < N + \\lvert V \\rvert$$. The surplus arises solely from rounding each allocation upward.\n\n`N` and `obligation(v)` MUST equal the exact values of `ceil(K × r)` and `ceil(stake(v) × N / total_stake)`. The redundancy factor `r` is exactly representable in binary, so the product `K × r` is integral. Implementations MUST NOT use floating-point arithmetic, and MUST NOT allow the intermediate product `stake(v) × N` to wrap: a single rounding or wrapping difference changes `n` and therefore the whole assignment.\n\nThe canonically ordered validator set is shuffled in place using Fisher–Yates, as in the procedure below, drawing each index uniformly with the ChaCha20 RNG specified here. The RNG uses the 32-byte assignment seed, 20 ChaCha rounds, a 64-bit counter initialized to zero, and a 64-bit stream identifier initialized to zero. `next_u32()` returns successive 32-bit output words from this RNG. The ChaCha output is consumed in block order as successive 32-bit little-endian words. In the procedure below, `m`, `x`, and `z` are unsigned 32-bit integers, `p` is an unsigned 64-bit integer, and `clz32(m)` denotes the number of leading zero bits in the 32-bit representation of `m`. The `z` calculation uses unsigned 32-bit wrapping arithmetic.\n\n```python\ndef draw(i, next_u32):\n    \"\"\"Uniform j in [0, i]; widening-multiply rejection sampling.\"\"\"\n    m = i + 1\n    z = ((m << clz32(m)) & 0xFFFF_FFFF) - 1   # 32-bit wrapping\n    while True:\n        x = next_u32()\n        p = x * m                              # 64-bit product\n        if (p & 0xFFFF_FFFF) <= z:             # low32(p) <= z\n            return p >> 32                     # high32(p)\n\nfor i in range(len(V) - 1, 0, -1):             # i = |V|-1 down to 1\n    j = draw(i, rng.next_u32)\n    V[i], V[j] = V[j], V[i]\n```\n\nThe shuffle fixes the validator ordering and consumes all randomness used by the assignment. Chunk indices are then dealt in increasing order to the shuffled validators, one at a time, skipping validators whose obligations are exhausted:\n\n```python\nremaining = [obligation(v) for v in V]\ni = 0\nfor chunk_index in range(n):\n    while remaining[i] == 0:\n        i = (i + 1) % len(V)\n    assignment[V[i]].add(chunk_index)\n    remaining[i] -= 1\n    i = (i + 1) % len(V)\n```\n\nThe procedure terminates after exactly `n` assignments, with `|assignment[v]| = obligation(v)` for every `v`. A validator's indices are not generally contiguous: they follow the round-robin order while several validators still have obligations outstanding, and the pattern changes as validators drop out.\n\nEvery receiver using the same authenticated encoding context derives the same assignment and uses it to determine which chunks it is responsible for rebroadcasting.\n\n### Chunk validation\n\nA validator accepts a Primary v1 packet with signed packet header `H` and chunk fields `(chunk_header, π, c)` if and only if all of the following hold:\n\n- **Timeliness.** The timestamp MUST be within the timestamp tolerance of the validator's local clock. Once the validator has locally entered its first round, the packet's round MUST also be within the round acceptance window of its current round.\n\n- **Author authenticity.** The packet signature MUST validly authenticate the signed packet header for the elected author. A validator that does not yet know the elected author for the round rejects the chunk.\n\n- **Chunk integrity.** Let `i` be the ESI read from the chunk header; it is both the Raptor ESI and the Merkle leaf index, and no separate index field is carried. The two reserved chunk-header bytes MUST be zero, `i < 2^(d-1)` MUST hold, and the Merkle proof `π` MUST verify the leaf `H(chunk_header ‖ c)` at leaf index `i` against `R`.\n\n- **Encoding commitment consistency.** If a commitment is already recorded for this round, the `EncodingCommitment` `C` constructed from the authenticated packet header MUST equal it.\n\n- **Assignment.** The chunk index MUST satisfy `i < n`. After the Merkle proof verifies the chunk at index `i` against the root in the authenticated packet header, the canonical assignment for that encoding context determines its first-hop recipient and rebroadcast responsibility.\n\nA validator records `C` for a round from the first authenticated Primary v1 header it receives for that round. Acceptance of the associated chunk still requires all applicable packet and chunk validation checks.\n\nHaving decoded payload `B`, a validator MUST verify that `|B| = app_message_len`, re-run the canonical encoding above, and reject the payload unless the resulting root equals `R`. A validator that rejects a payload for this reason proceeds as if it had received no proposal for the round.","rationale":"Deterministic RaptorCast leaves the Raptor code itself unchanged, and derives the seed from metadata the proposal already carries.\n\nThe Merkle construction is replaced by a single tree over the whole message, with its root carried in the signed header, which is what allows a chunk to be verified against a message-level commitment. The wire format changes accordingly: the per-chunk recipient field is no longer carried, since first-hop recipients and rebroadcast targets are both derived from the assignment, while the signed packet header carries the round and the message-level root. There are no changes to the consensus voting protocol, the quorum certificate format, or the execution layer.\n\n### Properties\n\n- **Verifiable Chunk Regeneration.** Any party holding a validated block `B`, `app_message_len`, `encoding_scheme_variant`, the epoch validator set, and the elected author for the committed round can deterministically regenerate every canonical chunk and its Merkle proof under the same committed root `R`. This follows from the deterministic canonical encoding and Merkle tree construction and requires no additional cryptographic assumption.\n\n- **Integrity.** If the author is correct, every correct validator that accepts a reconstructed payload recovers exactly the payload originally dispersed by the author.\n\n- **Consistency.** Fix an authenticated encoding context with committed root `R`. Assuming correctness of the Raptor encoding/decoding procedure and collision resistance of the Merkle hash, at most one payload `B` has a canonical re-encoding reproducing `R`. Consequently, correct validators that successfully decode from chunks verified under the same authenticated encoding context all reach the same outcome: either they all reject, or they all accept the same `B`.\n\nConsistency removes the ambiguity described above: each correct validator admits at most one `EncodingCommitment` per round, and the decode–re-encode check rejects any reconstructed payload whose canonical encoding does not reproduce the committed root.\n\nThe `EncodingCommitment` `C` binds the committed root `R` to the round, encoding scheme, payload length, and timestamp. The signed packet header authenticates that commitment for the elected author. The corresponding validator set is derived from the committed round, and the Merkle tree depth is deterministically derived from the encoding parameters and validator set. A party holding `B`, the authenticated packet header, and the corresponding validator set can reconstruct the canonical encoding of `B` and verify that its root equals `R`. This establishes that the elected author authenticated a commitment to `B` for that round; it does not establish that the author disseminated nothing else.\n\nA quorum certificate is a separate statement. Under the current voting rule, a QC for `B` implies that at least `f+1` correct validators reconstructed and validated `B`. By the Verifiable Chunk Regeneration property, each of those validators can regenerate any canonical chunk and its proof against `R`. Separately, two valid signed packet headers from the elected author for the same round that authenticate distinct `EncodingCommitment`s provide evidence that the author committed twice for that round.\n\nA successful decode–re-encode check binds the root in the signed header to the reconstructed payload. Merkle proofs bind individual chunks to that root, and the signature binds the header to the author, but neither alone establishes that the committed chunks form the canonical encoding of a single payload. Fixing each ESI to its leaf position specifies the required encoding; re-encoding after reconstruction verifies that the committed root is consistent with that canonical encoding. Without this check, an author could commit to a root whose leaves are drawn from incompatible encodings, and validators decoding different subsets may recover different payloads.\n\nEnforcing the decode–re-encode check from the first v1 proposal ensures that every block accepted under v1 has a reproducible canonical root. This matters for later extensions such as chunk-based synchronization, which rely on historical blocks having reproducible canonical encodings. The cost is one re-encode for each decoded block.\n\n### Future directions\n\nThe work done in this proposal is the encoding itself: each ESI fixed to its Merkle leaf index, a single message-level root carried in the signed header, a stake-weighted chunk assignment derived from a public seed, a per-round `EncodingCommitment`, and the decode–re-encode check that holds an author to that encoding. The extensions below will build on this foundation. Root voting will be specified in a forthcoming MIP. Chunk-based synchronization and equivocation accountability are additional protocol directions enabled by the canonical encoding; their specification and integration are outside the scope of this MIP.\n\n**Voting on the root.** This proposal provides the foundation for root voting by giving every proposal a single message-level root and making every verified chunk authenticate against that root. A future extension could therefore allow a validator to vote after receiving a single verified chunk, taking reconstruction off the critical path and saving a message delay. Voting before reconstruction additionally requires a dissemination condition ensuring that a quorum certificate implies enough distinct canonical chunks remain recoverable from correct parties, together with an encoding whose required chunk threshold decodes with certainty. A future voting extension must bind each vote to the `EncodingCommitment` `C`, rather than to the bare Merkle root `R`, because `C` additionally binds the round, encoding scheme, payload length, and timestamp. These additional requirements for root voting are outside the scope of this proposal.\n\n**Chunk-based synchronization.** The Verifiable Chunk Regeneration property above enables a node synchronizing a historical block to recover it as independently verifiable chunks from multiple peers rather than downloading the complete payload from a single source. Any correct validator that has reconstructed and validated the block can use the payload and its encoding context to deterministically regenerate the canonical chunk for any requested ESI together with its Merkle proof, without retaining the originally transmitted chunks. The receiving node verifies each chunk against the committed root, reconstructs the payload once enough verified chunks have been collected, and re-encodes it to confirm the same root. The chunk count and assignment are derived from the encoding context and therefore need not be transmitted separately. Peer discovery, requests, retries, and chunk selection are outside the scope of this MIP.\n\n**Equivocation evidence.** If the elected author authenticates two distinct Primary v1 `EncodingCommitment`s `C1 ≠ C2` for the same round, it has issued conflicting commitments for that round. A portable proof consists of the corresponding two signed packet headers. A verifier checks both signatures, verifies that both headers refer to the same round, recomputes the `EncodingCommitment` from each header, confirms that the signer was the elected author for that round, and verifies that `C1 ≠ C2`. Different valid signatures authenticating the same `C` do not constitute equivocation.","backwardsCompatibility":"The v1 packet header is not interpretable by nodes that understand only v0, and the two versions cannot decode one another's chunks. The v1 format applies only to proposal dissemination: v0-only validators cannot interpret v1 proposal chunks, while v0 remains in use for non-proposal messages such as votes and timeouts.\n\nDeployment therefore proceeds in four stages, each of which must be fully adopted before the next begins:\n\n1. **Accept v0 proposals only.** Nodes publish and accept v0. This is the pre-existing behavior.\n2. **Accept both, publish v0 proposals.** Nodes accept v1 chunks but continue to publish v0, so that v1-capable receivers are in place before any v1 traffic exists.\n3. **Accept both, publish v1 proposals.** Nodes publish v1 while continuing to accept v0 from nodes that have not yet advanced.\n4. **Accept v1 proposals only.** Support for the v0 proposal format is withdrawn.\n\nNodes at adjacent stages interoperate; nodes two or more stages apart do not. Chunks of an unaccepted version are dropped without further processing.","securityConsiderations":"**Mixed-payload roots.** An author may commit to a root whose leaves are drawn from the encodings of more than one block. Every chunk verifies against that root, and validators collecting different subsets may recover different payloads, so neither the Merkle proof nor the per-round commitment rejects them. Any reconstructed payload whose canonical re-encoding differs from the committed root is rejected, so a mixed root cannot cause correct validators to accept two distinct payloads under the same signed header, assuming correctness of the Raptor encoding and decoding procedure and collision resistance of the Merkle hash.\n\n**Seed grinding.** The only grindable component is the timestamp bucket, and the timeliness check confines it to the acceptance window. Since the seed uses `floor(unix_ts_ms / 2048)`, the number of distinct seeds an author can reach is the number of buckets the window intersects: 10 or 11 under the default ±10 seconds, depending on alignment. An author can enumerate all of them.\n\nSeed choice changes only the first-hop assignment of canonical symbols to recipients, so it cannot alter the global Raptor degree distribution. Changing timestamp/seed cannot evade commitment-conflict detection.\n\n**Round window.** Chunks outside the round acceptance window are silently dropped. This prevents unbounded buffering but means a node that falls significantly behind the current round will not receive chunks for rounds outside its window until it catches up. Before a node has locally entered its first round, no window is applied and admission is bounded instead by a per-author quota on the number of rounds each author may open.","subsystems":["networking","consensus","staking","db","economics","execution"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0009","numberShort":"9","title":"Active Set Increase","description":"Increase the `ACTIVE_VALSET_SIZE` from 200 to 300.","status":"Withdrawn","type":"Standards Track","category":"Core","authors":[{"name":"Jackson Lewis","github":"jacksononchain"}],"discussionsTo":"https://forum.monad.xyz/t/mip-9-active-set-increase/416","forumTopicId":416,"created":"2026-03-19","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-9.md","contentHash":"93cd6f17b82254460fa667aeac16c9b4a27f47c86f03139574655e160cb38eb7","abstract":"This MIP proposes an increase to the maximum active validator set from 200 to 300.\n\nThe current active validator set is capped at 200. Raising the cap expands participation without introducing abrupt load changes to the consensus layer (MonadBFT) or execution pipeline.","specification":"### Parameters\n\n| Parameter | Current Value | Proposed Value |\n|---|---|---|\n| `ACTIVE_VALSET_SIZE` | 200 | 300 |\n\n`ACTIVE_VALSET_SIZE` SHOULD be increased from 200 to 300.\n\n### Consensus and Execution Layer Impact\n\nThis MIP affects the consensus layer (MonadBFT). The active validator set size directly governs the number of participants in each consensus round. The execution daemon is unaffected unless validator set membership is read by on-chain contracts, in which case any such contracts SHOULD be reviewed for compatibility.\n\nThe consensus daemon enforces `ACTIVE_VALSET_SIZE` as an upper bound on the number of validators eligible to participate in block proposal and voting at any given epoch. The selection mechanism for which validators fill the active set (e.g., by stake) is unchanged by this MIP.","rationale":"An increment of 100 represents a meaningful expansion (~50%) without dramatically altering the message complexity in MonadBFT's voting rounds. Larger single-step increases carry higher risk of unforeseen performance degradation; smaller increments would add process overhead without proportionate benefit.\n\nThe increase allows for greater decentralization in the active set, enforcing stronger fault tolerance and economic security.","backwardsCompatibility":"This MIP does not introduce backwards incompatibilities. The change is additive: existing validators in the active set are unaffected. The hard-coded `ACTIVE_VALSET_SIZE = 200` must be updated to `300` to support the new parameter values prior to activation.","securityConsiderations":"**Consensus scalability**: Increasing the active validator set increases the number of messages exchanged per consensus round in MonadBFT.\n\n**Sybil risk**: Expanding the set without changes to the stake-based selection mechanism does not meaningfully increase Sybil risk. No additional mitigations are required.","subsystems":["execution","consensus","staking","economics"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0008","numberShort":"8","title":"Page-ified Storage State","description":"Partition EVM storage to align with database pages","status":"Final","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-8-page-ified-storage-state/407","forumTopicId":407,"created":"2026-03-05","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-8.md","contentHash":"2f38836368a6e0525658b222f8b21593f964e5377b8ea229d8d89cae208e15cc","abstract":"We introduce key locality to the Merkle Patricia Trie by adding a page abstraction to the state model, enabling page-level access and warm `SLOAD`/`SSTORE` cost for any slot within a loaded page.","motivation":"The EVM abstracts storage as 32-byte `{slot, value}` pairs. The gas schedule of this model is tied to the commitment scheme of the Merkle Patricia Trie, since the trie acts on these pairs. This creates a dilemma: mapping the MPT to disk causes access to a 32-byte slot to require loading an entire 4KB page, while optimizing the physical disk layout independently leaves the commitment layer bound to the original slot-based pricing model. \n\nIn either case, the inefficiency is propagated by the application layer and the state layer. At the application layer, high-level languages such as Solidity rely heavily on `keccak256` hashing for mapping layouts. This causes related data to be assigned to pseudorandom storage locations. At the state layer, the MPT hashes keys before commitment, so sequential slot updates modify disjoint regions of the trie. These factors cause logically contiguous slots to be scattered across disjoint pages, resulting in related data being charged as independent disk reads. \n\nTo address this, we introduce a page abstraction to the state model.  A page is a fixed-size contiguous group of EVM slots. Pages become the atomic unit for both disk I/O and MPT commitments. Once a page is loaded, subsequent `SLOAD` and `SSTORE` operations on slots within that page are treated as warm. The trie then commits `{page_index, page}` pairs. \n\nStandard EVM execution semantics and backward compatibility with existing gas pricing are preserved. Common storage patterns in Solidity naturally benefit from page warming. For example, mappings to structs benefit because a struct’s internal fields occupy contiguous storage slots once the mapping entry is resolved. This preserves existing smart contract development best practices while incentivizing contiguous storage grouping.","specification":"We introduce the following notation:   \n\n- Storage `slots` are 32 byte values.\n- EVM `words` are 32 byte values as defined in the Ethereum Yellow Paper.\n- EVM `pages` are 4096 bytes and composed of 128 words. \n\nFor a given slot, we determine its grouping by stripping the lower 7 bits of the key. This stratifies the key space and lets us define a page as a contiguous vector of 128 EVM words. Each key maps to `(page_index, offset_within_page)`, where a page stores 128 consecutive EVM words. The mapping functions from `slot` to `page` information are defined as follows:\n\n- `page_index(slot) = slot >> 7`\n- `offset(slot) = slot & 0x7F`\n\n### Page Commitment Function\n\nBLAKE3 supports inclusion proofs at the 1024-byte leaf granularity since internally it constructs a Merkle Tree over 1024-byte chunks. However, efficient single-word inclusion proofs are not natively supported. \n\nTo recover this property, we define a commitment function that computes a 32-byte Merkle root over a 4096-byte page by constructing an induced subtree using BLAKE3. This commitment function, referred to as the Induced Subtree Merkle Commit (ISMC), commits only to the occupied state.\n\nLet `P` be a 4096-byte page. Then note the following:\n\n1. The BLAKE3 compression function operates on 64-byte blocks.\n2. The page is partitioned into 64 pair-leaves where each leaf consists of two 32-byte words. Pair-leaf occupancy is tracked via a 64-bit bitmap, while a 128-bit slot bitmap is used for the commitment.\n3. Internal nodes form an induced subtree determined by occupancy of pair-leaves, topologically bypassing empty branches entirely. Singletons are carried up the tree without requiring empty hash operations.\n4. The commit is done in two phases:\n    1. **Merge Phase**: A bottom-up reduction of the occupied pair-leaves. This captures the payload of all active data in the page.\n    2. **Seal Phase**: The resulting subtree root is hashed alongside the 128-bit slot bitmap. This uniquely binds the data to the exact geometric positions and prevents spatial collisions.\n5. Execution and proof size scale with occupancy. The **merge phase** costs exactly `k - 1` compressions, where `k` is the number of active pairs.\n\nThe resulting root is the **page commitment**. A pseudocode implementation of this commitment is shown below. A full breakdown of this commitment function can be found in the paper titled Merkle Commitments via Induced Subtrees.\n\n**Reference implementation**\n\n```text\nFunction ISMC_Commit(page, slot_bitmap):\n    // Inputs:\n    // page: 4096-byte array (64 pairs of 64 bytes)\n    // slot_bitmap: 128-bit integer representing exact 32-byte word occupancy\n    \n    // Assume non-empty page\n    Assert slot_bitmap != 0\n\n    // Convert 128-bit slot bitmap to 64-bit pair bitmap (1 if either word in pair is active)\n    pair_bitmap = reduce_to_pair_bitmap(slot_bitmap)\n\n    // Domain-separated leaf IV. Derived once from the constant 32-byte\n    // domain string and used to compress every active pair-leaf to 32\n    // bytes, separating the leaf domain from the parent domain.\n    PAIR_LEAF_DOMAIN = \"ultra_merkle_pair_leaf_domain___\"   // 32 bytes\n    LEAF_IV = BLAKE3_compress(state=IV,\n                              block=PAIR_LEAF_DOMAIN || zeros(32),\n                              block_len=64,\n                              counter=0,\n                              flags=DERIVE_KEY_MATERIAL)\n    \n    // --- Phase 1: Data Merge Phase ---\n    active_nodes = []\n    \n    // Extract only occupied pair-leaves (Bypassing empty branches)\n    For i from 0 to 63:\n        If bit i is set in pair_bitmap:\n            pair_data = page[i * 64 : (i + 1) * 64]\n            // Reduce each 64-byte pair-leaf to 32 bytes via one bare\n            // compression with LEAF_IV and DERIVE_KEY_MATERIAL.\n            leaf_hash = BLAKE3_compress(LEAF_IV, pair_data,\n                                        block_len=64, counter=0,\n                                        flags=DERIVE_KEY_MATERIAL)\n            active_nodes.append({ index: i, value: leaf_hash })\n            \n    // Bottom-up reduction \n    For level from 0 to 5:\n        next_level_nodes = []\n        i = 0\n        \n        While i < length(active_nodes):\n            current_node = active_nodes[i]\n            \n            // Check if a right sibling exists in the active nodes\n            If i + 1 < length(active_nodes):\n                next_node = active_nodes[i + 1]\n                \n                // Nodes are siblings if they share the same parent at the next level\n                If (current_node.index >> (level + 1)) == (next_node.index >> (level + 1)):\n                    // Hash the two 32-byte children into a new 32-byte\n                    // parent using ONE bare compression (NOT the full\n                    // BLAKE3_Hash pipeline) with CHUNK_START|CHUNK_END.\n                    parent_value = BLAKE3_compress(IV,\n                                                   current_node.value || next_node.value,\n                                                   block_len=64, counter=0,\n                                                   flags=CHUNK_START | CHUNK_END)\n                    next_level_nodes.append({ index: current_node.index, value: parent_value })\n                    i += 2\n                    continue\n            \n            // Singleton Case: Carry up without hashing\n            next_level_nodes.append(current_node)\n            i += 1\n            \n        active_nodes = next_level_nodes\n        \n        // Early exit: The tree is fully reduced to a single root\n        If length(active_nodes) == 1:\n            break\n            \n    subtree_root = active_nodes[0].value\n    \n    // --- Phase 2: Structural Seal Phase ---\n    // Uniquely bind the subtree root to the exact geometric layout\n    slot_bitmap_le_16B = to_little_endian_bytes(slot_bitmap, 16)\n    seal_payload = concatenate(slot_bitmap_le_16B, subtree_root)  // 16 bytes + 32 bytes\n    page_commitment = BLAKE3_Hash(seal_payload)  // unkeyed\n    \n    Return page_commitment\n```\n\n### Inclusion Proofs\n\nIntuitively, the ISMC can be viewed as an embedded Merkle tree that proves the exact state of a 4096-byte page. Given a page commitment, we can efficiently prove the inclusion of any specific 32-byte word within that page.\n\nTo construct an inclusion proof for a particular word, the verifier must be able to recompute the page commitment using the merge schedule. Therefore, an ISMC inclusion proof consists of exactly two components:\n\n1. The 128-bit slot bitmap: Required to deterministically reconstruct the tree's geometry and prove the exact spatial index of the word.\n2. The sibling hashes: The minimal set of sibling hashes required to route from the target word to the subtree root under the induced merge schedule.\n\nThe inclusion proof size scales strictly with the occupancy of the page rather than its physical size. Let `k` be the number of active leaves; the maximum tree depth is 6. The number of sibling hashes along a leaf's path is at most `min(k - 1, 6)`. Therefore, the worst-case inclusion proof size for a word is strictly bounded by `min(k - 1, 6) * 32 bytes + 16 bytes`.\n\n### Leaves of Merkle-Patricia Trie\n\nThe Merkle Patricia Trie commits to `{page_index_i: page_commit(page_i)}` pairs where `page_commit(page_i)` is a 32-byte commitment to the contents of a page.  \n\nThis trie has the following modifications:\n\n1. **Hash Function**: Keccak.\n2. **Leaf values**: For each `page_index`, the corresponding leaf value is the **RLP-string framing** of the 32-byte page commitment, i.e. `RLP_encode_string(page_commit(page_i))` = `0xa0 || page_commit(page_i)` (33 bytes). The MPT leaf node then RLP-encodes this byte string when constructing the leaf RLP, matching how a standard MPT storage leaf nests an RLP-encoded `U256` value.\n3. **Leaf placement**: Each `page_index` uniquely determines a path from the MPT root to its leaf. This path is computed exactly as in a standard MPT, using the `page_index` as the key.\n4. **Trie structure**: The MPT structure is otherwise unchanged: branch, extension, and leaf nodes follow the standard MPT rules.\n5. **On-demand computation**: The value of each storage leaf is exactly 32 bytes, so `page_commit(page)` can be recomputed from the page contents whenever needed. No additional storage layout changes are required.\n6. **Merkle Proofs**: Merkle proofs for page commitments are unchanged from a standard MPT. Such a proof only proves that a particular page has been committed.\n\nAs a result, the inclusion proof for any individual word consists of two components: the inclusion proof of the word within its page commitment, and the inclusion proof of the page commitment within the MPT. The total proof size is the sum of these components.","rationale":"Contracts that allocate storage in contiguous chunks aligned to page boundaries are economically optimal, benefiting from lower gas costs and highly efficient inclusion proofs. Because the page commitment execution and proof size scale with the number of active pairs, the architecture natively aligns with the EVM’s storage patterns:\n\n1. **Random Sparse State**: The probability of two randomly hashed keys colliding within the same page is approximately 1 in 2<sup>249</sup>. A standard mapping slot is therefore likely to be the only populated element in its page, and its page commitment can be reconstructed with zero sibling hashes. Proof sizes for current mapping-based states remain stable outside of the bitmap overhead.\n2. **Contiguous State**: When a page contains a densely packed, contiguous set of words, a multi-word inclusion requires only the sibling hashes along the outer boundary of the data block. This amortizes the proof size per word, making contiguous multi-word inclusion proofs strictly more efficient, again outside of the bitmap overhead.\n3. **Amortized Sparse Reads**: When a page is sparsely populated at random, single-word inclusion proofs incur a small, bounded overhead within the page. Under a uniform distribution, the expected proof size grows logarithmically as `O(log k)`.\n\nBLAKE3 was chosen for its hashing speed, amenability to zk proof generation, and the BAO construction. This allows for faster merklization in both the standard case and proof generation. Applying BLAKE3 to the entire Merkle tree also enables bytecode inclusion proofs via the BAO construction.","backwardsCompatibility":"The EVM semantics remain unchanged under this update. The only modification is the introduction of page-level access warming to the gas schedule.\n\nExisting contracts that access consecutive storage slots will automatically observe reduced gas costs. This update does not require developers to adopt bespoke storage architectures or non-standard upgrade patterns to realize benefits:\n\n- **No Penalty for Dispersed State:** Existing hashed patterns and dispersed state layouts are not penalized; they simply maintain their historical baseline cost. For example, reading a standard `mapping(address => Struct)` will execute the initial mapping lookup at the baseline gas cost. However, all subsequent reads to that specific user's struct fields will benefit from the new warm-page gas discounts.\n- **Solidity:** Standard Solidity features natively allocate data in consecutive slots. Common primitives such as structs, densely packed state variables, and arrays will seamlessly inherit these gas optimizations without developer intervention.\n- **Unaligned Legacy Layouts:** Conventional upgradeable contract patterns may not perfectly align with 128-word page boundaries. While developers of future contracts may choose to hyper-optimize layouts to capture maximal page-warming discounts, failing to do so incurs no penalty. Misaligned or \"cold\" reads across page boundaries simply execute at the standard baseline cost.\n\nThe only class of contracts impacted by this update are those that explicitly rely on hardcoded opcode gas costs associated with storage accesses. All other contracts remain functionally unchanged.\n\n**EIP-2930 Access Lists**\n\nThe transaction payload format for [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access lists is backwards compatible.  If an access list contains multiple keys that map to the same 128-word page then the cold-access cost is charged only once and applies the warm-access cost to all subsequent keys within that boundary.\n\n**RPC Modifications**\n\n1. The RPC method `eth_getProof` must be updated to return the modified proof structure.\n2. The RPC method `eth_createAccessList` must be updated as above to return an updated `gasUsed` estimate.\n3. `eth_estimateGas`, `eth_call`, and `trace_call` should reflect the updated gas schedule.","securityConsiderations":"### Page Index Space Size\n\nThe current Merkle Patricia Trie uses a 2<sup>256</sup> key space, with keys hashed to determine leaf placement so that the tree remains balanced. Under the paged state scheme, the effective key space is reduced to 2<sup>249</sup>. This reduction does not affect the tree structure: the hash still forces a uniform distribution, so the MPT topology and security properties are preserved.\n\n### Page Size\n\nEach leaf of the current Merkle Patricia Trie corresponds to a single EVM storage slot. We considered 2, 4, 8, 16, 32, 64, and 128 words per page. Each of these page sizes is at most 4096 bytes of raw slot data, so they fit within an I/O page (ignoring any node metadata overhead).\n\nA storage page may span multiple I/O pages. We can estimate the probability that a storage page crosses an I/O page boundary using uniform random offsets (assuming the storage page is completely full):\n\n| Page Size | Probability of crossing I/O page | Worst Case Block Slowdown |\n| --------- | -------------------------------- | ------------------------- |\n| 1 word    | ~1%                              | 1.01x                     |\n| 2 words   | ~1%                              | 1.01x                     |\n| 4 words   | ~3%                              | 1.03x                     |\n| 8 words   | ~6%                              | 1.06x                     |\n| 16 words  | ~12%                             | 1.12x                     |\n| 32 words  | ~25%                             | 1.25x                     |\n| 64 words  | ~50%                             | 1.5x                      |\n| 128 words | ~100%                            | 2.00x                     |\n\nWhen a storage page straddles multiple I/O pages, a single EVM `SSTORE`/`SLOAD` can underprice storage operations due to read amplification. In practice, recent storage is cached, so these effects are usually mitigated.\n\nTo reason about the worst case, consider a single block of EVM execution in which an attacker controls all storage writes. If storage pages cross I/O boundaries, `SLOAD` operations for misaligned portions could be effectively 2× slower. To achieve this, an attacker would need to allocate an entire page and then wait until the relevant pages are no longer cached.","subsystems":["execution","evm","db","rpc","economics"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0007","numberShort":"7","title":"Extension Opcodes","description":"Add a reserved opcode for implementation-defined extension opcodes","status":"Review","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-7-extension-opcodes/387","forumTopicId":387,"created":"2026-01-28","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-7.md","contentHash":"b8ecb3dd604ffeec665faf7f83a9220c8f10cafc8bd909fc8f5a82b41ac545cb","abstract":"This MIP proposes a new opcode `EXTENSION` (`0xAE`) that can be used to extend the Monad VM with new opcode-level features, while minimizing the risk of collision with future changes to the Ethereum execution layer. It defines the encoding scheme for extended instructions, including the extension selector and two styles for encoding immediate arguments, and aligns with [EIP-8163](https://eips.ethereum.org/EIPS/eip-8163), which reserves the same opcode on Ethereum L1.","motivation":"At present, bytecode execution in the Monad VM is fully Ethereum-compatible: all Ethereum opcodes are supported, and there are no additional opcodes implemented by Monad that are not present in Ethereum. However, in the future, new features will be proposed for Monad that warrant the addition of new opcodes. For example, an early version of [MIP-4](./MIP-4.md) specified the addition of an opcode to inspect the state of Monad's reserve balance mechanism. While that MIP rejected the opcode-based design in favour of a precompile, the design decisions discussed in relation to that early version prompted this MIP.\n\nEIP-8163 reserves the `EXTENSION` (`0xAE`) opcode on Ethereum L1 specifically to enable non-L1 EVM chains to safely experiment with extensions without risking future incompatibility. This MIP adopts that reservation for Monad.","specification":"### Extended Opcode Encoding\n\nThe `EXTENSION` opcode (`0xAE`) MUST be immediately followed by a 1-byte extension selector. The extension selector MUST NOT be `0x5B` (`JUMPDEST`) or in the range `0x60`-`0x7F` (`PUSH1`-`PUSH32`). An extension selector in this excluded range MUST cause an exceptional halt, consuming all remaining gas.\nAn `EXTENSION` byte at the end of code has no selector and MUST cause an exceptional halt, consuming all remaining gas.\n\nDefine **extended opcode** as the 2-byte sequence `0xAE XX`.\n\nUntil a future MIP assigns meaning to a given selector, executing any extended opcode MUST behave as if `INVALID` (`0xFE`) had been executed.\n\n### Argument Encoding\n\nIf a particular extension requires immediate arguments, future MIPs MUST use one of the two encoding styles:\n\n#### Restricted-Range Immediates\n\nSimilar to [EIP-8024](https://eips.ethereum.org/EIPS/eip-8024), argument bytes follow the extended opcode directly:\n\n```\n0xAE XX a1 a2 ...\n```\n\nEach argument byte MUST NOT be `0x5B` or in the range `0x60`-`0x7F`. The number of argument bytes is fixed per extension selector and MUST be specified by the MIP that defines the extension.\n\n#### PUSH-Prefix Immediates\n\nArgument bytes are framed by a `PUSHx` byte (`0x60`-`0x7F`) that follows the extended opcode:\n\n```\n0xAE XX PUSHx b1 b2 ... bn\n```\n\nThe `PUSHx` byte determines the argument length (n = opcode − `0x5F`). The full byte range `0x00`-`0xFF` is available.\n\nIn either encoding style, an extended opcode followed by incorrectly encoded bytes MUST behave as if `INVALID` (`0xFE`) had been executed.","rationale":"Several alternative designs were considered:\n\n### No Extension\n\nIn this design, new implementation-specific opcodes are simply allocated to unused bytes in the existing EVM opcode space. This design is simple from an implementation perspective, but risks collision with future Ethereum upgrades. If such a collision were to occur, it is likely that a more complex resolution would be required, or that Monad would have to accept a permanent break of compatibility with Ethereum. A single reserved extension opcode reduces this risk substantially.\n\n### Selector Encoding like `PUSH1`\n\nThe currently accepted approach to introducing multibyte opcodes is to do so in a `JUMPDEST` analysis preserving way, following the precedent of [EIP-8024](https://eips.ethereum.org/EIPS/eip-8024). `JUMPDEST` analysis is unaffected by `EXTENSION` and by either of its argument encodings.\n\nAn earlier version of this MIP proposed that `EXTENSION` carry a single-byte immediate operand (structurally identical to `PUSH1`), forming a two-byte `0xAEXX` instruction and skipping over the `XX` byte during `JUMPDEST` analysis. This conflicts with `JUMPDEST` analysis preservation and compromises code portability across chains.\n\n### Stack-based\n\nRather than encoding extension arguments as immediate data, an alternative would be to pop the implementation-specific opcode from the stack. Doing so has the advantage of simpler upstream compatibility. The main downside of this approach is performance: popping a 32-byte word from the stack, interpreting it as an opcode, and dispatching on it would exit the hot path of typical interpreter designs. However, it is worth noting that the Monad VM's native-code compiler could trivially inline the presumed common pattern of `PUSH1 0xXX; EXTENSION` with zero overhead.\n\nAdditionally, stack-based dispatch would allow opcode selection to come from runtime data or computation, preventing static analysis of stack contents and extension behavior, and hindering EVM execution optimization.\n\n### Gas Costs\n\nNo gas costs for individual extension opcodes are proposed by this MIP. Any invalid or undefined extension opcode should cost the same as executing `INVALID` (consume all gas and revert).\n\n### Choice of Immediates Encoding Style\n\nThis MIP leaves the choice of immediates encoding style to the particular selector. Restricted-range immediates are compact and suitable for short arguments that do not require the full byte range. PUSH-prefix immediates allow arbitrary byte values at the cost of an extra framing byte.\n\n### Precompiles\n\nSome features could potentially be implemented either as new opcodes or as precompiles: adding precompiles is less risky from a collision perspective, but calling precompiles incurs additional overhead for ABI compatibility that may not be viable for all features.","backwardsCompatibility":"The opcode `0xAE` is currently invalid in both Ethereum and Monad. Since `JUMPDEST` analysis is unaffected by `EXTENSION`, no existing code behavior changes.","securityConsiderations":"Because `JUMPDEST` analysis is unaffected by `EXTENSION`, the risks associated with divergent jump analysis between Monad and Ethereum are mitigated. On both chains, `0xAE` followed by `0x5B` results in the `0x5B` remaining a valid jump destination.","subsystems":["execution","evm"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0006","numberShort":"6","title":"MONAD_NINE Network Upgrade","description":"Meta MIP for the `MONAD_NINE` network upgrade","status":"Final","type":"Meta","category":"Hardfork","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-6-monad-nine-revision-meta/381","forumTopicId":381,"created":"2026-01-22","requires":["0003","0004","0005"],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-6.md","contentHash":"ce62c73b3524ea3c97ae325794be5170ea1e35950ae9abd8e23ba7dcc8db6d98","abstract":"This Meta MIP specifies the changes included in the `MONAD_NINE` revision of the\nMonad network.","specification":"### Included MIPs\n\n- [MIP-3: Linear Memory](./MIP-3.md)\n- [MIP-4: Reserve Balance Introspection](./MIP-4.md)\n- [MIP-5: Fusaka EIP Activation](./MIP-5.md)\n\n### Activation\n\n| Network | Activation Timestamp |\n|---------|---------------------|\n| Monad Testnet | `1773153000` (Mar 10, 2026, 14:30 UTC) |\n| Monad Mainnet | `1773930600` (Mar 19, 2026, 14:30 UTC) |","rationale":"This upgrade includes:\n\n- [MIP-3: Linear Memory](./MIP-3.md)\n- [MIP-4: Reserve Balance Introspection](./MIP-4.md)\n- [MIP-5: Fusaka EIP Activation](./MIP-5.md)","securityConsiderations":"Deferred to the individual MIPs included.","hardforkName":"MONAD_NINE","bundledMIPs":["0003","0004","0005"],"subsystems":["consensus","evm"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0005","numberShort":"5","title":"Fusaka EIP Activation","description":"Activate EIP-7823, EIP-7883, and EIP-7939 from Ethereum's Fusaka upgrade.","status":"Final","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-5-fusaka-eip-activation/373","forumTopicId":373,"created":"2026-01-19","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-5.md","contentHash":"6c821a57e5620b10c535e1b05338291dc9b4a6cb0ab851a36a6612d401d2eaeb","abstract":"Activate three EIPs from Ethereum's Fusaka upgrade ([EIP-7607](https://eips.ethereum.org/EIPS/eip-7607)): EIP-7823, EIP-7883, and EIP-7939.","specification":"This MIP activates the following EIPs:\n\n- [EIP-7823: Set upper bounds for MODEXP](https://eips.ethereum.org/EIPS/eip-7823)\n- [EIP-7883: ModExp Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-7883)\n- [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939)\n\n### Excluded EIPs\n\nThe following EIPs are already activated by Monad:\n\n- [EIP-7951: Precompile for secp256r1 Curve Support](https://eips.ethereum.org/EIPS/eip-7951)\n\nThe following EIPs pertain to Ethereum consensus layer and data availability, so\nare not relevant to Monad:\n\n- [EIP-7594: PeerDAS - Peer Data Availability Sampling](https://eips.ethereum.org/EIPS/eip-7594)\n- [EIP-7917: Deterministic proposer lookahead](https://eips.ethereum.org/EIPS/eip-7917)\n- [EIP-7918: Blob base fee bounded by execution cost](https://eips.ethereum.org/EIPS/eip-7918)\n\nThe following EIPs set parameters for which Monad makes different choices per [the Monad specification](../assets/MIP-4/monad-initial-spec-proposal.pdf) (30M\ntransaction gas limit, and 2MB block size), so are not included:\n\n- [EIP-7825: Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825)\n- [EIP-7934: RLP Execution Block Size Limit](https://eips.ethereum.org/EIPS/eip-7934)","rationale":"Deferred to the individual EIPs included.","securityConsiderations":"Deferred to the individual EIPs included.","subsystems":["execution","consensus","evm","economics","networking"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0004","numberShort":"4","title":"Reserve Balance Introspection","description":"Add reserve balance precompile to query reserve balance violation state during transaction execution","status":"Final","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-4-reserve-balance-introspection/363","forumTopicId":363,"created":"2026-01-08","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-4.md","contentHash":"d95a3c7bad8a3f00d8e91d209095b0c2a153bdd74d44cc198ed776b3ef1da6e3","abstract":"Add a new precompile at address `0x1001` with a method `dippedIntoReserve` that returns whether the current execution state is in reserve balance violation.\nThis enables contracts to detect and recover from temporary reserve violations before transaction completion.","motivation":"Monad's reserve balance mechanism (per the initial spec, Algorithm 3) reverts transactions that leave any touched account below its reserve threshold at execution end.\nHowever, the check is performed post-execution: contracts have no way to know during execution whether they are in a violation state.\n\nThe reserve balance precompile allows contracts to query violation state mid-execution and adjust behavior accordingly—either by restoring balances, taking an alternative code path, or reverting early with a meaningful error.","specification":"| Name                           | Value        |\n| ------------------------------ | ------------ |\n| `GAS_DIPPED_INTO_RESERVE`      | `100`        |\n| `SELECTOR_DIPPED_INTO_RESERVE` | `0x3a61584e` |\n\nThe new precompile has address `0x1001`, and satisfies the following Solidity interface:\n\n```solidity\ninterface IReserveBalance {\n    function dippedIntoReserve() external returns (bool);\n}\n```\n\nThe Solidity selector for `dippedIntoReserve` is `SELECTOR_DIPPED_INTO_RESERVE (0x3a61584e)`.\n\nThe precompile must be invoked via `CALL`.\nInvocations via `STATICCALL`, `DELEGATECALL`, or `CALLCODE` must revert.\n\nInvocations via [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) delegations targeting the precompile address must revert.\n\nCalldata must consist of exactly the 4-byte `SELECTOR_DIPPED_INTO_RESERVE`.\nAny other calldata is invalid: if the calldata is shorter than 4 bytes or not equal to `SELECTOR_DIPPED_INTO_RESERVE` the precompile must revert with the error message \"method not supported\".\nIf extra calldata is appended beyond the selector, the precompile must revert with the error message \"input is invalid\".\n\nThe method `dippedIntoReserve()` is not payable and must revert with the error message \"value is nonzero\" when called with a nonzero value.\n\nIn case more than one revert condition is present, the revert error message must correspond with the conditions evaluated in the following order:\n\n1. is not invoked via `CALL`\n2. `gas < GAS_DIPPED_INTO_RESERVE`\n3. `len(calldata) < 4`\n4. `calldata[:4] != SELECTOR_DIPPED_INTO_RESERVE`\n5. `value > 0`\n6. `len(calldata) > 4`\n\nReverts consume all gas provided to the call frame.\n\nOn success, the method `dippedIntoReserve()` evaluates the condition that `DippedIntoReserve` (Algorithm 3 of the initial spec) would return, substituting the current state for the post-execution state.\nThe check considers all accounts touched in the transaction, regardless of call depth.\nThe call consumes `GAS_DIPPED_INTO_RESERVE` gas.\n\nThe return value is ABI-encoded as a Solidity `bool`—i.e., a 32-byte word in returndata.","rationale":"### Address\n\nPrecompile address chosen follows the existing staking precompile at `0x1000`.\n\n### Gas cost\n\nGas cost of `dippedIntoReserve()` is equivalent to the cost of one `tload`. \nImplementations should incrementally update their state to track violations of the reserve balance constraints, rather than iterating over the set of modified accounts in the current transaction on each call to the precompile.\nThen `dippedIntoReserve()` should be a check of this violation state and therefore should have similar resource usage to a load from transient storage.\n\nThis was not benchmarked, but aligns with benchmarking and costing for the staking precompile's gas costs, which were calculated from the number of database loads and stores, events, and transfers.\nThe reserve balance check produces no events or transfers and is analagous to a load from transient storage instead of database storage and thus uses the cost for a `tload`.\n\nReverts consuming all gas is consistent with the behavior of Ethereum precompiles, which do not return remaining gas on failure, as opposed to Solidity functions which return unused gas on revert.\n\n### Return value encoding\n\nThe return value is encoded consistently with standard Solidity ABI encoding.\nThis means callers can invoke the precompile via a normal contract call.\n\n### Precompile vs. opcode\n\nA previous design proposed adding a new opcode with similar semantics.\nSince this introspection feature is intended for direct use by smart contract developers (e.g., in bundler entrypoint contracts), a precompile was chosen because it can be called immediately without requiring compiler or toolchain updates.\n\n### Compatibility with other Monad precompiles\n\nThe semantics described above (strict calldata validation, ABI-encoded return values, rejecting calls with value, conventions around `*CALL` opcodes & EIP-7702, revert messages, and all-gas-consuming reverts) are chosen for explicit consistency with the existing Monad staking precompile.\n\nThe interface method `dippedIntoReserve()` is intentionally not declared `view`, so that a Solidity call site compiles to `CALL` rather than `STATICCALL`.","backwardsCompatibility":"This proposal adds a new precompile and does not modify existing behavior.\nContracts that do not use the precompile are unaffected.","securityConsiderations":"The precompile is read-only and exposes information that is already implicitly available (the transaction will revert if violation persists).\nNo new attack surface is introduced.","subsystems":["execution","evm","staking","db"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0003","numberShort":"3","title":"Linear Memory","description":"Redefine memory expansion cost to be linear and enforce an explicit maximum memory usage per transaction","status":"Final","type":"Standards Track","category":"Core","authors":[{"name":"Category Labs","github":"category-labs"}],"discussionsTo":"https://forum.monad.xyz/t/mip-3-linear-evm-memory-cost/362","forumTopicId":362,"created":"2025-12-10","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-3.md","contentHash":"410587ff8961576d71fc4729e44d551dcdf11b1070d2eb0fd9d7aee62576eb2c","abstract":"Redefine memory expansion cost to be linear and enforce an explicit maximum memory usage per transaction.","motivation":"Currently, EVM memory usage is bounded by a quadratic expansion cost and the 63/64 rule. The theoretical memory limit of a transaction is at least 26 MB. Historical transaction analysis shows that average memory usage is ~2 KB and maximum observed memory usage is ~2 MB. \n\nBenchmarks also indicate that the current memory expansion formula overcharges for actual resource usage.\n\nThis update aligns cost with actual resource consumption. It makes memory usage more predictable, particularly for contracts that rely on large memory allocations.","specification":"Memory expansion cost is redefined as:\n\n```python\nmemory_size_words = (memory_byte_size + 31) // 32\nmemory_cost = memory_size_words // 2\n```\n\nThe max memory usage is capped at 8 MB.  Memory allocation is bounded across call contexts with the following rule: \n\n1. Let `k` be the memory used by the current call, and `j` the memory used by parent calls.\n2. The remaining memory available to a child call is: \n\n```python\n remaining_memory = 8 * 1024 * 1024 - j - k\n```\n3. Once a call returns, the memory is returned to the pool.\n4. If a call exceeds the remaining memory limit, it halts exceptionally, consuming all gas remaining in that call frame.","rationale":"An alternative design would revert the call frame on exceeding the memory limit and return its remaining gas. Halting exceptionally follows the convention that errors in the executing code consume all gas. It also matches the behavior before this MIP, where expanding memory beyond what the remaining gas pays for is an out-of-gas exceptional halt.","backwardsCompatibility":"This proposal is highly compatible with existing contracts. Almost all standard EVM operations remain valid and ERC-4337 contracts continue to function correctly, as child call memory is released upon completion. Replay testing of historical Ethereum transactions will be used to quantify compatibility.\n\nHowever, contracts that allocate more than 8 MB of memory will now halt exceptionally.","securityConsiderations":"The cost to expand memory to the 8MB ceiling is 131,072 gas. The result is that it is cheaper to expand memory than current costs. Potentially concurrency limits for RPC nodes should be adjusted to prevent OOM issue.","subsystems":["execution","evm"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0002","numberShort":"2","title":"Increase Contract Code Size Limit","description":"Increase the maximum contract code size limit to 128 KB and initcode size limit to 256 KB","status":"Final","type":"Standards Track","category":"Core","authors":[{"name":"QEDK","github":"qedk"},{"name":"et al."}],"created":"2026-02-26","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-2.md","contentHash":"b4c8954b3b1cc488561f83f5d05ece066ccac2d329fe02d8b9aed936ada0aebe","abstract":"Increase the maximum contract code size (`MAX_CODE_SIZE`) from 24,576 bytes (24 KB) to 131,072 bytes (128 KB), and correspondingly increase the maximum initcode size (`MAX_INITCODE_SIZE`) from 49,152 bytes (48 KB) to 262,144 bytes (256 KB).","motivation":"[EIP-170](https://eips.ethereum.org/EIPS/eip-170) introduced a contract code size limit of 24,576 bytes to mitigate a potential denial-of-service vector: calling a contract incurs O(n) cost in disk reads, VM preprocessing, and Merkle proof generation relative to the contract's code size, none of which is directly compensated by gas. While this limit was reasonable given Ethereum's constraints at the time of the Spurious Dragon hard fork, it has become a significant obstacle for developers building complex applications.\n\nModern smart contract development frequently encounters the 24 KB ceiling. Complex DeFi protocols, on-chain order books, sophisticated governance systems, and contracts with rich error reporting routinely exceed this limit, forcing developers to adopt workarounds such as proxy patterns (e.g. [EIP-2535](https://eips.ethereum.org/EIPS/eip-2535)), library-based architectures using `DELEGATECALL`, or splitting application logic across multiple contracts. These workarounds increase deployment complexity, introduce additional gas overhead for cross-contract calls, and expand the attack surface through the use of proxies.\n\nMonad's architecture fundamentally changes the resource cost calculus that motivated the original limit. Monad's custom database (MonadDB) is optimized for fast state access from SSD, and Monad's native-code JIT compiler amortizes the cost of bytecode preprocessing across repeated contract invocations. Together, these optimizations ensure that loading and executing larger contracts does not impose a disproportionate burden on validators relative to gas fees paid. Additionally, Monad enforces a per-transaction gas limit of 30M gas, which inherently constrains the resource impact of any single contract interaction.","specification":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.html) and [RFC 8174](https://www.ietf.org/rfc/rfc8174.html).\n\n### Parameters\n\n| Constant | Previous Value | New Value |\n|---|---|---|\n| `MAX_CODE_SIZE` | 24,576 (0x6000) | 131,072 (0x20000) |\n| `MAX_INITCODE_SIZE` | 49,152 (0xC000) | 262,144 (0x40000) |\n\n### Contract Creation\n\nAs defined in [EIP-170](https://eips.ethereum.org/EIPS/eip-170), if contract creation initialization returns data with length of more than `MAX_CODE_SIZE` bytes, contract creation MUST fail with an out-of-gas error. This applies to all contract creation contexts: top-level creation transactions, `CREATE` (0xf0), and `CREATE2` (0xf5).\n\n### Initcode Size\n\nIf the length of initcode exceeds `MAX_INITCODE_SIZE`, the transaction or instruction MUST be treated as invalid, consistent with [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860). For creation transactions, this means the transaction is invalid. For `CREATE` and `CREATE2` instructions, execution MUST fail with an out-of-gas error.\n\nThe initcode cost defined by EIP-3860 remains unchanged:\n\n```python\nINITCODE_WORD_COST = 2\ninitcode_cost = INITCODE_WORD_COST * ceil(len(initcode) / 32)\n```\n\n### Code Deployment Cost\n\nThe per-byte code deposit cost of 200 gas per byte, as originally defined in the Ethereum Yellow Paper, remains unchanged.","rationale":"### Choice of 128 KB\n\nThe new limit of 128 KB (131,072 bytes) represents a ~5.3x increase over the Ethereum default. This value was chosen to provide substantial headroom for complex contracts without introducing unbounded resource consumption. At the deployment cost of 200 gas per byte, deploying a maximum-size contract requires approximately 26.2M gas for the code deposit alone, which fits within Monad's 30M per-transaction gas limit while leaving room for initialization logic.\n\nSome alternatives were considered:\n\n- **64 KB**: Proposed for Ethereum in [EIP-7830](https://github.com/ethereum/EIPs/blob/d434180a40093c8ece93db9d98c7963a3cfdddf9/EIPS/eip-7830.md) (for EOF contracts)\n- **64 KB and gas metering**: Proposed for Ethereum in [EIP-7907](https://github.com/ethereum/EIPs/blob/d434180a40093c8ece93db9d98c7963a3cfdddf9/EIPS/eip-7907.md) (with metering for excess code loading)\n\nMonad's optimized storage and execution layers permit a more aggressive limit. 128 KB was selected as a practical upper bound: large enough to accommodate the most complex foreseeable single-contract deployments, while small enough to remain well within the gas budget and to avoid requiring changes to storage or networking assumptions.\n\n### Initcode Limit\n\n`MAX_INITCODE_SIZE` is set to `2 * MAX_CODE_SIZE` (262,144 bytes), preserving the relationship established by [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860). Initcode may be larger than deployed code because it includes constructor logic and immutable variable encoding that is not retained in the final bytecode.\n\n### No Additional Gas Metering\n\nUnlike EIP-7907, this MIP does not introduce additional gas metering for loading large contract code (e.g. cold code access surcharges). Monad's storage architecture and JIT compilation make the marginal cost of loading larger code negligible relative to existing gas costs. Should future analysis indicate otherwise, additional metering can be introduced in a subsequent MIP.","backwardsCompatibility":"This change is backwards-compatible. All contracts valid under the previous 24,576-byte limit remain valid. Contracts between 24,576 and 131,072 bytes that could not previously be deployed on Monad can now be deployed successfully.\n\nContracts deployed on Ethereum or other EVM chains with code sizes exceeding 24,576 bytes (if any exist through non-standard means) can be redeployed on Monad without issue.","securityConsiderations":"Increasing the contract code size limit raises the maximum resource cost of operations that scale with code size, such as `EXTCODECOPY`, disk reads, and VM preprocessing. However, Monad mitigates these costs through:\n\n- **MonadDB**: Optimized SSD-based state storage reduces the marginal cost of reading larger contracts.\n- **JIT compilation**: Frequently-used contracts are compiled to native code, amortizing preprocessing costs.\n- **Per-transaction gas limit**: The 30M per-transaction gas cap bounds the total resource expenditure of any single transaction.\n\nRPC node operators should be aware that concurrent `eth_call` invocations involving large contracts may consume additional memory. Operators may adjust concurrency limits accordingly to avoid out-of-memory conditions.","subsystems":["execution","evm","staking","db","rpc","economics"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"},{"number":"0001","numberShort":"1","title":"MIP Purpose and Guidelines","description":"Guidelines and procedures for the Monad Improvement Proposal process","status":"Living","type":"Meta","category":"Process","authors":[{"name":"QEDK","github":"qedk"}],"discussionsTo":"https://forum.monad.xyz/t/mip-1-mip-purpose-and-guidelines","created":"2026-02-24","requires":[],"githubUrl":"https://github.com/monad-crypto/MIPs/blob/main/MIPs/MIP-1.md","contentHash":"6d057fa21305c1696717049315791eb6536188ae1dd51a46121aa2334177828f","subsystems":["consensus","evm","staking","db","rpc","economics","networking","execution"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-10-22T17:19:09+02:00"}],"count":18,"fetchedAt":"2026-11-04T01:19:10.258Z"}