{"mips":[{"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"}],"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":"9363746ba2e6e3673c84854ed6cbb1075bd59f803c367fe165dff58e04efb063","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-09-08T10:10:53+01: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":"70179a4d3ec329442e0e12b2d87dee5585bd7c807663fc63d76a6faa65b7bae1","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 (by convention a small JSON object) 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 writes to the address itself: because the writer is always the subject, a 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\"`. Every metadata entry is identified by a `metadataId` derived from its subject; the contract MUST authorize each write against the subject account (see [Authorization](#authorization)). The contract exposes a write method 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 and the only party authorized to write it.\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    /// 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\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\n    /// Set or replace a subject's entry. `data` is stored verbatim. See\n    /// Authorization, Write Preconditions, and Field Semantics.\n    function setMetadata(Subject calldata subject, string calldata data) external;\n\n    /// Read the entry's `data` for `subject`, or the empty string if none exists.\n    function getMetadata(Subject calldata subject) external view returns (string memory data);\n    /// True iff a metadata entry has been written for `subject` (its `data` is non-empty).\n    function hasMetadata(Subject calldata subject) external view returns (bool);\n}\n```\n\n### Authorization\n\nAuthority over a metadata entry is the subject account itself. A 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 it.\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 the subject is always the account, no ownership or admin model and no external contract call is needed to establish authority. 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` SHOULD be a UTF-8 JSON object whose keys are the topic's fields. A service defines its own schema and MAY include auxiliary values (evidence or document URIs, content hashes, signed attestations, or payloads defined by a later MRC) under its own keys; this MRC defines no schema and reserves no keys.\n- The registry MUST NOT validate or parse `data`, MUST NOT reject syntactically invalid JSON, and MUST return it byte-for-byte on read. JSON conformance 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` and `hasMetadata` MUST NOT call any external contract.\n- `getMetadata(subject)` MUST return the stored `data` for `subject`, or the empty string if no entry exists.\n- `hasMetadata(subject)` MUST return `true` iff a metadata entry has been written for `subject` (its stored `data` is non-empty).\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### 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: authority for every subject is the account 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 single JSON `data` object instead of typed struct members?** The set of facts differs by service and evolves over time. A JSON object keeps the registry stable across those changes with no ABI break or storage migration. The entries also have no first-class human-readable fields (name, logo) that would justify dedicated on-chain columns.\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 every value self-reported and never authoritative?** A `data` value is a self-reported statement, not a proof. A consumer must be free to grade it against any supporting evidence and against independent observation; the registry stating 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 single `data` field is an opaque, unparsed convention), no attester/recipient split (the subject is always the writer), and no resolver extension points. The \"why not just use EAS\" answer is that a consumer here depends only on the chain and the account's own statements, not on a schema registry or an attester graph, 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 authority in this MRC is proof-of-control of the subject address (not an issuer, attester, or name owner), and it carries no schema registry and no external identity resolution, which is what keeps the contract 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":"Every value is a self-attestation authorized only by control of the subject address, not a proof of the fact it asserts. 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-08-25T12:53:33+02:00"},{"number":"0013","numberShort":"13","title":"Validator Metadata Registry","description":"An on-chain registry standard for human-readable Monad validator metadata","status":"Final","type":"Standards Track","category":"MRC","authors":[{"name":"Dorde Mijovic <dorde@monad.foundation>","github":"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":"350ab9e63a66d19346a6d3948343d49f6639dcbf30172029adeeb232d118b4b4","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-08-25T13:06:26+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"}],"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":"3cc17cd38baed270a584ac80d310b2a06c0dd5dfed5547490ac591c2fb24ad10","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.","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.","subsystems":["execution","consensus","evm","staking"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-08-13T00:59:29+05:30"},{"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"}],"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":"a18f30e11e14e37be557a3856bae5aff4e5b1f66677a7b3a470e709d92a55964","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-06-15T20:52:52+01:00"},{"number":"0010","numberShort":"10","title":"Deterministic RaptorCast","description":"Introduces a canonical encoding scheme for RaptorCast that closes asymmetric liveness and equivocation attack surfaces and reduces dissemination latency","status":"Draft","type":"Standards Track","category":"Core","authors":[{"name":"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":"9e71ca135cbfcb2388fab07f88004d00d2a86323dcb0c2dcf0c6b60bd5714665","abstract":"This MIP proposes Deterministic RaptorCast (v1), a new broadcast mode for the RaptorCast dissemination layer that fixes the Raptor encoding via a publicly derivable seed and enforces per-round equivocation detection by recording the first valid (Merkle root, leader signature) pair seen for each round, referred to as an ``EncodingCommitment``.\n\nThe change delivers three benefits: (1) it can enable validators to vote directly on the Merkle root commitment upon receiving a verified chunk without decoding, saving one message delay on the critical path; (2) it closes asymmetric liveness attacks in which a Byzantine leader selectively withholds singleton Encoding Symbol Identifiers (ESIs) from targeted validators, causing reconstruction delays of several hundred milliseconds; and (3) it closes mixed-commitment equivocation, in which a leader constructs a single Merkle root from chunks encoding multiple distinct payloads. Note that benefit (3) is contingent on the consensus protocol being revised to vote on the Merkle root commitment rather than the decoded payload; without that change, the equivocation attack surface does not arise in the first place.","motivation":"MonadBFT uses RaptorCast to disseminate block proposals across the validator set. The current protocol (v0) (see [RaptorCast: Designing a Messaging Layer](https://www.category.xyz/blogs/raptorcast-designing-a-messaging-layer)) places no constraint on which ESI the leader assigns to which position in the Merkle tree, nor on which ESIs are delivered to which validator. This delivery freedom has three consequences that Deterministic RaptorCast addresses.\n\n- **Latency** A natural optimization is to overlap block propagation with consensus voting. Rather than waiting to fully reconstruct a block before casting a vote, a validator that receives a chunk and can verify it against the block's Merkle root could vote immediately—saving one message delay on the consensus critical path. Under v0, however, this is unsafe. Because the leader has freedom over ESI assignment, the same Merkle root can be consistent with multiple distinct payloads; a vote on the root therefore does not unambiguously certify a single block. A canonical (deterministic) encoding removes this ambiguity: since the Merkle root now commits to exactly one possible payload, opening the opportunity for validators to vote on the root as soon as they receive a verified chunk.\n\n- **Attack 1: Asymmetric liveness** Raptor codes decode most efficiently when the decoder receives at least some degree-1 ([singleton](https://www.category.xyz/blogs/raptorcast-designing-a-messaging-layer#2-encoding-system)) ESIs, since these are the entry point for the Belief Propagation peeling decoder. Without any singletons the decoder stalls immediately and must fall back to Gaussian elimination, which is substantially more expensive. A Byzantine leader can exploit v0's delivery freedom to selectively deprive targeted validators of singletons while supplying adversarially chosen high-degree repair chunks that maximize Gaussian elimination cost. The result is that targeted validators reconstruct upto several hundred milliseconds later (depending on the number of source symbols) than the rest of the set which is enough of a delay to starve them of votes in the current view. This gives the leader discretion to force future view failures.\n\n- **Attack 2: Mixed-commitment equivocation** Lowering the latency by voting on the merkle root of encoded chunks involves decode-re-encode consistency check to make sure encoding is valid. Without a fixed ESI-to-position mapping, the decode-re-encode consistency check cannot be correctly defined for rateless codes. A leader can construct a single Merkle root whose leaves are drawn from encodings of multiple distinct payloads. Every chunk passes Merkle verification, but validators collecting different subsets decode to different payloads. If validators vote on the root without decoding, they believe they are certifying the same block while certifying different ones.","specification":"### Seed Derivation\n\nThe canonical seed (also called encoding seed) is computed by the leader at proposal time. It consists of the hash of (round number, leader identity, and proposal time). The chunk header contains all necessary data for validators to deterministically recompute this seed, which they do upon receiving the chunk. Validators also check that the proposal time falls within an acceptable range of their local clock before accepting the seed as valid.\n\n### Encoding\n\nThe leader encodes payload `B` as:\n\n```\n(c_1, ..., c_n) = RaptorEnc(B, seed)\nR               = MerkleRoot(c_1, ..., c_n)\n```\n\nIn v0, chunks were organized into multiple Merkle trees of `32` chunks each, with each tree independently signed by the leader. v1 replaces this with a \nsingle global Merkle root `R` computed over all `n` chunks, providing a unified commitment that binds every chunk to a single canonical encoding of \nthe proposal. The Merkle tree depth is dynamically calculated based on the  number of chunks, up to a maximum depth of 15. Each Merkle proof is \n`20 × (merkle_tree_depth - 1)` bytes, giving a worst-case proof size of `280` bytes compared to `100` bytes in v0.\n\nPosition `i` MUST contain the chunk generated with `ESI = i` under seed. The pair `(R, σ)` where `σ = Sign(round, timestamp, R)` is the ``EncodingCommitment`` for this proposal.\n\n### Chunk Validation\n\nA validator accepts a v1 chunk packet `(round, timestamp, R, σ, i, π_i, c_i)`. A validator accepts the chunk if and only if all of the following holds:\n\n- **Timeliness** The packet's round is within the accepted round window, and the timestamp is within an acceptable range of the validator's local clock.\n\n- **Leader authenticity** `σ` is a valid leader signature over round, timestamp and `R` (`σ = Sign(round, timestamp, R)`).\n\n- **Encoding commitment consistency** `(R, σ)` matches the encoding commitment already recorded for this round. If no commitment is recorded yet, the validator records this one.\n\n- **Chunk integrity** The Merkle proof `π_i` verifies `c_i` at index `i` against `R`.\n\nChunks failing any check are silently dropped. A conflicting commitment (same round, different `R` or `σ`) is logged as equivocation evidence and the chunk carrying the conflicting commitment is dropped.\n\n### Encoding Verification (Decode-Re-encode Check)\n\nAfter collecting sufficient chunks and decoding payload B, a validator MUST verify:\n\n```\nMerkleRoot(RaptorEnc(B, seed)) == R\n```\n\nThe seed is deterministically recomputed from the values given in the chunk header. If the above check fails, the payload is rejected. This check is well-defined under v1 because seed fixes the ESI-to-position mapping: re-encoding `B` always produces the same chunk set that can be validated against the recorded ``EncodingCommitment``. Under v0 this check cannot be reliably applied.","rationale":"Deterministic RaptorCast requires minimal changes to the existing RaptorCast infrastructure. The encoding scheme, Merkle commitments, chunk delivery, and rebroadcast all remain unchanged. The additions are: a canonical seed derived from existing proposal metadata, per-round equivocation detection at each validator. These changes are sufficient to close both attack surfaces and to make voting on the Merkle root commitment safe before decoding, with no changes to the consensus voting protocol, the quorum certificate format, or the execution layer. The resulting protocol satisfies the following properties.\n\n### Properties\n\nDeterministic RaptorCast satisfies the following three properties.\n\n- **Availability** If a Quorum Certificate for a Merkle root `R` exists, every correct validator eventually terminates.\n\n- **Integrity** If the leader is correct, every correct validator that decodes a payload recovers exactly the payload originally dispersed by the leader.\n\n- **Consistency** Any two correct validators that decode a payload from chunks consistent with `R` recover the same payload. If no valid payload is consistent with `R`, every correct validator returns `⊥`.\n\nConsistency is what makes voting on `R` safe prior to decoding. A validator that casts a vote on `R` implicitly vouches for a unique payload: consistency guarantees that every validator that subsequently decodes arrives at the same one. Absent this property, two validators could vote on the same `R` while holding chunks that decode to different payloads, violating consensus safety.\n\nThe canonical seed also unlocks a post-consensus validity gate at the execution layer that was not previously applicable. Because the decode-re-encode check produces the same result at every correct validator, it can be applied after consensus has decided a block: any block whose decoded payload fails the check will be independently rejected by every correct validator, with no risk of a split. Under v0 this gate could not be employed without a canonical seed; different validators re-encoding the same payload could produce different roots and reach different conclusions about the same block.\n\nThis is enforced by two complementary mechanisms. First, per-round equivocation detection: upon receiving the first valid v1 chunk for a round, each validator records an ``EncodingCommitment`` — the pair (global_merkle_root, signature) — and rejects any subsequent chunk for that round with different fields. Second, the canonical seed fixes the ESI-to-position mapping, making the decode-re-encode check well-defined: re-encoding any decoded payload under the public seed always produces the same chunk set that can be validated against the same ``EncodingCommitment``.","securityConsiderations":"The attacks motivating this MIP, asymmetric liveness and mixed-commitment equivocation, are closed by the canonical seed and per-round equivocation detection as described in the Properties section.\n\n- **Seed grinding** The seed construction bounds a Byzantine leader's payload-grinding advantage to negligible within the available window: a leader cannot do better than random chance in selecting a seed that produces a favorable degree distribution within the time bucket.\n\n- **Round window** Chunks outside the accepted round 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.","subsystems":["execution","consensus","staking","economics","networking"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-06-15T20:52:52+01: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":"ec302bf2f868c05d96036a6ea6706ff552de38f46d4d00fabe08e08a269810e7","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-08-25T13:51:30+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"}],"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":"44af36f749a3d0c72b7a9692bec944303cfd3615112399cf7a14c364c24b5c55","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-08-24T18:43:01+02:00"},{"number":"0007","numberShort":"7","title":"Extension opcodes","description":"Add a reserved opcode for implementation-defined extension opcodes","status":"Draft","type":"Standards Track","category":"Core","authors":[{"name":"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":"2afb6729bb8e87a8aeca30abb6dc2ca58cf5d45a0cf5e350cf3868b621ddc4be","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.\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-06-15T20:52:52+01: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"}],"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":"62e65241ad73c3eade3a1eb19cda5ceff7385ae18b2ae12eaeb748bf72de5cdd","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)","hardforkName":"MONAD_NINE","bundledMIPs":["0003","0004","0005"],"subsystems":["consensus","evm"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-06-15T20:52:52+01: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"}],"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":"0fcea78038ce918535ad18c33cac1c57689f316950e0d9299deaf41c4a05fc05","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](https://category-labs.github.io/category-research/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)","subsystems":["execution","consensus","evm","economics","networking"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-06-15T20:52:52+01: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"}],"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":"56c8cba2548252d6543232c029e1c666bb53c03e7561b36f5a121c0a8ca26f53","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-07-14T09:12:18+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"}],"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":"78e19448864f62336f76efb16c600d23a30e16937fdb3c8d0c49e6e826af71cd","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.","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-07-28T15:20:30+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":"0fd841d8ce82f25757bca015e47d7c4d0cae530e2c0a7b124da75431ed652337","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-06-15T20:52:52+01: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":"da38e02ac979518960412071395bedee810292e33ceddbc446df5b1472d73922","subsystems":["consensus","evm","staking","db","rpc","economics","networking","execution"],"createdAt":"2026-06-15T20:52:52+01:00","updatedAt":"2026-08-02T21:58:49+02:00"}],"count":15,"fetchedAt":"2026-09-19T21:40:24.014Z"}