# Best Practices for Building High Performance Apps Source: https://docs.monad.xyz/developer-essentials/best-practices Learn best practices for building high performance apps on Monad ## Configure web hosting to keep costs under control * Vercel and Railway provide convenient serverless platforms for hosting your application, abstracting away the logistics of web hosting relative to using a cloud provider directly. You may end up paying a premium for the convenience, especially at higher volumes. * AWS and other cloud providers offer more flexibility and commodity pricing. * Before choosing any service, check pricing and be aware that many providers offer loss-leader pricing on lower volumes, but then charge higher rates once you hit a certain threshold. * For example, suppose there is a \$20 plan that includes 1 TB per month of data transfer, with \$0.20 per GB beyond that. Do the math to note that the second TB (and onward) will cost \$200. If the next tier up says "contact us", don't assume the next tier up will be charging \$20 per TB. * If you are building a high-traffic app and you aren't careful about serving static files more cheaply, it will be easy to exceed the loss-leader tier and pay much more than you expect. * For production deployments on AWS, consider: * Amazon S3 + CloudFront for static file hosting and CDN * AWS Lambda for serverless functions * Amazon ECS or EKS for containerized applications * Amazon RDS for database needs * This setup typically provides granular cost control and scalability for high-traffic applications. ## Use a hardcoded value instead of `eth_estimateGas` call if gas usage is static Many on-chain actions have a fixed gas cost. The simplest example is that a transfer of native tokens always costs 21,000 gas, but there are many others. This makes it unnecessary to call `eth_estimateGas` for each transaction. Use a hardcoded value instead, as suggested [here](/developer-essentials/gas-pricing#set-the-gas-limit-explicitly-if-it-is-constant). Eliminating an `eth_estimateGas` call substantially speeds up the user workflow in the wallet, and avoids a potential bad behavior in some wallets when `eth_estimateGas` reverts (discussed in the linked page). ## Reduce `eth_call` latency by submitting multiple requests concurrently Making multiple `eth_call` requests serially will introduce unnecessary latency due to multiple round trips to an RPC node. You can make many `eth_call`s concurrently, either by condensing them into a single `eth_call` or by submitting a batch of calls. Alternatively, you might find it better to switch to an indexer. ### Condensing multiple `eth_call`s into one * **Multicall:** Multicall is a utility smart contract that allows you to aggregate multiple read requests (`eth_call`) into a single one. This is particularly effective for fetching data points like token balances, allowances, or contract parameters simultaneously. The standard `Multicall3` contract is deployed at [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://monadvision.com/address/0xcA11bde05977b3631167028862bE2a173976CA11) on both Monad Mainnet and Monad Testnet. Many libraries offer helper functions to simplify multicall usage, e.g. [viem](https://viem.sh/docs/contract/multicall.html). Read more about `Multicall3` [here](https://www.multicall3.com). * **Custom Batching Contracts:** For complex read patterns or scenarios not easily handled by the standard multicall contract, you can deploy a custom smart contract that aggregates the required data in a single function, which can then be invoked via a single `eth_call`. Multicall executes calls serially as you can see from the code [**here**](https://monadvision.com/address/0xcA11bde05977b3631167028862bE2a173976CA11?tab=Contract#file-Multicall3.sol). So while using multicall avoids multiple round trips to an RPC server, it is still inadvisable to put too many expensive calls into one multicall. A batch of calls (explained next) can be executed on the RPC in parallel. ### Submitting a batch of calls Most major libraries support batching multiple RPC requests into a single message. For example, `viem` handles `Promise.all()` on an array of promises by submitting them as a single batch: ```javascript theme={null} const resultPromises = Array(BATCH_SIZE) .fill(null) .map(async (_, i) => { return await PUBLIC_CLIENT.simulateContract({ address: ..., abi: ..., functionName: ..., args: [...], }) }) const results = await Promise.all(resultPromises) ``` ### Use indexers for read-heavy loads If your application frequently queries historical events or derived state, consider using an indexer, as described next. ## Use an indexer instead of repeatedly calling `eth_getLogs` to listen for your events Below is a quickstart guide for the most popular data indexing solutions. Please view the [indexer docs](/tooling-and-infra/indexers/) for more details. ### Using Allium See also: [**Allium**](/tooling-and-infra/indexers/common-data#allium) You'll need an Allium account, which you can request [here](https://www.allium.so/contact). * Allium Explorer * Blockchain analytics platform that provides SQL-based access to historical blockchain data (blocks, transactions, logs, traces, and contracts). * You can create Explorer APIs through the [GUI](https://app.allium.so/explorer/api) to query and analyze historical blockchain data. When creating a Query for an API [here](https://app.allium.so/explorer/queries) (using the `New` button), select `Monad Mainnet` or `Monad Testnet` from the chain list. * Relevant docs: * [Explorer Documentation](https://docs.allium.so/app/overview) * [Explorer API](https://docs.allium.so/api/explorer/overview) * Allium Datastreams * Provides real-time blockchain data streams (including blocks, transactions, logs, traces, contracts, and balance snapshots) through Kafka, Pub/Sub, and Amazon SNS. * [GUI](https://app.allium.so/developer/streams/new) to create new streams for onchain data. When creating a stream, select the relevant `Monad Mainnet` or `Monad Testnet` topics from the `Select topics` dropdown. * Relevant docs: * [Datastreams Documentation](https://docs.allium.so/data-products-real-time/allium-datastreams) * [Getting Started with Google Pub/Sub](https://docs.allium.so/data-products-real-time/allium-datastreams/kafka-pubsub/getting-started-with-google-pub-sub) * Allium Developers * Enables fetching wallet transaction activity and tracking balances (native, ERC20, ERC721, ERC1155). * For the request's body, use `monad_mainnet` for Monad Mainnet or `monad_testnet` for Monad Testnet as the `chain` parameter. * Relevant docs: * [API Key Setup Guide](https://docs.allium.so/data-products-real-time/allium-developer/wallet-apis-1#getting-started) * [Wallet APIs Documentation](https://docs.allium.so/data-products-real-time/allium-developer/wallet-apis) ### Using Envio HyperIndex See also: [**Envio**](/tooling-and-infra/indexers/indexing-frameworks#envio) and [**Guide: How to use Envio HyperIndex to build a token transfer notification bot**](/guides/indexers/tg-bot-using-envio) * Follow the [quick start](https://docs.envio.dev/docs/HyperIndex/contract-import) to create an indexer. In the `config.yaml` file, use network ID `10143` to select Monad testnet (used in the example below) or network ID `143` for Monad mainnet. * Example configuration * Sample `config.yaml` file ```yaml lines title="config.yaml" theme={null} name: your-indexers-name networks: - id: 10143 # Monad Testnet # Optional custom RPC configuration - only add if default indexing has issues # rpc_config: # url: YOUR_RPC_URL_HERE # Replace with your RPC URL (e.g., from Alchemy) # interval_ceiling: 50 # Maximum number of blocks to fetch in a single request # acceleration_additive: 10 # Speed up factor for block fetching # initial_block_interval: 10 # Initial block fetch interval size start_block: 0 # Replace with the block you want to start indexing from contracts: - name: YourContract # Replace with your contract name address: - 0x0000000000000000000000000000000000000000 # Replace with your contract address # Add more addresses if needed for multiple deployments of the same contract handler: src/EventHandlers.ts events: # Replace with your event signatures # Format: EventName(paramType paramName, paramType2 paramName2, ...) # Example: Transfer(address from, address to, uint256 amount) # Example: OrderCreated(uint40 orderId, address owner, uint96 size, uint32 price, bool isBuy) - event: EventOne(paramType1 paramName1, paramType2 paramName2) # Add more events as needed ``` * Sample `EventHandlers.ts` ```tsx lines title="EventHandlers.ts" theme={null} import { YourContract, YourContract_EventOne, } from "generated"; // Handler for EventOne // Replace parameter types and names based on your event definition YourContract.EventOne.handler(async ({ event, context }) => { // Create a unique ID for this event instance const entity: YourContract_EventOne = { id: `${event.chainId}_${event.block.number}_${event.logIndex}`, // Replace these with your actual event parameters paramName1: event.params.paramName1, paramName2: event.params.paramName2, // Add any additional fields you want to store }; // Store the event in the database context.YourContract_EventOne.set(entity); }) // Add more event handlers as needed ``` * Important: The `rpc_config` section under a network (check `config.yaml` sample) is optional and should only be configured if you experience issues with the default Envio setup. This configuration allows you to: * Use your own RPC endpoint * Configure block fetching parameters for better performance * Relevant docs: * [Overview](https://docs.envio.dev/docs/HyperIndex/overview) ### Using GhostGraph See also: [**Ghost**](/tooling-and-infra/indexers/indexing-frameworks#ghost) * Relevant docs: * [Getting Started](https://docs.tryghost.xyz/category/-getting-started) * [Setting up a GhostGraph Indexer on Monad Testnet](/guides/indexers/ghost#setting-up-ghostgraph-indexing) ### Using Goldsky See also: [**Goldsky**](/tooling-and-infra/indexers/common-data#goldsky) * Goldsky Subgraphs * To deploy a Goldsky subgraph follow [this guide](https://docs.goldsky.com/subgraphs/deploying-subgraphs#from-source-code). * As the network identifier, use `monad-mainnet` for Monad Mainnet or `monad-testnet` for Monad Testnet. For subgraph configuration examples, refer to [The Graph Protocol section](#using-the-graphs-subgraph) below. * For information about querying Goldsky subgraphs, see the [GraphQL API documentation](https://docs.goldsky.com/subgraphs/graphql-endpoints). * Goldsky Mirror * Enables direct streaming of on-chain data to your database. * For the chain name in the `dataset_name` field when creating a `source` for a pipeline, use `monad_mainnet` for Monad Mainnet or `monad_testnet` for Monad Testnet (check below example) * Example `pipeline.yaml` config file ```yaml lines title="pipeline.yaml" theme={null} name: monad-testnet-erc20-transfers apiVersion: 3 sources: monad_testnet_erc20_transfers: dataset_name: monad_testnet.erc20_transfers filter: address = '0x0' # Add erc20 contract address. Multiple addresses can be added with 'OR' operator: address = '0x0' OR address = '0x1' version: 1.2.0 type: dataset start_at: earliest # Data transformation logic (optional) transforms: select_relevant_fields: sql: | SELECT id, address, event_signature, event_params, raw_log.block_number as block_number, raw_log.block_hash as block_hash, raw_log.transaction_hash as transaction_hash FROM ethereum_decoded_logs primary_key: id # Sink configuration to specify where data goes eg. DB sinks: postgres: type: postgres table: erc20_transfers schema: goldsky secret_name: A_POSTGRESQL_SECRET from: select_relevant_fields ``` * Relevant docs: * [Getting Started with Mirror](https://docs.goldsky.com/mirror/create-a-pipeline#goldsky-cli) * [Data Streaming Guides](https://docs.goldsky.com/mirror/guides/) ### Using QuickNode Streams See also: [**QuickNode Streams**](/tooling-and-infra/indexers/common-data#quicknode) * On your QuickNode Dashboard, select `Streams` > `Create Stream`. In the create stream UI, select Monad Mainnet or Monad Testnet under Network. Alternatively, you can use the [Streams REST API](https://www.quicknode.com/docs/streams/rest-api/getting-started) to create and manage streams—use `monad-mainnet` for Monad Mainnet or `monad-testnet` for Monad Testnet as the network identifier. * You can consume a Stream by choosing a destination during stream creation. Supported destinations include Webhooks, S3 buckets, and PostgreSQL databases. Learn more [here](https://www.quicknode.com/docs/streams/destinations). * Relevant docs: * [Getting Started](https://www.quicknode.com/docs/streams/getting-started) ### Using The Graph's Subgraph See also: [**The Graph**](/tooling-and-infra/indexers/indexing-frameworks#the-graph) * Network ID: Use `monad-mainnet` for Monad Mainnet or `monad-testnet` for Monad Testnet * Example configuration * Sample `subgraph.yaml` file ```yaml lines title="subgraph.yaml" theme={null} specVersion: 1.2.0 indexerHints: prune: auto schema: file: ./schema.graphql dataSources: - kind: ethereum name: YourContractName # Replace with your contract name network: monad-testnet # Monad testnet configuration source: address: "0x0000000000000000000000000000000000000000" # Replace with your contract address abi: YourContractABI # Replace with your contract ABI name startBlock: 0 # Replace with the block where your contract was deployed/where you want to index from mapping: kind: ethereum/events apiVersion: 0.0.9 language: wasm/assemblyscript entities: # List your entities here - these should match those defined in schema.graphql # - Entity1 # - Entity2 abis: - name: YourContractABI # Should match the ABI name specified above file: ./abis/YourContract.json # Path to your contract ABI JSON file eventHandlers: # Add your event handlers here, for example: # - event: EventName(param1Type, param2Type, ...) # handler: handleEventName file: ./src/mapping.ts # Path to your event handler implementations ``` * Sample `mappings.ts` file ```tsx lines title="mappings.ts" theme={null} import { // Import your contract events here // Format: EventName as EventNameEvent EventOne as EventOneEvent, // Add more events as needed } from "../generated/YourContractName/YourContractABI" // Replace with your contract name, abi name you supplied in subgraph.yaml import { // Import your schema entities here // These should match the entities defined in schema.graphql EventOne, // Add more entities as needed } from "../generated/schema" /** * Handler for EventOne * Update the function parameters and body according to your event structure */ export function handleEventOne(event: EventOneEvent): void { // Create a unique ID for this entity let entity = new EventOne( event.transaction.hash.concatI32(event.logIndex.toI32()) ) // Map event parameters to entity fields // entity.paramName = event.params.paramName // Example: // entity.sender = event.params.sender // entity.amount = event.params.amount // Add metadata fields entity.blockNumber = event.block.number entity.blockTimestamp = event.block.timestamp entity.transactionHash = event.transaction.hash // Save the entity to the store entity.save() } /** * Add more event handlers as needed * Format: * * export function handleEventName(event: EventNameEvent): void { * let entity = new EventName( * event.transaction.hash.concatI32(event.logIndex.toI32()) * ) * * // Map parameters * entity.param1 = event.params.param1 * entity.param2 = event.params.param2 * * // Add metadata * entity.blockNumber = event.block.number * entity.blockTimestamp = event.block.timestamp * entity.transactionHash = event.transaction.hash * * entity.save() * } */ ``` * Sample `schema.graphql` file ```graphql lines title="schema.graphql" theme={null} # Define your entities here # These should match the entities listed in your subgraph.yaml # Example entity for a generic event type EventOne @entity(immutable: true) { id: Bytes! # Add fields that correspond to your event parameters # Examples with common parameter types: # paramId: BigInt! # uint256, uint64, etc. # paramAddress: Bytes! # address # paramFlag: Boolean! # bool # paramAmount: BigInt! # uint96, etc. # paramPrice: BigInt! # uint32, etc. # paramArray: [BigInt!]! # uint[] array # paramString: String! # string # Standard metadata fields blockNumber: BigInt! blockTimestamp: BigInt! transactionHash: Bytes! } # Add more entity types as needed for different events # Example based on Transfer event: # type Transfer @entity(immutable: true) { # id: Bytes! # from: Bytes! # address # to: Bytes! # address # tokenId: BigInt! # uint256 # blockNumber: BigInt! # blockTimestamp: BigInt! # transactionHash: Bytes! # } # Example based on Approval event: # type Approval @entity(immutable: true) { # id: Bytes! # owner: Bytes! # address # approved: Bytes! # address # tokenId: BigInt! # uint256 # blockNumber: BigInt! # blockTimestamp: BigInt! # transactionHash: Bytes! # } ``` * Relevant docs: * [Quickstart](https://thegraph.com/docs/en/subgraphs/quick-start/) ### Using thirdweb's Insight API See also: [**thirdweb**](/tooling-and-infra/indexers/common-data#thirdweb) * REST API offering a wide range of on-chain data, including events, blocks, transactions, token data (such as transfer transactions, balances, and token prices), contract details, and more. * Use chain ID `143` for Monad Mainnet or `10143` for Monad Testnet when constructing request URLs. * Relevant docs: * [Get started](https://insight.thirdweb.com/reference) ## Manage nonces locally if sending multiple transactions in quick succession This only applies if you are setting nonces manually. If you are delegating this to the wallet, no need to worry about this. * `eth_getTransactionCount` requires a network request. If you have multiple transactions from the same wallet in short succession, you should implement local nonce tracking. ## Submit multiple transactions concurrently If you are submitting a series of transactions, instead submitting sequentially, implement concurrent transaction submission for improved efficiency. Before: ```jsx lines theme={null} for (let i = 0; i < TIMES; i++) { const tx_hash = await WALLET_CLIENT.sendTransaction({ account: ACCOUNT, to: ACCOUNT_1, value: parseEther('0.1'), gasLimit: BigInt(21000), baseFeePerGas: BigInt(50000000000), chain: CHAIN, nonce: nonce + Number(i), }) } ``` After: ```jsx lines theme={null} const transactionsPromises = Array(BATCH_SIZE) .fill(null) .map(async (_, i) => { return await WALLET_CLIENT.sendTransaction({ to: ACCOUNT_1, value: parseEther('0.1'), gasLimit: BigInt(21000), baseFeePerGas: BigInt(50000000000), chain: CHAIN, nonce: nonce + Number(i), }) }) const hashes = await Promise.all(transactionsPromises) ``` # Changelog Source: https://docs.monad.xyz/developer-essentials/changelog/index We provide several changelogs: * [Releases](/developer-essentials/changelog/releases): a list of all notable releases. Some releases only apply to one network. * [Testnet Changelog](/developer-essentials/changelog/testnet): only changes made to `testnet` ## How changes happen in Monad [Revisions](#revisions) are behavioral changes to the protocol (as contrasted with efficiency improvements to the client, which don't impact correctness). Revisions are referred to in other blockchains as hard forks. Monad tracks revisions with a counter. The client code typically activates revisions at a future timestamp so that validators can come to agreement about whether to accept the revision and upgrade ahead of time. When the future timestamp is hit, a supermajority of the validators have already upgraded, and they update their behavior in sync, allowing the chain to continue without any pauses. There are multiple networks (owing to the existence of several test networks). Each network has a different schedule for when each Revision is adopted. These schedules are tracked in [ChainConfigs](#chainconfigs). The node software is under active development, resulting in occasional [releases](/developer-essentials/changelog/releases). Releases are rolled out to different networks at different schedules, and not all releases apply to all networks. ### Revisions Monad revisions are major behavioral changes to the protocol, as defined in [`revision.h`](https://github.com/category-labs/monad/blob/main/category/vm/evm/monad/revision.h).
Revision Notes
MONAD\_NINE
  • \[[MIP-3](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-3.md)] Linear memory implementation
  • \[[MIP-4](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-4.md)] Reserve balance precompile
  • \[[MIP-5](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-5.md)] Activate Osaka fork (CLZ opcode)
MONAD\_EIGHT
  • [Reserve balance](/developer-essentials/reserve-balance) checks now use final state code hash
  • \[Staking] Reduce pagination on staking precompile inverse mappings (`precompile_get_delegations()` and `precompile_get_delegators()`) from 100 to 50
MONAD\_SEVEN
  • [Opcode pricing](/developer-essentials/opcode-pricing) implemented
MONAD\_SIX
  • EIP-2935 bugfix
MONAD\_FIVE
  • \[Staking] Lower `ACTIVE_VALIDATOR_STAKE` from `25,000,000 MON` to `10,000,000 MON`
MONAD\_FOUR
  • [Staking](/monad-arch/consensus/staking) goes live with the following parameters:
    • `ACTIVE_VALIDATOR_STAKE = 25,000,000 MON`
    • `MIN_AUTH_ADDRESS_STAKE = 100,000 MON`
  • [Reserve balance](/developer-essentials/reserve-balance)
  • [EIP-7702](/developer-essentials/eip-7702)
  • [Dynamic base fee](/developer-essentials/gas-pricing)
  • Min base fee [raised](/developer-essentials/gas-pricing) (50 MON-gwei -> 100 MON-gwei)
  • [Per-transaction gas limit](/developer-essentials/gas-pricing) of 30M gas
  • Block gas limit (150M -> 200M), i.e. gas per second 375Mgps -> 500Mgps
  • Enable [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) (extended historical block hashes)
  • Enable [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951) (P256VERIFY precompile)
  • Enable [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537) (BLS12-381 precompiles)
  • Raise max contract size for `CREATE`/`CREATE2` to 128 kb
MONAD\_THREE
  • [MonadBFT](/monad-arch/consensus/monad-bft) implemented
  • Block time (500ms -> 400ms), i.e. gas per second 300Mgps -> 375Mgps
MONAD\_TWO
  • Raise max contract size for plain contract creation transactions (24.5kb -> 128 kb)
MONAD\_ONE
  • Block time (1s -> 500ms)
  • Block gas limit (300M -> 150M); gas per second unchanged at 300Mgps
  • Transactions [charged by gas limit](/developer-essentials/gas-pricing)
### ChainConfigs Each ChainConfig describes one network, including its history of upgrading to different Revisions. The ChainConfigs are defined in [`monad-chain-config/src/lib.rs`](https://github.com/category-labs/monad-bft/blob/master/monad-chain-config/src/lib.rs). | ChainConfig | Notes | | ----------- | ------------------------------------------------------------------------ | | `mainnet` | Chain id 143 | | `testnet` | Chain id 10143; see [changelog](/developer-essentials/changelog/testnet) | | `devnet` | Chain id 20143 | # Mainnet Changelog Source: https://docs.monad.xyz/developer-essentials/changelog/mainnet This is a curated list of only changes affecting [mainnet](/developer-essentials/network-information). We group changes into * protocol changes (generally requiring a new [Revision](/developer-essentials/changelog#revisions)) * generally tagged with **\[EVM]**, **\[Consensus]**, or **\[Network params]** * RPC/SDK behavioral changes (generally tagged with **\[RPC]**) * performance changes * tagged based on the nature of the change as **\[EVM]**, **\[Consensus]**, or **\[RPC]** * internal/node-ops changes (generally tagged as **\[Node ops]**) ## v0.13.1 \[2026-03-16] Revision: [`MONAD_NINE`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft` (consensus): [tag `v0.13.1`](https://github.com/category-labs/monad-bft/releases/tag/v0.13.1) (`7db6cea2`) * `monad` (execution): [tag `v0.13.1`](https://github.com/category-labs/monad/releases/tag/v0.13.1) (`722cbf5b5`) > **Note:** This is a patch release based on v0.13.0 to fix State Archive node RPC issues. The monad (execution) tag points to the same commit as v0.13.0. #### Notable robustness changes * **\[RPC]** Use latest voted as fallback for last\_proposed * Ref: [monad-bft PR #2857](https://github.com/category-labs/monad-bft/pull/2857) ## v0.13.0 \[2026-03-12] Revision: [`MONAD_NINE`](/developer-essentials/changelog#revisions) (upgrade; Thursday, 2026-03-19 at 14:30 GMT) Tags or hashes: * `monad-bft` (consensus): [tag `v0.13.0`](https://github.com/category-labs/monad-bft/releases/tag/v0.13.0) * `monad` (execution): [tag `v0.13.0+1`](https://github.com/category-labs/monad/releases/tag/v0.13.0+1) > **Note:** The canonical release version is `v0.13.0`. The `+1` build metadata on the monad execution tag indicates a post-release rebuild that includes the reserve balance precompile fallback cost fix ([PR #2109](https://github.com/category-labs/monad/pull/2109)). #### Highlights * **RPC: `latest` block tag: `Finalized` -> `Proposed`** — queries with the `latest` block tag now return data from the latest proposed block, reducing query latency for `eth_getBalance`, `eth_call`, and other state queries. * **RPC: `eth_sendRawTransactionSync`: `Voted` -> `Proposed`** — earlier receipts when using the `eth_sendRawTransactionSync` method. * **Websocket: `newHeads/logs`: `Finalized` -> `Voted`** — earlier notifications for websocket subscribers to either `newHeads` or `logs`. #### Notable protocol changes All updates below are gated by the `MONAD_NINE` revision * **\[EVM]** [MIP-3](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-3.md): Linear memory implementation * Ref: [monad PR #2032](https://github.com/category-labs/monad/pull/2032) * **\[Execution]** [MIP-4](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-4.md): Reserve balance precompile * Ref: [monad PR #2040](https://github.com/category-labs/monad/pull/2040) (scaffolding) * Ref: [monad PR #2065](https://github.com/category-labs/monad/pull/2065) (cached/incremental checks) * Ref: [monad PR #2086](https://github.com/category-labs/monad/pull/2086) (add dippedIntoReserve) * Ref: [monad PR #2106](https://github.com/category-labs/monad/pull/2106) (dippedIntoReserve argument length error) * Ref: [monad PR #2109](https://github.com/category-labs/monad/pull/2109) (fallback cost fix for clean spec) * Ref: [monad PR #2037](https://github.com/category-labs/monad/pull/2037) (skip reserve checks for init-selfdestruct) * **\[EVM]** [MIP-5](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-5.md): Activate Osaka fork (CLZ opcode) * Ref: [monad PR #2024](https://github.com/category-labs/monad/pull/2024) > **Hard Fork:** This release activates MONAD\_NINE at timestamp `1773153000` (testnet) / `1773930600` (mainnet). Nodes must upgrade before activation. #### Notable RPC/SDK changes * **\[RPC]** Latest blocktag uses proposed blocks * Ref: [monad-bft PR #2675](https://github.com/category-labs/monad-bft/pull/2675) * **\[RPC]** Use voted blocks in websocket notifications * Ref: [monad-bft PR #2799](https://github.com/category-labs/monad-bft/pull/2799) * **\[RPC]** Add experimental flag to RPC doc macro * Ref: [monad-bft PR #2773](https://github.com/category-labs/monad-bft/pull/2773) * **\[RPC]** Refactor RPC middleware for improved request handling * Ref: [monad-bft PR #2805](https://github.com/category-labs/monad-bft/pull/2805) * **\[RPC]** Add decompression guard to RPC * Ref: [monad-bft PR #2793](https://github.com/category-labs/monad-bft/pull/2793) #### Notable robustness changes * **\[Consensus]** Add signature verifier for Raptorcast * Ref: [monad-bft PR #2747](https://github.com/category-labs/monad-bft/pull/2747) * **\[Consensus]** Fix reactivate logic in r10 decoder * Ref: [monad-bft PR #2740](https://github.com/category-labs/monad-bft/pull/2740) * **\[Execution]** Fix buffer overflow in C->Rust logging * Ref: [monad-bft PR #2769](https://github.com/category-labs/monad-bft/pull/2769) * **\[Consensus]** Add global connect rate limit to wireauth * Ref: [monad-bft PR #2765](https://github.com/category-labs/monad-bft/pull/2765) * **\[Consensus]** Validate ping/pong source address against name record in peer discovery * Ref: [monad-bft PR #2752](https://github.com/category-labs/monad-bft/pull/2752) * **\[Consensus]** Persist voted\_head in ledger * Ref: [monad-bft PR #2744](https://github.com/category-labs/monad-bft/pull/2744) * **\[Consensus]** Add dual UDP packet sender for dataplane * Ref: [monad-bft PR #2746](https://github.com/category-labs/monad-bft/pull/2746) * **\[Execution]** Fix ThreadSanitizer race by joining bootstrap fiber before thread exit * Ref: [monad PR #2053](https://github.com/category-labs/monad/pull/2053) * **\[Execution]** Include account balance in selfdestruct tracer frame * Ref: [monad PR #2039](https://github.com/category-labs/monad/pull/2039) * **\[Consensus]** Ensure proposed head is on canonical chain * Ref: [monad-bft PR #2756](https://github.com/category-labs/monad-bft/pull/2756) * **\[Execution]** Fix sentinel collision in compact virtual chunk offset * Ref: [monad PR #2083](https://github.com/category-labs/monad/pull/2083) * **\[Execution]** Fix potential race condition in execute\_block\_transactions * Ref: [monad PR #2092](https://github.com/category-labs/monad/pull/2092) #### Notable internal changes * **\[Consensus]** Remove unneeded channels when secondary raptorcast is disabled * Ref: [monad-bft PR #2808](https://github.com/category-labs/monad-bft/pull/2808) * **\[Consensus]** Upgrade alloy to stable release * Ref: [monad-bft PR #2792](https://github.com/category-labs/monad-bft/pull/2792) * **\[Execution]** Add `evm-as` syntactic sugar for VM utilities * Ref: [monad PR #2097](https://github.com/category-labs/monad/pull/2097) * **\[Consensus]** Parametrize wireauth metrics for better observability * Ref: [monad-bft PR #2692](https://github.com/category-labs/monad-bft/pull/2692) * **\[Consensus]** Cleanup block persist implementation * Ref: [monad-bft PR #2767](https://github.com/category-labs/monad-bft/pull/2767) * **\[Consensus]** Fix wip extension in block-persist * Ref: [monad-bft PR #2776](https://github.com/category-labs/monad-bft/pull/2776) * **\[Consensus]** Remove legacy `do_local_insert` * Ref: [monad-bft PR #2728](https://github.com/category-labs/monad-bft/pull/2728) * **\[Consensus]** Add metrics for raptorcast deserialize failures * Ref: [monad-bft PR #2789](https://github.com/category-labs/monad-bft/pull/2789) * **\[RPC]** Reduce RPC event server broadcast channel size * Ref: [monad-bft PR #2815](https://github.com/category-labs/monad-bft/pull/2815) * **\[Consensus]** Pre-TFM base fee cleanup * Ref: [monad-bft PR #2664](https://github.com/category-labs/monad-bft/pull/2664) * **\[Consensus]** Remove pre-TFM reserve balance logic * Ref: [monad-bft PR #2622](https://github.com/category-labs/monad-bft/pull/2622) * **\[RPC]** Simplify hex encoding/decoding in RPC * Ref: [monad-bft PR #2806](https://github.com/category-labs/monad-bft/pull/2806) * **\[Archive]** Add exists(key) to kvstore * Ref: [monad-bft PR #2688](https://github.com/category-labs/monad-bft/pull/2688) ## v0.12.7 \[2026-01-29] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) #### Notable RPC/SDK changes * **\[RPC]** Allow block hash as block identifier in `eth_estimateGas` * Ref: [monad-bft PR #2676](https://github.com/category-labs/monad-bft/pull/2676) * **\[RPC]** Use `RawValue` for RPC id to preserve original JSON types * Reduces effectiveness of DoS attacks using large JSON arrays/dicts in request IDs * Ref: [monad-bft PR #2455](https://github.com/category-labs/monad-bft/pull/2455) #### Notable robustness changes * **\[Consensus]** Remove consecutive sequence number assertion * Ref: [monad-bft PR #2704](https://github.com/category-labs/monad-bft/pull/2704) * **\[Consensus]** Improve liveness when advancing round using TC from timeout messages * Ref: [monad-bft PR #2701](https://github.com/category-labs/monad-bft/pull/2701) * **\[Consensus]** Disallow creating an empty validator set * Ref: [monad-bft PR #2682](https://github.com/category-labs/monad-bft/pull/2682) #### Notable internal changes * **\[Consensus]** Add security tests for `secp256k1` * Adds Wycheproof tests, malleability tests, and security unit tests * Ref: [monad-bft PR #2706](https://github.com/category-labs/monad-bft/pull/2706) * **\[Node ops]** Update `rkyv` and other dependencies * Ref: [monad-bft PR #2702](https://github.com/category-labs/monad-bft/pull/2702) * **\[Consensus]** Return bound socket addresses synchronously in dataplane * Ref: [monad-bft PR #2653](https://github.com/category-labs/monad-bft/pull/2653) * **\[Consensus]** Rename state backend cache for clarity * Ref: [monad-bft PR #2699](https://github.com/category-labs/monad-bft/pull/2699) ## v0.12.6 \[2026-01-14] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.6`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.6) * `monad`: [tag `v0.12.6`](https://github.com/category-labs/monad/releases/tag/v0.12.6) #### Notable RPC/SDK changes * **\[RPC]** Fix depth bug in selfdestructing call frames * Ref: [monad PR #1977](https://github.com/category-labs/monad/pull/1977) * **\[RPC]** Prestate tracer conformance fixes * Ref: [monad PR #1946](https://github.com/category-labs/monad/pull/1946) #### Notable robustness changes * **\[Consensus]** Add signature verification rate limiting with authenticated peer bypass * Ref: [monad-bft PR #2601](https://github.com/category-labs/monad-bft/pull/2601) * **\[Consensus]** Validate confirm group message before updating peers * Ref: [monad-bft PR #2680](https://github.com/category-labs/monad-bft/pull/2680) * **\[Consensus]** Use Tai64N directly for wireauth timestamp comparison * Ref: [monad-bft PR #2678](https://github.com/category-labs/monad-bft/pull/2678) * **\[Consensus]** Improve RaptorCast decoding cache eviction * Ref: [monad-bft PR #2651](https://github.com/category-labs/monad-bft/pull/2651) * **\[Consensus]** Fix blocksync in-flight request tracking on cache hydration * Ref: [monad-bft PR #2635](https://github.com/category-labs/monad-bft/pull/2635) * **\[Execution]** Update triedb voted metadata before executing proposal * Solves a race condition between Voted updates through websocket and corresponding JSON-RPC calls * Ref: [monad PR #1964](https://github.com/category-labs/monad/pull/1964) * **\[Node ops]** Persist peers periodically for improved discovery resilience * Ref: [monad-bft PR #2633](https://github.com/category-labs/monad-bft/pull/2633) #### Notable performance changes * **\[Consensus]** Boost dataplane throughput with ring buffer implementation * Ref: [monad-bft PR #2596](https://github.com/category-labs/monad-bft/pull/2596) * **\[Execution]** Native implementation for `MLOAD`, `MSTORE`, `MSTORE8`, `CALLDATALOAD` opcodes * Ref: [monad PR #1963](https://github.com/category-labs/monad/pull/1963) * **\[Execution]** Improve snapshot write performance * Ref: [monad PR #1973](https://github.com/category-labs/monad/pull/1973), [monad PR #1960](https://github.com/category-labs/monad/pull/1960) * **\[Execution]** Optimize database history length adjustment with binary search * Ref: [monad PR #1922](https://github.com/category-labs/monad/pull/1922) * **\[Execution]** Unify single buffer and scatter read handling in AsyncIO * Ref: [monad PR #1944](https://github.com/category-labs/monad/pull/1944) #### Notable internal changes * **\[Node ops]** Docker single-node container updates for prebuilt images * Fixes to [Local Docker installation](https://github.com/category-labs/monad-bft/blob/master/README.md#using-pre-built-images) * Ref: [monad-bft PR #2674](https://github.com/category-labs/monad-bft/pull/2674) * **\[Consensus]** Add support for generic txpool sidecars * Enables IPC transaction priority and forwarding controls * Ref: [monad-bft PR #2557](https://github.com/category-labs/monad-bft/pull/2557) * **\[EVM]** Osaka fork preparation * Modexp gas changes and upper bound (EIP-7823/EIP-7883) * Implement `CLZ` opcode * Ref: [monad PR #1970](https://github.com/category-labs/monad/pull/1970), [monad PR #1981](https://github.com/category-labs/monad/pull/1981) * **\[Node ops]** Accept `--hyphen-style` long options in CLI * Ref: [monad PR #2005](https://github.com/category-labs/monad/pull/2005) * **\[Node ops / Archive]** Archive pipeline improvements * Add `WritePolicy` to `KVStore` for conditional write protection (`NoClobber`) * Add BFT-uploading stats logging * Ref: [monad-bft PR #2649](https://github.com/category-labs/monad-bft/pull/2649), [monad-bft PR #2661](https://github.com/category-labs/monad-bft/pull/2661) * **\[Node ops]** Simplify RPC txpool status tracking * Ref: [monad-bft PR #2644](https://github.com/category-labs/monad-bft/pull/2644) * **\[Node ops]** Snapshot tooling improvements in `monad_cli` * Add `--dump_concurrency_limit` parameter * Add sharding support for distributed snapshot creation * Ref: [monad PR #1967](https://github.com/category-labs/monad/pull/1967), [monad PR #1965](https://github.com/category-labs/monad/pull/1965) * **\[Node ops]** Add RaptorCast decoding cache metrics * Ref: [monad-bft PR #2667](https://github.com/category-labs/monad-bft/pull/2667) * **\[Execution]** Add `CommitBuilder` API to execution database * Ref: [monad PR #1968](https://github.com/category-labs/monad/pull/1968) * **\[Execution]** Remove legacy `using_chunks_for_root_offsets` metadata field * Ref: [monad PR #1943](https://github.com/category-labs/monad/pull/1943) * **\[Node ops / Txgen]** Add ERC-4337 + EIP-7702 generator (`erc4337_7702_bundled`) * Ref: [monad-bft PR #2628](https://github.com/category-labs/monad-bft/pull/2628) * **\[Node ops]** Add systemd service scripts for `blockcapd` and execution events archivers * Ref: [monad-bft PR #2590](https://github.com/category-labs/monad-bft/pull/2590) * **\[SDK]** Add `monad_event_resolve_ring_file` API * Ref: [monad PR #1741](https://github.com/category-labs/monad/pull/1741) * **\[Execution]** Enable VM host exception handling outside VM * Ref: [monad PR #1990](https://github.com/category-labs/monad/pull/1990) ## v0.12.4 \[2025-12-05] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.4`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.4) * `monad`: [tag `v0.12.4`](https://github.com/category-labs/monad/releases/tag/v0.12.4) *(unchanged from v0.12.3)* #### Notable internal changes * **\[Node ops]** Fix peer discovery port configuration when constructing self name record * Use port from peer discovery config instead of bound socket when constructing self name record. This bug affected node operators using a NAT. * Ref: [monad-bft PR #2655](https://github.com/category-labs/monad-bft/pull/2655) ## v0.12.3 \[2025-12-04] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.3`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.3) * `monad`: [tag `v0.12.3`](https://github.com/category-labs/monad/releases/tag/v0.12.3) #### Notable RPC/SDK changes * **\[RPC]** [EIP-7966](https://eips.ethereum.org/EIPS/eip-7966) (`eth_sendRawTransactionSync`) support * Ref: [monad-bft PR #2542](https://github.com/category-labs/monad-bft/pull/2542) * **\[RPC]** Populate `value` field on traces for staking precompile syscalls (added in `0.12.3-rpc-hotfix2`) * Ref: [monad PR #1938](https://github.com/category-labs/monad/pull/1938) #### Notable robustness changes * **\[Consensus]** Wire authentication protocol for UDP * Adds authenticated UDP communication to improve network security * Includes replay window adjustments for improved reliability * Ref: [monad-bft PR #2417](https://github.com/category-labs/monad-bft/pull/2417), [monad-bft PR #2544](https://github.com/category-labs/monad-bft/pull/2544), [monad-bft PR #2626](https://github.com/category-labs/monad-bft/pull/2626), [monad-bft PR #2091](https://github.com/category-labs/monad-bft/pull/2091) * **\[Execution]** Improve `statesync` shutdown and fix protocol corruption * Modified `statesync_server_recv()` to guarantee complete reads * Ref: [monad PR #1930](https://github.com/category-labs/monad/pull/1930) * **\[RPC]** Prevent reserve balance crash in RPC * Add reserve balance violation error messages in call frames * Ref: [monad PR #1932](https://github.com/category-labs/monad/pull/1932), [monad-bft PR #2629](https://github.com/category-labs/monad-bft/pull/2629) * This was already previously released in `v0.12.2-rpc-hotfix` * **\[Consensus]** Add timeout to `txpool` IPC stream * Prevents hanging connections in transaction pool communication * Ref: [monad-bft PR #2619](https://github.com/category-labs/monad-bft/pull/2619) * **\[Consensus]** Reject timeout certificates with empty signers * Adds validation to prevent malformed timeout certificates * Ref: [monad-bft PR #2630](https://github.com/category-labs/monad-bft/pull/2630) * **\[Consensus]** Verify RaptorCast chunk length is non-zero * Adds validation to prevent malformed RaptorCast chunks * Ref: [monad-bft PR #2638](https://github.com/category-labs/monad-bft/pull/2638) * **\[Execution]** Fix MPT restore bug that failed to preserve `version_lower_bound` * Ref: [monad PR #1955](https://github.com/category-labs/monad/pull/1955) * **\[Execution]** Fix move trie version forward bug * Erase versions that fall out of history range before moving forward to newer versions * Ref: [monad PR #1957](https://github.com/category-labs/monad/pull/1957) * **\[Execution]** Fix dangling pointers to intercode * Resolves memory safety issue with `get_code` and `read_code` functions * Ref: [monad PR #1941](https://github.com/category-labs/monad/pull/1941) * **\[Execution]** Add retries to `runloop_monad_ethblocks` * Ref: [monad PR #1953](https://github.com/category-labs/monad/pull/1953) #### Notable performance changes * **\[RPC]** Improve `eth_getLogs` performance * Reduce memory copies and unify receipt-to-logs processing * Ref: [monad-bft PR #2588](https://github.com/category-labs/monad-bft/pull/2588), [monad-bft PR #2591](https://github.com/category-labs/monad-bft/pull/2591), [monad-bft PR #2631](https://github.com/category-labs/monad-bft/pull/2631) * **\[Execution]** Fiber: add support for move-only functors * Improves execution efficiency by supporting move semantics * Ref: [monad PR #1936](https://github.com/category-labs/monad/pull/1936) #### Notable internal changes * **\[RPC / Node ops]** Allow RPC to run without `monad-bft` * Enables standalone RPC operation for improved deployment flexibility * Ref: [monad-bft PR #2613](https://github.com/category-labs/monad-bft/pull/2613) * **\[Node ops]** Add `--root-offsets-chunk-count` flag to `monad-mpt` to configure the number of chunks allocated for root offsets * Each chunk holds approximately 16.5M blocks. Previously, all nodes were hardcoded to use 2 chunks (max TrieDB capacity of \~33M blocks) * New default: 16 **must be a power of 2** * Note that the default value of 16 translates to approximately 268M blocks \~ 1240 days. In most cases the limiting factor is disk capacity which will auto-compact when filled to 80% * Ref: [monad PR #1937](https://github.com/category-labs/monad/pull/1937) * **\[Node ops]** Peer discovery improvements * Full nodes periodically pull validator name records for dynamic validator discovery * Add authenticated UDP port to name record * Ref: [monad-bft PR #2607](https://github.com/category-labs/monad-bft/pull/2607), [monad-bft PR #2538](https://github.com/category-labs/monad-bft/pull/2538) * **\[Node ops / Archive]** Archive infrastructure improvements * Refactor `monad-block-writer` for improved reliability with `--max-blocks-per-iter` configuration * Async backfill with traces-only archive support * Add `require-traces` archiver flag and indexer fallback source * Support for historical execution event archiving with generic directory archiving * **Potentially breaking: Remove `--start-block` from `monad-archiver` systemd service; operators must use imperative CLI to set start block** * Example: `monad-archiver set-start-block --block 1000000 --archive-sink s3://bucket-name/path` * Use `--async-backfill` flag to set the async-backfill marker instead of primary marker * Ref: [monad-bft PR #2610](https://github.com/category-labs/monad-bft/pull/2610), [monad-bft PR #2606](https://github.com/category-labs/monad-bft/pull/2606), [monad-bft PR #2598](https://github.com/category-labs/monad-bft/pull/2598), [monad-bft PR #2514](https://github.com/category-labs/monad-bft/pull/2514), [monad-bft PR #2612](https://github.com/category-labs/monad-bft/pull/2612), [monad-bft PR #2569](https://github.com/category-labs/monad-bft/pull/2569), [monad-bft PR #2623](https://github.com/category-labs/monad-bft/pull/2623) * **\[Node ops]** Networking configuration updates * Use default MTU 1500 * Add HDR histogram for broadcast latency tracking in `monad-executor` and `monad-raptorcast` * Ref: [monad-bft PR #2576](https://github.com/category-labs/monad-bft/pull/2576), [monad-bft PR #2602](https://github.com/category-labs/monad-bft/pull/2602) * **\[Consensus]** RaptorCast improvements * Clear histogram every 30s and keep only p99 for better latency tracking * Fix timer update after receiving control messages to ensure proper keepalives * Ref: [monad-bft PR #2627](https://github.com/category-labs/monad-bft/pull/2627), [monad-bft PR #2637](https://github.com/category-labs/monad-bft/pull/2637) * **\[Consensus]** Dataplane: refactor to use socket handles * Prepares dataplane for future extension with authenticated sockets * Ref: [monad-bft PR #2458](https://github.com/category-labs/monad-bft/pull/2458) * **\[Execution]** Replay monad: always execute first block off latest finalized state * Ref: [monad PR #1927](https://github.com/category-labs/monad/pull/1927) * **\[Node ops]** Txgen improvements: README and guide, nonce gaps and legacy tx options, ERC20 pools, Uniswap v3 mode, NFT sale mode, websocket and RPC request generation * Ref: [monad-bft PR #2568](https://github.com/category-labs/monad-bft/pull/2568), [monad-bft PR #2567](https://github.com/category-labs/monad-bft/pull/2567), [monad-bft PR #2577](https://github.com/category-labs/monad-bft/pull/2577), [monad-bft PR #2600](https://github.com/category-labs/monad-bft/pull/2600), [monad-bft PR #2527](https://github.com/category-labs/monad-bft/pull/2527), [monad-bft PR #2394](https://github.com/category-labs/monad-bft/pull/2394), [monad-bft PR #2608](https://github.com/category-labs/monad-bft/pull/2608) ## v0.12.2 \[2025-11-18] The Public Mainnet phase began on 2025-11-24. As such, public mainnet started at [`v0.12.2`](/developer-essentials/changelog/releases#v0122). # Releases Source: https://docs.monad.xyz/developer-essentials/changelog/releases This page catalogues every notable release. Some releases target only specific networks; for a more intuitive view of upgrades to one network, see: * [mainnet](/developer-essentials/changelog/mainnet) * [testnet](/developer-essentials/changelog/testnet) We group changes into * protocol changes (generally requiring a new [Revision](/developer-essentials/changelog#revisions)) * generally tagged with **\[EVM]**, **\[Consensus]**, or **\[Network params]** * RPC/SDK behavioral changes (generally tagged with **\[RPC]**) * performance changes * tagged based on the nature of the change as **\[EVM]**, **\[Consensus]**, or **\[RPC]** * internal/node-ops changes (generally tagged as **\[Node ops]**) ## v0.14.3 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2026-05-14 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-05-07 | | Tags or hashes: * `monad-bft` (consensus): [tag `v0.14.3`](https://github.com/category-labs/monad-bft/releases/tag/v0.14.3) (`f2a2092b`) * `monad` (execution): [tag `v0.14.3`](https://github.com/category-labs/monad/releases/tag/v0.14.3) (`7fcd693f7`) #### Highlights * **Authenticated UDP enforcement** — Raptorcast now requires wire-authenticated UDP sockets; previously optional, now enforced for all nodes #### Notable robustness changes * **\[Consensus]** Raptorcast: require authenticated UDP socket * Ref: [monad-bft PR #2956](https://github.com/category-labs/monad-bft/pull/2956) * Enforces authenticated UDP socket setup; previously optional * **\[Execution]** Fix `DbCache` assert-fail when reading finalized state older than latest finalized block * Ref: [monad (execution) PR #2190](https://github.com/category-labs/monad/pull/2190) * Replaces assertion with explicit checks; fixes incorrect `truncated` value propagation when walking the chain back past the latest finalized block * **\[Execution]** Raise `MIN_HISTORY_LENGTH` floor from 257 to 300 * Ref: [monad (execution) PR #2217](https://github.com/category-labs/monad/pull/2217) * Under disk pressure, history could shrink below the 256-block minimum required for the block hash buffer, forcing a full statesync after restart; new floor gives \~44 blocks of headroom * **\[Execution]** Fix `evm-as` PushLabel encoding at offset zero for pre-Shanghai targets * Ref: [monad (execution) PR #2213](https://github.com/category-labs/monad/pull/2213) * Fixes incorrect `PUSH0` encoding in the EVM assembler for pre-Shanghai code paths #### Notable performance changes * **\[Execution]** Extend trie span optimization to fnext, child\_off, and SharedPtr arrays * Ref: [monad (execution) PR #2176](https://github.com/category-labs/monad/pull/2176) * **\[Execution]** Remove inflight read coalescing from rwdb find path * Ref: [monad (execution) PR #2183](https://github.com/category-labs/monad/pull/2183) * **\[Execution]** Elide DB fetch in `State::is_touched` * Ref: [monad (execution) PR #2198](https://github.com/category-labs/monad/pull/2198) * **\[RPC]** Skip redundant receipt conversion on log retrieval path * Ref: [monad-bft PR #2839](https://github.com/category-labs/monad-bft/pull/2839) * Avoids building a full `TransactionReceipt` when serving log queries; goes directly from `(TxEnvelope, Receipt)` to `ReceiptEnvelope`, reducing overhead on `eth_getLogs` and related calls #### Notable internal changes * **\[Consensus]** Peer discovery: require authenticated UDP in name records * Ref: [monad-bft PR #2955](https://github.com/category-labs/monad-bft/pull/2955) * Enforces non-zero port policy for authenticated UDP; port 0 was used as an empty value in RLP-encoded peer entries and is now rejected * **\[Consensus]** Add full nodes message type and handling logic for peer reputation scoring * Ref: [monad-bft PR #2914](https://github.com/category-labs/monad-bft/pull/2914), [PR #2915](https://github.com/category-labs/monad-bft/pull/2915) * Introduces a new message type for full nodes to send peer participation data to the publisher for reputation scoring * **\[Consensus]** Peer discovery: drop V1 wire name records * Ref: [monad-bft PR #2952](https://github.com/category-labs/monad-bft/pull/2952) * Removes backward compatibility with V1 peer wire format; all peers must use V2 name records * **\[Consensus]** Expose block number by hash retrieval in archive * Ref: [monad-bft PR #2980](https://github.com/category-labs/monad-bft/pull/2980) * Archive nodes now support block number lookup by block hash * **\[Consensus]** Raptorcast: round-robin mode for stake-proportional chunk assigner * Ref: [monad-bft PR #2970](https://github.com/category-labs/monad-bft/pull/2970) * Adds round-robin assignment to the stake-proportional chunk assigner used in deterministic Raptorcast ## v0.14.2 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2026-04-30 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-04-23 | | Tags or hashes: * `monad-bft` (consensus): [tag `v0.14.2`](https://github.com/category-labs/monad-bft/releases/tag/v0.14.2) (`6307011c`) * `monad` (execution): [tag `v0.14.2`](https://github.com/category-labs/monad/releases/tag/v0.14.2) (`169e5c45b`) #### Highlights * **`debug_*` improvements** — new `index` field on traced logs; `eth_call` returns explicit `reserve balance violation` error * **8-9x speedup on compaction hot path** — trie node array optimization reduces redundant pointer recomputation * **Fair queue forwarded ingress in txpool** — improves tx forwarding fairness between validators #### Notable RPC/SDK changes * **\[RPC]** Move callframe retrieval methods to ChainState * Ref: [monad-bft PR #2936](https://github.com/category-labs/monad-bft/pull/2936), [PR #2937](https://github.com/category-labs/monad-bft/pull/2937) * Covers block and transaction callframes; deduplicates call frame decoding in debug trace handlers * **\[RPC]** Include log index in debug trace output; surface reserve balance error in `eth_call` * Ref: [monad-bft PR #2658](https://github.com/category-labs/monad-bft/pull/2658) * Adds `index` field to each traced log in the debug call tracer, reflecting the global log order within a transaction across all nested call frames; `eth_call` now returns an explicit `reserve balance violation` error instead of a generic failure when a transaction exceeds the sender's reserve balance * **\[RPC]** Move eth call gas limit config to EthCallHandler * Ref: [monad-bft PR #2926](https://github.com/category-labs/monad-bft/pull/2926) * **\[RPC]** `debug_*` methods now count toward eth call rate limiting * Ref: [monad-bft PR #2868](https://github.com/category-labs/monad-bft/pull/2868) * `debug_traceCall`, `debug_traceTransaction`, and related methods now update the eth call stats tracker; previously they bypassed it * **\[RPC]** Enforce max response size earlier for debug RPC methods * Ref: [monad-bft PR #2959](https://github.com/category-labs/monad-bft/pull/2959) * **\[RPC]** Lower RPC batch request limit to match Geth's default * Ref: [monad-bft PR #2934](https://github.com/category-labs/monad-bft/pull/2934) #### Notable robustness changes * **\[Consensus]** ConfirmGroup message fix * Ref: [monad-bft PR #2652](https://github.com/category-labs/monad-bft/pull/2652) * Prevents oversized ConfirmGroup messages being rejected by full nodes; introduces NoConfirm response when group reaches max size * **\[Consensus]** Fix leak in leanudp identity usage map * Ref: [monad-bft PR #2930](https://github.com/category-labs/monad-bft/pull/2930) * **\[Consensus]** Enforce stale message timeout in leanudp * Ref: [monad-bft PR #2873](https://github.com/category-labs/monad-bft/pull/2873) * **\[RPC]** Fix eth call stats tracking memleak in RPC * Ref: [monad-bft PR #2910](https://github.com/category-labs/monad-bft/pull/2910) * **\[Consensus]** Relax statesync assertion for stale DoneSync edge case * Ref: [monad-bft PR #2905](https://github.com/category-labs/monad-bft/pull/2905) * **\[Execution]** Fix possible UB in unaligned read * Ref: [monad (execution) PR #2168](https://github.com/category-labs/monad/pull/2168) * **\[Execution]** Fix incorrect auto\_expire\_version initialization after DB reset * Ref: [monad (execution) PR #2167](https://github.com/category-labs/monad/pull/2167) * **\[Execution]** Fix compaction progress reset during statesync * Ref: [monad (execution) PR #2133](https://github.com/category-labs/monad/pull/2133) * Fixes compaction head being incorrectly reset to 0 during statesync, causing compaction to restart from the beginning * **\[Consensus]** Decode exact forwarded transactions * Ref: [monad-bft PR #2810](https://github.com/category-labs/monad-bft/pull/2810) * Ensures forwarded transactions are decoded with no trailing bytes #### Notable performance changes * **\[Consensus]** Front-load cheap consensus validation checks * Ref: [monad-bft PR #2928](https://github.com/category-labs/monad-bft/pull/2928) * **\[Consensus]** Fair queue forwarded ingress in txpool client * Ref: [monad-bft PR #2882](https://github.com/category-labs/monad-bft/pull/2882) * **\[Consensus]** Move forwarded tx pacing into executor, simplify ingress batching * Ref: [monad-bft PR #2884](https://github.com/category-labs/monad-bft/pull/2884), [PR #2886](https://github.com/category-labs/monad-bft/pull/2886) * **\[Consensus]** Drop already-obsolete consensus messages before certificate validation * Ref: [monad-bft PR #2927](https://github.com/category-labs/monad-bft/pull/2927) * **\[Execution]** Optimize trie node array access (8-9x speedup on compaction hot path) * Ref: [monad (execution) PR #2163](https://github.com/category-labs/monad/pull/2163) * Hoists base pointers for packed node arrays; reduces redundant pointer recomputation in compaction, expiration, and version tracking loops #### Notable internal changes * **\[Consensus]** Raptorcast: direct UDP transport * Ref: [monad-bft PR #2898](https://github.com/category-labs/monad-bft/pull/2898), [PR #2900](https://github.com/category-labs/monad-bft/pull/2900) * Adds a dedicated direct UDP socket for point-to-point traffic, bypassing Raptorcast erasure coding. Eliminates the \~2.5x bandwidth amplification inherent in Raptorcast for unicast sends. Falls back to the Raptorcast path gracefully for peers that don't support it. * **Note:** Still in testing — not recommended for production use in this release. * **\[Consensus]** Wireauth: lower per-instance defaults * Ref: [monad-bft PR #2906](https://github.com/category-labs/monad-bft/pull/2906) * **\[Execution]** Replace system boost fiber with submodule (boost-1.83.0) * Ref: [monad (execution) PR #2157](https://github.com/category-labs/monad/pull/2157) * **\[Execution]** Separate precompile gas cost calculation from runtime implementation * Ref: [monad (execution) PR #2099](https://github.com/category-labs/monad/pull/2099), [PR #2098](https://github.com/category-labs/monad/pull/2098) * Prep work for zkVM compilation * **\[Execution]** Remove support for EVMC\_FRONTIER * Ref: [monad (execution) PR #2041](https://github.com/category-labs/monad/pull/2041) * **\[Execution]** Add block-level event recording for Ethereum and Monad replay * Ref: [monad (execution) PR #2006](https://github.com/category-labs/monad/pull/2006) * Emits BLOCK\_START/BLOCK\_END events during replay, making replay event streams usable for tooling * **\[Consensus]** Configure shared peer scoring for txpool and raptorcast * Ref: [monad-bft PR #2907](https://github.com/category-labs/monad-bft/pull/2907) * **\[Consensus]** Raptorcast: enforce type-level safety for BroadcastGroup in build target * Ref: [monad-bft PR #2843](https://github.com/category-labs/monad-bft/pull/2843) * Restricts build target to established groups, eliminating packet builder epoch/round tracking desync risk ## v0.14.1 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2026-04-09 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-04-08 | | Tags or hashes: * `monad-bft` (consensus): [tag `v0.14.1`](https://github.com/category-labs/monad-bft/releases/tag/v0.14.1) (`4ed79392`) * `monad` (execution): [tag `v0.14.1`](https://github.com/category-labs/monad/releases/tag/v0.14.1) (`54e132cfa`) — same commit as v0.14.0 > **Note:** Patch release on top of v0.14.0. Execution layer unchanged. #### Notable RPC/SDK changes * **\[RPC]** Fix `eth_fillTransaction` gas estimation and reserve balance handling * Ref: [monad-bft PR #2949](https://github.com/category-labs/monad-bft/pull/2949) * Maps reserve-balance failures to a dedicated result instead of collapsing into a generic error; makes omitted-gas fill estimation more permissive with an unbounded-balance state override during fill estimation * **Note:** `eth_fillTransaction` with insufficient balance now returns `reserve balance violation` (was: `insufficient balance`). Callers matching on exact error strings should update accordingly. ## v0.14.0 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | N/A | | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-04-02 | | Tags or hashes: * `monad-bft` (consensus): [tag `v0.14.0`](https://github.com/category-labs/monad-bft/releases/tag/v0.14.0) (`2d487de0`) * `monad` (execution): [tag `v0.14.0`](https://github.com/category-labs/monad/releases/tag/v0.14.0) (`54e132cfa`) #### Highlights * **RPC: \~400ms faster receipt queries** — Receipts now tracked in chainstate buffer and available at Proposed time, eliminating trie lookups that added \~400ms on v0.13.0. * **RPC: `eth_fillTransaction`** — New endpoint to get a fully populated transaction (gas, nonce, etc.) without signing. Useful for wallets and dapps preparing transactions. * **RPC: Websocket notifications on proposed blocks** — `newHeads` and `logs` subscriptions now emit on proposed blocks instead of voted, reducing notification latency for real-time applications. #### Notable RPC/SDK changes * **\[RPC]** `eth_fillTransaction` RPC endpoint * Ref: [monad-bft PR #2662](https://github.com/category-labs/monad-bft/pull/2662), [PR #2932](https://github.com/category-labs/monad-bft/pull/2932) * PR #2932 fixes several inconsistencies: preserves user-provided `maxFeePerGas` when sufficient, treats `0x0` gas/fee as missing, allows gas fill for failed txs, rejects blob-style requests, handles EIP-2930 legacy-fee correctly * **\[RPC]** Use proposed blocks in websocket notifications * Ref: [monad-bft PR #2817](https://github.com/category-labs/monad-bft/pull/2817) * **\[RPC]** Transaction receipts in chainstate buffer (\~400ms faster receipt queries) * Ref: [monad-bft PR #2809](https://github.com/category-labs/monad-bft/pull/2809), [PR #2818](https://github.com/category-labs/monad-bft/pull/2818) * **\[RPC]** Consolidate triedb usage to ChainState * Ref: [monad-bft PR #2876](https://github.com/category-labs/monad-bft/pull/2876) * **\[RPC]** Use ChainStateBuffer for RPC logs * Ref: [monad-bft PR #2840](https://github.com/category-labs/monad-bft/pull/2840) * **\[Execution]** Add execution events Rust SDK with path resolution and descriptor info * Ref: [monad (execution) PR #2123](https://github.com/category-labs/monad/pull/2123), [PR #2135](https://github.com/category-labs/monad/pull/2135), [PR #2136](https://github.com/category-labs/monad/pull/2136) #### Notable robustness changes * **\[Execution]** Remove ASSERT that relied on suboptimality of reserve balance accounting * Ref: [monad (execution) PR #2096](https://github.com/category-labs/monad/pull/2096) * **\[Consensus]** RLP cleanup and stricter decoding enforcement * Ref: [monad-bft PR #2785](https://github.com/category-labs/monad-bft/pull/2785) * **\[Consensus]** Fix zero-length allocation leak in triedb sync read FFI * Ref: [monad-bft PR #2816](https://github.com/category-labs/monad-bft/pull/2816) * **\[Consensus]** Fix dataplane ban expiry TOCTOU underflow * Ref: [monad-bft PR #2851](https://github.com/category-labs/monad-bft/pull/2851) * **\[Archive]** Fix S3 client stuck after sustained outage * Ref: [monad-bft PR #2823](https://github.com/category-labs/monad-bft/pull/2823) * **\[Archive]** Relax checker replica matching for AWS args * Ref: [monad-bft PR #2943](https://github.com/category-labs/monad-bft/pull/2943) * Handles missing optional AWS fields in stored replica config; avoids dropping replicas from tracked state * **\[Execution]** Fix runloop\_monad off-by-one when nblocks=1 * Ref: [monad (execution) PR #2018](https://github.com/category-labs/monad/pull/2018) * **\[Execution]** Fix RangedGetMachine skipping keys past min prefix * Ref: [monad (execution) PR #2110](https://github.com/category-labs/monad/pull/2110) * **\[Execution]** Fix node writer reentrance causing non-contiguous writes * Ref: [monad (execution) PR #2082](https://github.com/category-labs/monad/pull/2082) * **\[Execution]** Fix undefined behavior in RLP encoding with empty inputs * Ref: [monad (execution) PR #2152](https://github.com/category-labs/monad/pull/2152) * **\[Consensus]** Fix unstable ordering of OpenRPC schemas * Ref: [monad-bft PR #2893](https://github.com/category-labs/monad-bft/pull/2893) * **\[Consensus]** Fix ledger-tail: read author\_address from persisted peers * Ref: [monad-bft PR #2888](https://github.com/category-labs/monad-bft/pull/2888) #### Notable performance changes * **\[Consensus]** Cache all successful Raptorcast decoding outcomes * Ref: [monad-bft PR #2887](https://github.com/category-labs/monad-bft/pull/2887) * **\[Consensus]** Weighted fair queue for transaction ingestion * Ref: [monad-bft PR #2828](https://github.com/category-labs/monad-bft/pull/2828) * **\[Consensus]** Gas-based peer scoring * Ref: [monad-bft PR #2829](https://github.com/category-labs/monad-bft/pull/2829) * **\[Consensus]** XOR decoder buffers in AVX2 chunks * Ref: [monad-bft PR #2863](https://github.com/category-labs/monad-bft/pull/2863) * **\[Consensus]** Lazy decoder for BLS signature * Ref: [monad-bft PR #2861](https://github.com/category-labs/monad-bft/pull/2861) * **\[Execution]** Improve fast list compaction offset calculation with hysteresis * Ref: [monad (execution) PR #2107](https://github.com/category-labs/monad/pull/2107) #### Notable internal changes * **\[Consensus]** ⚠️ Make auth UDP fields in `node.toml` required * Ref: [monad-bft PR #2891](https://github.com/category-labs/monad-bft/pull/2891) * **\[Consensus]** Lower max `raptor10_fullnode_redundancy_factor` from 7 to 3 * Ref: [monad-bft PR #2852](https://github.com/category-labs/monad-bft/pull/2852) * **\[Node ops]** ⚠️ `monad-ledger-tail`: rename `--node-config-path` to `--peers-path` * Ref: [monad-bft PR #2916](https://github.com/category-labs/monad-bft/pull/2916) * If you run `monad-ledger-tail` manually or in a custom systemd unit, update the flag name * **\[Node ops]** Change systemd services from `Restart=no` to `Restart=always` * Ref: [monad-bft PR #2860](https://github.com/category-labs/monad-bft/pull/2860) * **\[Consensus]** Lightweight UDP framing protocol (leanudp) * Ref: [monad-bft PR #2827](https://github.com/category-labs/monad-bft/pull/2827) * **\[Consensus]** Support direct UDP port in peer discovery * Ref: [monad-bft PR #2842](https://github.com/category-labs/monad-bft/pull/2842) * **\[Consensus]** Auth socket with codec abstraction, direct point-to-point routing * Ref: [monad-bft PR #2897](https://github.com/category-labs/monad-bft/pull/2897), [PR #2896](https://github.com/category-labs/monad-bft/pull/2896) * **\[Consensus]** Raptorcast: remove legacy packet parser * Ref: [monad-bft PR #2864](https://github.com/category-labs/monad-bft/pull/2864) * **\[Archive]** Support per-source AWS profile with isolation (`--profile` flag for archiver source/sink/fallback, reads from `.aws/credentials` and `.aws/config`) * Ref: [monad-bft PR #2865](https://github.com/category-labs/monad-bft/pull/2865) * **\[Execution]** Refactor chain: remove `static_validate_header` and `validate_transaction` virtual methods * Ref: [monad (execution) PR #2049](https://github.com/category-labs/monad/pull/2049) ## v0.13.1 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2026-03-16 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-03-16 | | | [`devnet`](/developer-essentials/testnets#tempnet) | N/A | | Tags or hashes: * `monad-bft` (consensus): [tag `v0.13.1`](https://github.com/category-labs/monad-bft/releases/tag/v0.13.1) (`7db6cea2`) * `monad` (execution): [tag `v0.13.1`](https://github.com/category-labs/monad/releases/tag/v0.13.1) (`722cbf5b5`) > **Note:** This is a patch release based on v0.13.0 to fix State Archive node RPC issues. The monad (execution) tag points to the same commit as v0.13.0. #### Notable robustness changes * **\[RPC]** Use latest voted as fallback for last\_proposed * Ref: [monad-bft PR #2857](https://github.com/category-labs/monad-bft/pull/2857) * Fixes State Archive node RPC returning stale block data when `last_proposed` is unavailable ## v0.13.0 (MONAD\_NINE) | Network | Date released | Hardfork timestamp | Comment | | ------------------------------------------------------ | ------------- | ------------------------------- | ---------------------- | | [`mainnet`](/developer-essentials/network-information) | 2026-03-12 | 1773930600(2026-03-19 14:30UTC) | `MONAD_NINE` hard fork | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-03-05 | 1773153000(2026-03-10 14:30UTC) | `MONAD_NINE` hard fork | | [`devnet`](/developer-essentials/testnets#tempnet) | N/A | N/A | | Tags or hashes: * `monad-bft` (consensus): [tag `v0.13.0`](https://github.com/category-labs/monad-bft/releases/tag/v0.13.0) * `monad` (execution): [tag `v0.13.0+1`](https://github.com/category-labs/monad/releases/tag/v0.13.0+1) > **Note:** The canonical release version is `v0.13.0`. The `+1` build metadata on the monad execution tag indicates a post-release rebuild that includes the reserve balance precompile fallback cost fix ([PR #2109](https://github.com/category-labs/monad/pull/2109)). #### Highlights * **RPC: `latest` block tag: `Finalized` -> `Proposed`** — queries with the `latest` block tag now return data from the latest proposed block, reducing query latency for `eth_getBalance`, `eth_call`, and other state queries. * **RPC: `eth_sendRawTransactionSync`: `Voted` -> `Proposed`** — earlier receipts when using the `eth_sendRawTransactionSync` method. * **Websocket: `newHeads/logs`: `Finalized` -> `Voted`** — earlier notifications for websocket subscribers to either `newHeads` or `logs`. #### Notable protocol changes All updates below are gated by the `MONAD_NINE` revision * **\[EVM]** [MIP-3](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-3.md): Linear memory implementation * Ref: [monad PR #2032](https://github.com/category-labs/monad/pull/2032) * **\[Execution]** [MIP-4](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-4.md): Reserve balance precompile * Ref: [monad PR #2040](https://github.com/category-labs/monad/pull/2040) (scaffolding) * Ref: [monad PR #2065](https://github.com/category-labs/monad/pull/2065) (cached/incremental checks) * Ref: [monad PR #2086](https://github.com/category-labs/monad/pull/2086) (add dippedIntoReserve) * Ref: [monad PR #2106](https://github.com/category-labs/monad/pull/2106) (dippedIntoReserve argument length error) * Ref: [monad PR #2109](https://github.com/category-labs/monad/pull/2109) (fallback cost fix for clean spec) * Ref: [monad PR #2037](https://github.com/category-labs/monad/pull/2037) (skip reserve checks for init-selfdestruct) * **\[EVM]** [MIP-5](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-5.md): Activate Osaka fork (CLZ opcode) * Ref: [monad PR #2024](https://github.com/category-labs/monad/pull/2024) > **Hard Fork:** This release activates MONAD\_NINE at timestamp `1773153000` (testnet) / `1773930600` (mainnet). Nodes must upgrade before activation. #### Notable RPC/SDK changes * **\[RPC]** Latest blocktag uses proposed blocks * Ref: [monad-bft PR #2675](https://github.com/category-labs/monad-bft/pull/2675) * **\[RPC]** Use voted blocks in websocket notifications * Ref: [monad-bft PR #2799](https://github.com/category-labs/monad-bft/pull/2799) * **\[RPC]** Add experimental flag to RPC doc macro * Ref: [monad-bft PR #2773](https://github.com/category-labs/monad-bft/pull/2773) * **\[RPC]** Refactor RPC middleware for improved request handling * Ref: [monad-bft PR #2805](https://github.com/category-labs/monad-bft/pull/2805) * **\[RPC]** Add decompression guard to RPC * Ref: [monad-bft PR #2793](https://github.com/category-labs/monad-bft/pull/2793) #### Notable robustness changes * **\[Consensus]** Add signature verifier for Raptorcast * Ref: [monad-bft PR #2747](https://github.com/category-labs/monad-bft/pull/2747) * **\[Consensus]** Fix reactivate logic in r10 decoder * Ref: [monad-bft PR #2740](https://github.com/category-labs/monad-bft/pull/2740) * **\[Execution]** Fix buffer overflow in C->Rust logging * Ref: [monad-bft PR #2769](https://github.com/category-labs/monad-bft/pull/2769) * **\[Consensus]** Add global connect rate limit to wireauth * Ref: [monad-bft PR #2765](https://github.com/category-labs/monad-bft/pull/2765) * **\[Consensus]** Validate ping/pong source address against name record in peer discovery * Ref: [monad-bft PR #2752](https://github.com/category-labs/monad-bft/pull/2752) * **\[Consensus]** Persist voted\_head in ledger * Ref: [monad-bft PR #2744](https://github.com/category-labs/monad-bft/pull/2744) * **\[Consensus]** Add dual UDP packet sender for dataplane * Ref: [monad-bft PR #2746](https://github.com/category-labs/monad-bft/pull/2746) * **\[Execution]** Fix ThreadSanitizer race by joining bootstrap fiber before thread exit * Ref: [monad PR #2053](https://github.com/category-labs/monad/pull/2053) * **\[Execution]** Include account balance in selfdestruct tracer frame * Ref: [monad PR #2039](https://github.com/category-labs/monad/pull/2039) * **\[Consensus]** Ensure proposed head is on canonical chain * Ref: [monad-bft PR #2756](https://github.com/category-labs/monad-bft/pull/2756) * **\[Execution]** Fix sentinel collision in compact virtual chunk offset * Ref: [monad PR #2083](https://github.com/category-labs/monad/pull/2083) * **\[Execution]** Fix potential race condition in execute\_block\_transactions * Ref: [monad PR #2092](https://github.com/category-labs/monad/pull/2092) #### Notable internal changes * **\[Consensus]** Remove unneeded channels when secondary raptorcast is disabled * Ref: [monad-bft PR #2808](https://github.com/category-labs/monad-bft/pull/2808) * **\[Consensus]** Upgrade alloy to stable release * Ref: [monad-bft PR #2792](https://github.com/category-labs/monad-bft/pull/2792) * **\[Execution]** Add `evm-as` syntactic sugar for VM utilities * Ref: [monad PR #2097](https://github.com/category-labs/monad/pull/2097) * **\[Consensus]** Parametrize wireauth metrics for better observability * Ref: [monad-bft PR #2692](https://github.com/category-labs/monad-bft/pull/2692) * **\[Consensus]** Cleanup block persist implementation * Ref: [monad-bft PR #2767](https://github.com/category-labs/monad-bft/pull/2767) * **\[Consensus]** Fix wip extension in block-persist * Ref: [monad-bft PR #2776](https://github.com/category-labs/monad-bft/pull/2776) * **\[Consensus]** Remove legacy `do_local_insert` * Ref: [monad-bft PR #2728](https://github.com/category-labs/monad-bft/pull/2728) * **\[Consensus]** Add metrics for raptorcast deserialize failures * Ref: [monad-bft PR #2789](https://github.com/category-labs/monad-bft/pull/2789) * **\[RPC]** Reduce RPC event server broadcast channel size * Ref: [monad-bft PR #2815](https://github.com/category-labs/monad-bft/pull/2815) * **\[Consensus]** Pre-TFM base fee cleanup * Ref: [monad-bft PR #2664](https://github.com/category-labs/monad-bft/pull/2664) * **\[Consensus]** Remove pre-TFM reserve balance logic * Ref: [monad-bft PR #2622](https://github.com/category-labs/monad-bft/pull/2622) * **\[RPC]** Simplify hex encoding/decoding in RPC * Ref: [monad-bft PR #2806](https://github.com/category-labs/monad-bft/pull/2806) * **\[Archive]** Add exists(key) to kvstore * Ref: [monad-bft PR #2688](https://github.com/category-labs/monad-bft/pull/2688) ## v0.12.7 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2026-01-29 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-01-20 | | | [`devnet`](/developer-essentials/testnets#tempnet) | N/A | | Tags or hashes: * `monad-bft`: [tag `v0.12.7`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.7) * `monad`: [tag `v0.12.7`](https://github.com/category-labs/monad/releases/tag/v0.12.7) #### Notable RPC/SDK changes * **\[RPC]** Allow block hash as block identifier in `eth_estimateGas` * Ref: [monad-bft PR #2676](https://github.com/category-labs/monad-bft/pull/2676) * **\[RPC]** Use `RawValue` for RPC id to preserve original JSON types * Reduces effectiveness of DoS attacks using large JSON arrays/dicts in request IDs * Ref: [monad-bft PR #2455](https://github.com/category-labs/monad-bft/pull/2455) #### Notable robustness changes * **\[EVM]** Fix undefined behavior in modexp precompile * Ref: [monad PR #2045](https://github.com/category-labs/monad/pull/2045) * **\[Consensus]** Remove consecutive sequence number assertion * Ref: [monad-bft PR #2704](https://github.com/category-labs/monad-bft/pull/2704) * **\[Consensus]** Improve liveness when advancing round using TC from timeout messages * Ref: [monad-bft PR #2701](https://github.com/category-labs/monad-bft/pull/2701) * **\[Consensus]** Disallow creating an empty validator set * Ref: [monad-bft PR #2682](https://github.com/category-labs/monad-bft/pull/2682) #### Notable internal changes * **\[Consensus]** Add security tests for `secp256k1` * Adds Wycheproof tests, malleability tests, and security unit tests * Ref: [monad-bft PR #2706](https://github.com/category-labs/monad-bft/pull/2706) * **\[Node ops]** Update `rkyv` and other dependencies * Ref: [monad-bft PR #2702](https://github.com/category-labs/monad-bft/pull/2702) * **\[Consensus]** Return bound socket addresses synchronously in dataplane * Ref: [monad-bft PR #2653](https://github.com/category-labs/monad-bft/pull/2653) * **\[Consensus]** Rename state backend cache for clarity * Ref: [monad-bft PR #2699](https://github.com/category-labs/monad-bft/pull/2699) ## v0.12.6 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2026-01-14 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2026-01-07 | | | [`devnet`](/developer-essentials/testnets#tempnet) | N/A | | Tags or hashes: * `monad-bft`: [tag `v0.12.6`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.6) * `monad`: [tag `v0.12.6`](https://github.com/category-labs/monad/releases/tag/v0.12.6) #### Notable RPC/SDK changes * **\[RPC]** Fix depth bug in selfdestructing call frames * Ref: [monad PR #1977](https://github.com/category-labs/monad/pull/1977) * **\[RPC]** Prestate tracer conformance fixes * Ref: [monad PR #1946](https://github.com/category-labs/monad/pull/1946) #### Notable robustness changes * **\[Consensus]** Add signature verification rate limiting with authenticated peer bypass * Ref: [monad-bft PR #2601](https://github.com/category-labs/monad-bft/pull/2601) * **\[Consensus]** Validate confirm group message before updating peers * Ref: [monad-bft PR #2680](https://github.com/category-labs/monad-bft/pull/2680) * **\[Consensus]** Use Tai64N directly for wireauth timestamp comparison * Ref: [monad-bft PR #2678](https://github.com/category-labs/monad-bft/pull/2678) * **\[Consensus]** Improve RaptorCast decoding cache eviction * Ref: [monad-bft PR #2651](https://github.com/category-labs/monad-bft/pull/2651) * **\[Consensus]** Fix blocksync in-flight request tracking on cache hydration * Ref: [monad-bft PR #2635](https://github.com/category-labs/monad-bft/pull/2635) * **\[Execution]** Update triedb voted metadata before executing proposal * Solves a race condition between Voted updates through websocket and corresponding JSON-RPC calls * Ref: [monad PR #1964](https://github.com/category-labs/monad/pull/1964) * **\[Node ops]** Persist peers periodically for improved discovery resilience * Ref: [monad-bft PR #2633](https://github.com/category-labs/monad-bft/pull/2633) #### Notable performance changes * **\[Consensus]** Boost dataplane throughput with ring buffer implementation * Ref: [monad-bft PR #2596](https://github.com/category-labs/monad-bft/pull/2596) * **\[Execution]** Native implementation for `MLOAD`, `MSTORE`, `MSTORE8`, `CALLDATALOAD` opcodes * Ref: [monad PR #1963](https://github.com/category-labs/monad/pull/1963) * **\[Execution]** Improve snapshot write performance * Ref: [monad PR #1973](https://github.com/category-labs/monad/pull/1973), [monad PR #1960](https://github.com/category-labs/monad/pull/1960) * **\[Execution]** Optimize database history length adjustment with binary search * Ref: [monad PR #1922](https://github.com/category-labs/monad/pull/1922) * **\[Execution]** Unify single buffer and scatter read handling in AsyncIO * Ref: [monad PR #1944](https://github.com/category-labs/monad/pull/1944) #### Notable internal changes * **\[Node ops]** Docker single-node container updates for prebuilt images * Fixes to [Local Docker installation](https://github.com/category-labs/monad-bft/blob/master/README.md#using-pre-built-images) * Ref: [monad-bft PR #2674](https://github.com/category-labs/monad-bft/pull/2674) * **\[Consensus]** Add support for generic txpool sidecars * Enables IPC transaction priority and forwarding controls * Ref: [monad-bft PR #2557](https://github.com/category-labs/monad-bft/pull/2557) * **\[EVM]** Osaka fork preparation * Modexp gas changes and upper bound (EIP-7823/EIP-7883) * Implement `CLZ` opcode * Ref: [monad PR #1970](https://github.com/category-labs/monad/pull/1970), [monad PR #1981](https://github.com/category-labs/monad/pull/1981) * **\[Node ops]** Accept `--hyphen-style` long options in CLI * Ref: [monad PR #2005](https://github.com/category-labs/monad/pull/2005) * **\[Node ops / Archive]** Archive pipeline improvements * Add `WritePolicy` to `KVStore` for conditional write protection (`NoClobber`) * Add BFT-uploading stats logging * Ref: [monad-bft PR #2649](https://github.com/category-labs/monad-bft/pull/2649), [monad-bft PR #2661](https://github.com/category-labs/monad-bft/pull/2661) * **\[Node ops]** Simplify RPC txpool status tracking * Ref: [monad-bft PR #2644](https://github.com/category-labs/monad-bft/pull/2644) * **\[Node ops]** Snapshot tooling improvements in `monad_cli` * Add `--dump_concurrency_limit` parameter * Add sharding support for distributed snapshot creation * Ref: [monad PR #1967](https://github.com/category-labs/monad/pull/1967), [monad PR #1965](https://github.com/category-labs/monad/pull/1965) * **\[Node ops]** Add RaptorCast decoding cache metrics * Ref: [monad-bft PR #2667](https://github.com/category-labs/monad-bft/pull/2667) * **\[Execution]** Add `CommitBuilder` API to execution database * Ref: [monad PR #1968](https://github.com/category-labs/monad/pull/1968) * **\[Execution]** Remove legacy `using_chunks_for_root_offsets` metadata field * Ref: [monad PR #1943](https://github.com/category-labs/monad/pull/1943) * **\[Node ops / Txgen]** Add ERC-4337 + EIP-7702 generator (`erc4337_7702_bundled`) * Ref: [monad-bft PR #2628](https://github.com/category-labs/monad-bft/pull/2628) * **\[Node ops]** Add systemd service scripts for `blockcapd` and execution events archivers * Ref: [monad-bft PR #2590](https://github.com/category-labs/monad-bft/pull/2590) * **\[SDK]** Add `monad_event_resolve_ring_file` API * Ref: [monad PR #1741](https://github.com/category-labs/monad/pull/1741) * **\[Execution]** Enable VM host exception handling outside VM * Ref: [monad PR #1990](https://github.com/category-labs/monad/pull/1990) ## v0.12.5 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------------------ | | [`mainnet`](/developer-essentials/network-information) | N/A | | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-12-16 | reset from genesis | | [`devnet`](/developer-essentials/testnets#tempnet) | N/A | | Tags or hashes: * `monad-bft`: [tag `v0.12.5`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.5) * `monad`: [tag `v0.12.5`](https://github.com/category-labs/monad/releases/tag/v0.12.5) #### Notable network changes * **\[Network reset]** `testnet` **reset from genesis** starting from `MONAD_EIGHT` * Increases total supply to 100 B, consistent with `mainnet` * Clears a path to eliminate dead code (revisions required for early `testnet`) * Ref: [monad PR #1983](https://github.com/category-labs/monad/pull/1983), [monad-bft PR #2673](https://github.com/category-labs/monad-bft/pull/2673) ## v0.12.4 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2025-12-05 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-12-05 | | | [`devnet`](/developer-essentials/testnets#tempnet) | 2025-12-05 | | Tags or hashes: * `monad-bft`: [tag `v0.12.4`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.4) * `monad`: [tag `v0.12.4`](https://github.com/category-labs/monad/releases/tag/v0.12.4) *(unchanged from v0.12.3)* #### Notable internal changes * **\[Node ops]** Fix peer discovery port configuration when constructing self name record * Use port from peer discovery config instead of bound socket when constructing self name record. This bug affected node operators using a NAT. * Ref: [monad-bft PR #2655](https://github.com/category-labs/monad-bft/pull/2655) ## v0.12.3 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2025-12-05 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-12-04 | | | [`devnet`](/developer-essentials/testnets#tempnet) | 2025-12-04 | | Tags or hashes: * `monad-bft`: [tag `v0.12.3`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.3) * `monad`: [tag `v0.12.3`](https://github.com/category-labs/monad/releases/tag/v0.12.3) #### Notable RPC/SDK changes * **\[RPC]** [EIP-7966](https://eips.ethereum.org/EIPS/eip-7966) (`eth_sendRawTransactionSync`) support * Ref: [monad-bft PR #2542](https://github.com/category-labs/monad-bft/pull/2542) * **\[RPC]** Populate `value` field on traces for staking precompile syscalls (added in `0.12.3-rpc-hotfix2`) * Ref: [monad PR #1938](https://github.com/category-labs/monad/pull/1938) #### Notable robustness changes * **\[Consensus]** Wire authentication protocol for UDP * Adds authenticated UDP communication to improve network security * Includes replay window adjustments for improved reliability * Ref: [monad-bft PR #2417](https://github.com/category-labs/monad-bft/pull/2417), [monad-bft PR #2544](https://github.com/category-labs/monad-bft/pull/2544), [monad-bft PR #2626](https://github.com/category-labs/monad-bft/pull/2626), [monad-bft PR #2091](https://github.com/category-labs/monad-bft/pull/2091) * **\[Execution]** Improve `statesync` shutdown and fix protocol corruption * Modified `statesync_server_recv()` to guarantee complete reads * Ref: [monad PR #1930](https://github.com/category-labs/monad/pull/1930) * **\[RPC]** Prevent reserve balance crash in RPC * Add reserve balance violation error messages in call frames * Ref: [monad PR #1932](https://github.com/category-labs/monad/pull/1932), [monad-bft PR #2629](https://github.com/category-labs/monad-bft/pull/2629) * This was already previously released in `v0.12.2-rpc-hotfix` * **\[Consensus]** Add timeout to `txpool` IPC stream * Prevents hanging connections in transaction pool communication * Ref: [monad-bft PR #2619](https://github.com/category-labs/monad-bft/pull/2619) * **\[Consensus]** Reject timeout certificates with empty signers * Adds validation to prevent malformed timeout certificates * Ref: [monad-bft PR #2630](https://github.com/category-labs/monad-bft/pull/2630) * **\[Consensus]** Verify RaptorCast chunk length is non-zero * Adds validation to prevent malformed RaptorCast chunks * Ref: [monad-bft PR #2638](https://github.com/category-labs/monad-bft/pull/2638) * **\[Execution]** Fix MPT restore bug that failed to preserve `version_lower_bound` * Ref: [monad PR #1955](https://github.com/category-labs/monad/pull/1955) * **\[Execution]** Fix move trie version forward bug * Erase versions that fall out of history range before moving forward to newer versions * Ref: [monad PR #1957](https://github.com/category-labs/monad/pull/1957) * **\[Execution]** Fix dangling pointers to intercode * Resolves memory safety issue with `get_code` and `read_code` functions * Ref: [monad PR #1941](https://github.com/category-labs/monad/pull/1941) * **\[Execution]** Add retries to `runloop_monad_ethblocks` * Ref: [monad PR #1953](https://github.com/category-labs/monad/pull/1953) #### Notable performance changes * **\[RPC]** Improve `eth_getLogs` performance * Reduce memory copies and unify receipt-to-logs processing * Ref: [monad-bft PR #2588](https://github.com/category-labs/monad-bft/pull/2588), [monad-bft PR #2591](https://github.com/category-labs/monad-bft/pull/2591), [monad-bft PR #2631](https://github.com/category-labs/monad-bft/pull/2631) * **\[Execution]** Fiber: add support for move-only functors * Improves execution efficiency by supporting move semantics * Ref: [monad PR #1936](https://github.com/category-labs/monad/pull/1936) #### Notable internal changes * **\[RPC / Node ops]** Allow RPC to run without `monad-bft` * Enables standalone RPC operation for improved deployment flexibility * Ref: [monad-bft PR #2613](https://github.com/category-labs/monad-bft/pull/2613) * **\[Node ops]** Add `--root-offsets-chunk-count` flag to `monad-mpt` to configure the number of chunks allocated for root offsets * Each chunk holds approximately 16.5M blocks. Previously, all nodes were hardcoded to use 2 chunks (max TrieDB capacity of \~33M blocks) * New default: 16 **must be a power of 2** * Note that the default value of 16 translates to approximately 268M blocks \~ 1240 days. In most cases the limiting factor is disk capacity which will auto-compact when filled to 80% * Ref: [monad PR #1937](https://github.com/category-labs/monad/pull/1937) * **\[Node ops]** Peer discovery improvements * Full nodes periodically pull validator name records for dynamic validator discovery * Add authenticated UDP port to name record * Ref: [monad-bft PR #2607](https://github.com/category-labs/monad-bft/pull/2607), [monad-bft PR #2538](https://github.com/category-labs/monad-bft/pull/2538) * **\[Node ops / Archive]** Archive infrastructure improvements * Refactor `monad-block-writer` for improved reliability with `--max-blocks-per-iter` configuration * Async backfill with traces-only archive support * Add `require-traces` archiver flag and indexer fallback source * Support for historical execution event archiving with generic directory archiving * **Potentially breaking: Remove `--start-block` from `monad-archiver` systemd service; operators must use imperative CLI to set start block** * Example: `monad-archiver set-start-block --block 1000000 --archive-sink s3://bucket-name/path` * Use `--async-backfill` flag to set the async-backfill marker instead of primary marker * Ref: [monad-bft PR #2610](https://github.com/category-labs/monad-bft/pull/2610), [monad-bft PR #2606](https://github.com/category-labs/monad-bft/pull/2606), [monad-bft PR #2598](https://github.com/category-labs/monad-bft/pull/2598), [monad-bft PR #2514](https://github.com/category-labs/monad-bft/pull/2514), [monad-bft PR #2612](https://github.com/category-labs/monad-bft/pull/2612), [monad-bft PR #2569](https://github.com/category-labs/monad-bft/pull/2569), [monad-bft PR #2623](https://github.com/category-labs/monad-bft/pull/2623) * **\[Node ops]** Networking configuration updates * Use default MTU 1500 * Add HDR histogram for broadcast latency tracking in `monad-executor` and `monad-raptorcast` * Ref: [monad-bft PR #2576](https://github.com/category-labs/monad-bft/pull/2576), [monad-bft PR #2602](https://github.com/category-labs/monad-bft/pull/2602) * **\[Consensus]** RaptorCast improvements * Clear histogram every 30s and keep only p99 for better latency tracking * Fix timer update after receiving control messages to ensure proper keepalives * Ref: [monad-bft PR #2627](https://github.com/category-labs/monad-bft/pull/2627), [monad-bft PR #2637](https://github.com/category-labs/monad-bft/pull/2637) * **\[Consensus]** Dataplane: refactor to use socket handles * Prepares dataplane for future extension with authenticated sockets * Ref: [monad-bft PR #2458](https://github.com/category-labs/monad-bft/pull/2458) * **\[Execution]** Replay monad: always execute first block off latest finalized state * Ref: [monad PR #1927](https://github.com/category-labs/monad/pull/1927) * **\[Node ops]** Txgen improvements: README and guide, nonce gaps and legacy tx options, ERC20 pools, Uniswap v3 mode, NFT sale mode, websocket and RPC request generation * Ref: [monad-bft PR #2568](https://github.com/category-labs/monad-bft/pull/2568), [monad-bft PR #2567](https://github.com/category-labs/monad-bft/pull/2567), [monad-bft PR #2577](https://github.com/category-labs/monad-bft/pull/2577), [monad-bft PR #2600](https://github.com/category-labs/monad-bft/pull/2600), [monad-bft PR #2527](https://github.com/category-labs/monad-bft/pull/2527), [monad-bft PR #2394](https://github.com/category-labs/monad-bft/pull/2394), [monad-bft PR #2608](https://github.com/category-labs/monad-bft/pull/2608) ## v0.12.2 | Network | Date released | Comment | | ------------------------------------------------------ | ------------- | ------- | | [`mainnet`](/developer-essentials/network-information) | 2025-11-19 | | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-11-18 | | | [`devnet`](/developer-essentials/testnets#tempnet) | 2025-11-18 | | Tags or hashes: * `monad-bft`: [tag `v0.12.2-rc`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.2-rc) * `monad`: [tag `v0.12.2-rc`](https://github.com/category-labs/monad/releases/tag/v0.12.2-rc) #### Notable protocol changes All updates below are gated by the `MONAD_EIGHT` revision * **\[Execution]** Reserve balance checks now use the final state code hash. This makes checks slightly more liberal (less likely to revert transactions during execution), and also makes the implementation closer to the [Coq model](https://category-labs.github.io/category-research/reserve-balance-coq-proofs/monad.proofs.reservebal.html#execValidatedTx) that was proven correct under some assumptions. * This changes the behavior of the following corner case: * In transaction 1, account X is sent a small amount of MON (e.g. 5 MON) * Then (much later) in transaction 2, someone both deploys a smart contract wallet at account X and sends MON out of that smart contract. * Previously, transaction 2's execution would treat account X as an EOA and apply reserve balance checks on it, thus reverting the transaction because it would be a dipping transaction that was not pre-authorized by consensus. * After this change, account X will be treated as a smart contract account and will be omitted from reserve balance checks. * Note that smart contract addresses are deterministic, making it effectively impossible for an address that receives a code deployment to also be an EOA (i.e. to correspond to a private key). * Ref: [monad PR #1916](https://github.com/category-labs/monad/pull/1916), [monad PR #1917](https://github.com/category-labs/monad/pull/1917) * **\[EVM]** Reduce pagination on staking precompile inverse mappings from 100 to 50 * Applies to `precompile_get_delegations()` and `precompile_get_delegators()` * Ref: [monad PR #1920](https://github.com/category-labs/monad/pull/1920) #### Notable deployment changes * **\[SDK]** Execution events no longer require a special "preview" release to enable all EVM notifications (there are no more `--exec-events` special versions) * **\[SDK]** Fix race condition in event ring create * Ref: [monad PR #1914](https://github.com/category-labs/monad/pull/1914) #### Notable robustness changes * **\[RPC]** Fix `eth_feeHistory` * Ref: [monad-bft PR #2366](https://github.com/category-labs/monad-bft/pull/2366) * **\[Consensus]** Update transaction classification logic to apply emptying before delegation * Improves delegation ordering in `try_apply_block_fees` * Ref: [monad-bft PR #2561](https://github.com/category-labs/monad-bft/pull/2561) * **\[Execution]** Failsafe sanity checks for reward system transactions * Ref: [monad PR #1921](https://github.com/category-labs/monad/pull/1921) * **\[RPC]** Check reserve balance in `eth_call` * Prevent simulated transactions from violating reserve balance * Call `revert_transaction` in `eth_call` implementation * Ref: [monad PR #1912](https://github.com/category-labs/monad/pull/1912) * **\[Node ops]** Fix remotely fetched `forkpoint.toml` write format * Ensures forkpoint files are written in the correct format when fetched from remote sources * Ref: [monad-bft PR #2554](https://github.com/category-labs/monad-bft/pull/2554) * **\[Node ops]** Fix `monad-keystore` key corruption issue * `to_bls` and `to_secp` now consume secret to prevent corrupted keystore files * Add tests for command line keystore app to verify all flags combinations work correctly * Ref: [monad-bft PR #2553](https://github.com/category-labs/monad-bft/pull/2553) * **\[Execution]** Fix bad write offset in `storage_pool` `try_trim_contents()` when bytes to start TRIM is not a multiple of disk page size * Ref: [monad PR #1908](https://github.com/category-labs/monad/pull/1908) * **\[RPC]** Use txpool bridge channel size for ratelimiting * Prevents `try_send` method from failing by using channel capacity for `eth_sendRawTransaction` ratelimiting * Ref: [monad-bft PR #2506](https://github.com/category-labs/monad-bft/pull/2506) * **\[Consensus]** Improve RaptorCast message validation * Add sanity check for `chunk_id` range in message parsing * Reject overflowing `merkle_leaf_idx` in merkle proof construction * Update calculation of `encoded_symbol_capacity` in decoder * Ref: [monad-bft PR #2541](https://github.com/category-labs/monad-bft/pull/2541) * **\[Consensus]** Improve RaptorCast group invite response checks * Add sanity check to prevent processing group invite accept after reject from same full node * Ref: [monad-bft PR #2574](https://github.com/category-labs/monad-bft/pull/2574) * **\[Consensus]** Update txpool insertion policy - make balance check stricter * Ref: [monad-bft PR #2575](https://github.com/category-labs/monad-bft/pull/2575) #### Notable performance changes * **\[Consensus]** Add dataplane warnings when priority queues are overrun * Sets total priority queue capacity to 100MB per priority queue (200MB total max) * Messages will be dropped with warnings logged if capacity is exceeded * Ref: [monad-bft PR #2559](https://github.com/category-labs/monad-bft/pull/2559) * **\[Execution / Node ops]** Remove read-only database `io_uring` SQ poll thread * **IMPORTANT**: Operators with `monad-execution` systemctl overrides should make the appropriate changes to eliminate the `--ro_sq_thread_cpu` parameter as demonstrated in [monad-bft PR #2558](https://github.com/category-labs/monad-bft/pull/2558). Overrides are commonly set when running an RPC or archive node with `--trace_calls` enabled. * Reduces thread contention during statesync, improving block execution performance * Make `ro_sq_thread_cpu` argument optional, remove from default configuration * Ref: [monad PR #1911](https://github.com/category-labs/monad/pull/1911), [monad-bft PR #2558](https://github.com/category-labs/monad-bft/pull/2558) * **\[RPC]** Move recover authority logic inside submitted lambda for `eth_call` * Avoid blocking on fiber promises when called from rust thread, preventing performance issues * Replace parallelization with single call to `recover_authority()` since `eth_call` transactions have single authority * Ref: [monad PR #1827](https://github.com/category-labs/monad/pull/1827) * **\[Execution]** Remove `iopoll` support * Ref: [monad PR #1910](https://github.com/category-labs/monad/pull/1910) #### Notable internal changes * **\[Node ops]** Add `monad-version` crate for git version info in binaries * Ref: [monad-bft PR #2572](https://github.com/category-labs/monad-bft/pull/2572) * **\[Node ops]** Add CLI argument for number of fibers in trace transaction execution pool (default: 100) * Ref: [monad-bft PR #2584](https://github.com/category-labs/monad-bft/pull/2584) * **\[Node ops]** Update block reward for mainnet * Ref: [monad-bft PR #2582](https://github.com/category-labs/monad-bft/pull/2582) * **\[Node ops]** Add block writer binary for testing and debugging * Ref: [monad-bft PR #2546](https://github.com/category-labs/monad-bft/pull/2546) * **\[Node ops]** Make cruft timer retention configurable * Add environment variable support for artifact retention times (override in `/home/monad/.env`) * Ref: [monad-bft PR #2548](https://github.com/category-labs/monad-bft/pull/2548) * **\[Node ops]** Add warning when restoring from snapshot (hard reset) is required * Prompt node operator when local tip is behind by `STATESYNC_BLOCK_THRESHOLD` * Ref: [monad-bft PR #2581](https://github.com/category-labs/monad-bft/pull/2581) * **\[Node ops]** Update `monad-cruft` permissions to monad user * Ref: [monad-bft PR #2556](https://github.com/category-labs/monad-bft/pull/2556) * **\[Node ops]** Fix txpool metrics recording for transaction replacements * Ref: [monad-bft PR #2564](https://github.com/category-labs/monad-bft/pull/2564) * **\[RPC]** Operator can configure RPC worker threads * Ref: [monad-bft PR #2562](https://github.com/category-labs/monad-bft/pull/2562) * **\[Archive]** Add TOML config file support instead of CLI-only configuration * Enables `monad-archiver` binary to use `--config` flag for configuration * CLI arguments continue to work and take precedence over config file values * Ref: [monad-bft PR #2570](https://github.com/category-labs/monad-bft/pull/2570) * **\[Node ops]** Add random mutations to `txgen` for improved testing * Ref: [monad-bft PR #2543](https://github.com/category-labs/monad-bft/pull/2543) * **\[Node ops]** Add reserve balance coherency tests * Ref: [monad-bft PR #2573](https://github.com/category-labs/monad-bft/pull/2573) * **\[SDK]** Record execution events * Includes state access and system transactions * Ref: [monad PR #1906](https://github.com/category-labs/monad/pull/1906), [monad PR #1907](https://github.com/category-labs/monad/pull/1907), [monad PR #1913](https://github.com/category-labs/monad/pull/1913) * **\[SDK]** Explicitly clear flow info that doesn't apply to certain event types * Prevents poorly written event clients from reading invalid data * Ref: [monad PR #1919](https://github.com/category-labs/monad/pull/1919) * **\[Execution]** Improve assertion messages with `MONAD_ASSERT_PRINTF` * Ref: [monad PR #1918](https://github.com/category-labs/monad/pull/1918) * **\[Node ops]** Add statesync server metrics * Add `SyncDone` metric to track successes and failures * Add request timing metric for statesync server * Ref: [monad-bft PR #2555](https://github.com/category-labs/monad-bft/pull/2555) * **\[Node ops]** Disambiguate triedb error logs * Improve error message clarity by making each log call unique * Ref: [monad-bft PR #2579](https://github.com/category-labs/monad-bft/pull/2579) * **\[Node ops]** Update devnet chain configs * Shorten default devnet epoch length from 50,000 blocks to 10,000 blocks and epoch start delay from 5,000 rounds to 1,000 rounds * Ref: [monad-bft PR #2578](https://github.com/category-labs/monad-bft/pull/2578) * **\[Node ops]** Update test infrastructure * Switch `test_state` and `precompiles_test` tests to `TraitsTest` * Add `runloop_monad_ethblocks` to CLI for Monad EVM block replay * Ref: [monad PR #1905](https://github.com/category-labs/monad/pull/1905), [monad PR #1894](https://github.com/category-labs/monad/pull/1894) * **\[Execution]** Remove unused cross-thread pipe messaging infrastructure * Ref: [monad PR #1915](https://github.com/category-labs/monad/pull/1915) * **\[Node ops]** Keep manytrace agent alive in node state * Ref: [monad-bft PR #2583](https://github.com/category-labs/monad-bft/pull/2583) * **\[Node ops]** Update keystore documentation * Ref: [monad-bft PR #2380](https://github.com/category-labs/monad-bft/pull/2380) * **\[Node ops]** Add more logging for block validation errors * Ref: [monad-bft PR #2550](https://github.com/category-labs/monad-bft/pull/2550) * **\[Node ops]** Log round leader when processing certificates * Ref: [monad-bft PR #2580](https://github.com/category-labs/monad-bft/pull/2580) ## v0.12.1 | Network | Date released | Comment | | --------------------------------------------------- | ------------- | ------- | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-11-12 | | | [`devnet`](/developer-essentials/testnets#tempnet) | 2025-11-12 | | Tags or hashes: * `monad-bft`: [tag `v0.12.1`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.1) * `monad`: [tag `v0.12.1`](https://github.com/category-labs/monad/releases/tag/v0.12.1) #### Notable RPC/SDK changes * **\[RPC]** Support for prestate and statediff tracing for the following RPC endpoints: * `debug_traceTransaction` * `debug_traceBlockByNumber` * `debug_traceBlockByHash` * Ref: [monad PR #1753](https://github.com/category-labs/monad/pull/1753), [monad-bft PR #2403](https://github.com/category-labs/monad-bft/pull/2403) * **\[RPC]** New endpoint `eth_createAccessList` * Ref: [monad PR #1748](https://github.com/category-labs/monad/pull/1748), [monad-bft PR #2519](https://github.com/category-labs/monad-bft/pull/2519) * **\[RPC]** Fix serialization for `to` address in historical traces * Follow on to [monad-bft PR #2516](https://github.com/category-labs/monad-bft/pull/2516) implemented in `v0.12.0` (in a historical context) * Ref: [monad-bft PR #2523](https://github.com/category-labs/monad-bft/pull/2523), [monad PR #1861](https://github.com/category-labs/monad/pull/1861) * **\[RPC]** Remove artificial block height floor for `eth_call` and RPC queries * Previously limited queries using `latest` and `safe` block tags to `finalized + 1`, now uses actual latest voted block * Ref: [monad-bft PR #2148](https://github.com/category-labs/monad-bft/pull/2148) * **\[RPC]** Fix RPC errors when requesting traces for call frames that include a self-destructing transaction * Ref: [monad PR #1887](https://github.com/category-labs/monad/pull/1887) * **\[RPC]** Fix inconsistent results being reported when using state overrides in RPC calls * Ref: [monad PR #1812](https://github.com/category-labs/monad/pull/1812) * **\[RPC]** Fix for a possible denial-of-service attack on RPC nodes by overriding balances in the staking contract * Ref: [monad PR #1903](https://github.com/category-labs/monad/pull/1903) #### Notable robustness changes * **\[Node ops/Consensus]** Validators limit statesync service to validators and whitelisted full nodes * Full nodes service all requests * Ref: [monad-bft PR #2524](https://github.com/category-labs/monad-bft/pull/2524) * **\[Consensus]** More intelligent txpool eviction logic * Ref: [monad-bft PR #2525](https://github.com/category-labs/monad-bft/pull/2525) * **\[Consensus]** Start vote pacing timer on startup * Ref: [monad-bft PR #2528](https://github.com/category-labs/monad-bft/pull/2528) * **\[Consensus]** Fix historical logs `selfdestruct` issue * Ref: [monad-bft PR #2531](https://github.com/category-labs/monad-bft/pull/2531) * **\[Consensus]** Graceful handling of gas limit check * Ref: [monad-bft PR #2518](https://github.com/category-labs/monad-bft/pull/2518) * **\[Consensus]** Remove 0-stake validator support * Support for 0-stake validators in the validator set was used as a workaround for lack of full-node support in the past and should no longer be supported * Ref: [monad-bft PR #2545](https://github.com/category-labs/monad-bft/pull/2545) #### Notable performance changes * **\[Consensus]** Bias statesync requests to responsive peers * Support dynamic peer expansion/contraction * Nodes no longer panic if all peers are pruned (e.g., not whitelisted by any init peers) * Ref: [monad-bft PR #2536](https://github.com/category-labs/monad-bft/pull/2536) * **\[Consensus]** Drop blocksync requests that hit disk after cache hydration (\~7min after startup) * Ref: [monad-bft PR #2533](https://github.com/category-labs/monad-bft/pull/2533) * **\[Consensus]** Dataplane: replace interval with sleep * Ref: [monad-bft PR #2517](https://github.com/category-labs/monad-bft/pull/2517) * **\[Execution]** Replace data structures in `VersionStack` code to improve performance when pushing and popping EVM call frames. * Ref: [monad PR #1802](https://github.com/category-labs/monad/pull/1802), [monad PR #1877](https://github.com/category-labs/monad/pull/1877) #### Notable internal changes * **\[Node ops]** Try fetching remote `forkpoint.toml` and `validators.toml` on startup * **IMPORTANT**: Nodes now automatically attempt to download configuration files from remote locations on startup, no "Soft Reset" required. This remote fetch is subject to a configurable remote threshold * The following env variables must be set to enable this behavior: * `REMOTE_FORKPOINT_URL` * `REMOTE_VALIDATORS_URL` * Ref: [monad-bft PR #2484](https://github.com/category-labs/monad-bft/pull/2484), [monad-bft PR #2534](https://github.com/category-labs/monad-bft/pull/2534) * **\[Node ops]** Serialize forkpoints in both TOML and RLP formats, with RLP as source of truth; cleanup `get_latest_config` branches * Ref: [monad-bft PR #2521](https://github.com/category-labs/monad-bft/pull/2521) * **\[Node ops/Consensus]** Dynamically expand statesync upstream peers * Always initializes to `statesync.init_peers` (no longer defaults to bootstrap peers) * If `expand_to_group` is enabled, expands to validator set (for validators) and/or secondary raptorcast peers (for public / prioritized full nodes) * Ref: [monad-bft PR #2535](https://github.com/category-labs/monad-bft/pull/2535) * **\[Node ops/Consensus]** Full nodes no longer default to blocksync from validator set * Dedicated full nodes must specify upstream as blocksync override * **IMPORTANT**: prioritized/public full nodes can only blocksync *after* joining secondary raptorcast group. `refresh_period` and `invite_lookahead` recommended to be lowered to 20 seconds * Ref: [monad-bft PR #2532](https://github.com/category-labs/monad-bft/pull/2532) * **\[Consensus]** Use epoch field to encode round number for secondary raptorcast * Fixes issue where syncing full nodes receive unsolicited chunks from expired groups * Ref: [monad-bft PR #2442](https://github.com/category-labs/monad-bft/pull/2442) * **\[Consensus]** Add statesync server pending requests metric (better load visibility) * Ref: [monad-bft PR #2530](https://github.com/category-labs/monad-bft/pull/2530) * **\[Node ops]** Import `monad-execution` API changes * Ref: [monad-bft PR #2522](https://github.com/category-labs/monad-bft/pull/2522) ## v0.12.0 | Network | Date released | Comment | | --------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-11-05 | Revision is set to [`MONAD_SEVEN`](https://www.notion.so/developer-essentials/changelog/README.md#revisions) at 2025-11-05 14:30 GMT | | [`devnet`](/developer-essentials/testnets#tempnet) | 2025-11-03 | Devnet was reset with this new release, with revision set to [`MONAD_SEVEN`](https://www.notion.so/developer-essentials/changelog/README.md#revisions) | Tags or hashes: * `monad-bft`: [tag `v0.12.0`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.0) * `monad`: [tag `v0.12.0`](https://github.com/category-labs/monad/releases/tag/v0.12.0) #### Notable protocol changes * **\[EVM/Network params]** [Opcode/precompile repricing](/developer-essentials/opcode-pricing) * Opcode pricing: increase cost of cold storage access * Precompile pricing: better align a few underpriced precompile gas costs with the associated hardware / latency costs * Ref: [monad PR #1600](https://github.com/category-labs/monad/pull/1600) (for most of the repricings) and [monad PR #1700](https://github.com/category-labs/monad/pull/1700) (for `CREATE`/`CREATE2` repricing), [monad PR #1830](https://github.com/category-labs/monad/pull/1830) (for reduced v2 scope), [monad PR #1871](https://github.com/category-labs/monad/pull/1871) (activation for `MONAD_SEVEN`) * Gated by `MONAD_SEVEN` revision * **\[Consensus/Network params]** \[Staking] Increase `BLOCK_REWARD_MON` from 1 MON to 25 MON * Note that this is not gated by `MONAD_SEVEN` (execution hard fork) but by a specific epoch start for `testnet` * Ref: [monad-bft PR #2511](https://github.com/category-labs/monad-bft/pull/2511) #### Notable robustness changes * **\[Consensus]** Consensus message prioritization in dataplane * Priority queues for UDP traffic ensure consensus messages are delivered before other traffic * All consensus messages to validators are published and rebroadcast with high priority * Ref: [monad-bft PR #2352](https://github.com/category-labs/monad-bft/pull/2352), [monad-bft PR #2354](https://github.com/category-labs/monad-bft/pull/2354) * **\[Consensus]** DoS protection: Reject repeated invalid consensus messages * Prevents malicious validators from consuming CPU time by repeatedly sending invalid BLS signatures * Ref: [monad-bft PR #2445](https://github.com/category-labs/monad-bft/pull/2445) * **\[Consensus]** Leader synchronization improvements * Forward AdvanceRound messages to current leader to prevent stale views due to message censorship * Ref: [monad-bft PR #2485](https://github.com/category-labs/monad-bft/pull/2485) * **\[Consensus]** Validate group message sender authenticity * Ref: [monad-bft PR #2457](https://github.com/category-labs/monad-bft/pull/2457) #### Notable RPC/SDK changes * **\[RPC]** Fix `eth_syncing` serialization * Ref: [monad-bft PR #2476](https://github.com/category-labs/monad-bft/pull/2476) * **\[RPC]** Update `debug_getRawReceipts` RLP encoding * Ref: [monad-bft PR #2490](https://github.com/category-labs/monad-bft/pull/2490) * **\[RPC]** Failed contract creation should return null to address * Ref: [monad PR #1861](https://github.com/category-labs/monad/pull/1861), [monad-bft PR #2516](https://github.com/category-labs/monad-bft/pull/2516) #### Notable performance changes * **\[Consensus]** Dataplane: io\_uring batching for UDP sends * Replace GSO with io\_uring batching to preserve 64KB burst size while splitting payload * Batches multiple UDP sends into single io\_uring submission * Ref: [monad-bft PR #2317](https://github.com/category-labs/monad-bft/pull/2317) * **\[Consensus]** Dataplane: Restore full 1500 byte MTU * Ref: [monad-bft PR #2438](https://github.com/category-labs/monad-bft/pull/2438) * **\[Consensus]** RaptorCast: Modular packet assembly and round-robin ordering * Ref: [monad-bft PR #2377](https://github.com/category-labs/monad-bft/pull/2377), [monad-bft PR #2469](https://github.com/category-labs/monad-bft/pull/2469), [monad-bft PR #2318](https://github.com/category-labs/monad-bft/pull/2318) * **\[Consensus]** Txpool optimizations * Add configurable limits, remove pending pool, refactor last commit state tracking * Ref: [monad-bft PR #2487](https://github.com/category-labs/monad-bft/pull/2487), [monad-bft PR #2363](https://github.com/category-labs/monad-bft/pull/2363), [monad-bft PR #2475](https://github.com/category-labs/monad-bft/pull/2475) * **\[Archive]** Move indexer read-back to async task * Ref: [monad-bft PR #2486](https://github.com/category-labs/monad-bft/pull/2486) * **\[RPC]** Remove unnecessary lock in request submission path * Ref: [monad-bft PR #2416](https://github.com/category-labs/monad-bft/pull/2416) #### Notable internal changes * **\[Node ops]** Docker support for local Monad installation * Optional `--use-prebuilt` flag to avoid compilation * Ref: [monad-bft PR #2456](https://github.com/category-labs/monad-bft/pull/2456), [monad-bft PR #2489](https://github.com/category-labs/monad-bft/pull/2489) * **\[Node ops]** ledger-tail: New CLI configuration options * Supports custom paths for ledger, forkpoint, node config, and validators files * Fixes default `validators.toml` path * Ref: [monad-bft PR #2439](https://github.com/category-labs/monad-bft/pull/2439) * **\[Node ops]** wal2json: Multi-file support with timestamp filtering * Fast timestamp seeking/filtering across multiple files (e.g., `wal2json -a"3:30pm EDT" -b"4:00pm EDT" wal*`) * Improved performance with parallel processing and reduced locking * Ref: [monad-bft PR #2496](https://github.com/category-labs/monad-bft/pull/2496), [monad-bft PR #2493](https://github.com/category-labs/monad-bft/pull/2493) ## v0.11.6-tn1 Revisions [`MONAD_FIVE`](/developer-essentials/changelog#revisions) and [`MONAD_SIX`](/developer-essentials/changelog#revisions) introduced. Revision [`MONAD_NEXT`](/developer-essentials/changelog#revisions) re-defined. | Network | Date released | Comment | | --------------------------------------------------- | ------------- | ------------------------------------------------------------------------- | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-10-31 | Revision is set to `MONAD_SIX` at 2025-10-31 13:30 GMT | | [`devnet`](/developer-essentials/testnets#tempnet) | 2025-10-30 | Devnet was reset with this new release, with revision set to `MONAD_NEXT` | Tags or hashes for `testnet` deployment (no protocol differences): * `monad-bft`: [tag `v0.11.6-tn1`](https://github.com/category-labs/monad-bft/releases/tag/v0.11.6-tn1) * `monad`: [tag `v0.11.6-tn1`](https://github.com/category-labs/monad/releases/tag/v0.11.6-tn1) #### Notable protocol changes - `MONAD_NEXT` * **\[EVM/Network params]** [Opcode Repricing](/developer-essentials/opcode-pricing) * Ref: [monad PR #1600](https://github.com/category-labs/monad/pull/1600) (for most of the repricings) and [monad PR #1700](https://github.com/category-labs/monad/pull/1700) (for on storage slot deletion via `SSTORE` * Raise the cost of `CREATE` and `CREATE2` * Note that this reduces the set of opcode pricings on `MONAD_NEXT` relative to `v0.11.3`. #### Notable protocol changes - `MONAD_SIX` * **\[EVM]** EIP-2935 bugfix * Ref: [monad PR #1846](https://github.com/category-labs/monad/pull/1846) #### Notable protocol changes - `MONAD_FIVE` * **\[Consensus/EVM]** \[Staking] Lower `ACTIVE_VALIDATOR_STAKE` from 25,000,000 MON to **10,000,000 MON** * Ref: [monad PR #1810](https://github.com/category-labs/monad/pull/1810) * **\[EVM]** \[Staking] Add `getProposerValId()` method to return the `val_id` of the most recent block proposer * Ref: [monad PR #1815](https://github.com/category-labs/monad/pull/1815) #### Notable RPC/SDK changes * **\[RPC]** Fix race condition in block number queries * Return triedb block number in ChainState to prevent errors when `eth_getBlockNumber` returns blocks not yet available in triedb * Fixes common wallet workflow issue with sequential `eth_getBlockNumber` and `eth_getBalance` calls * Ref: [monad-bft PR #2393](https://github.com/category-labs/monad-bft/pull/2393) * **\[SDK]** Add call frame recording to execution events * Ref: [monad PR #1541](https://github.com/category-labs/monad/pull/1541) * These will not be recorded unless the `--trace_calls` command line argument is passed to execution * **\[RPC]** Add `monad_eth_call_executor_get_state()` API for better visibility into `eth_call` requests * Ref: [monad PR #1764](https://github.com/category-labs/monad/pull/1764) * Returns counts of currently executing/queued requests and error counts #### Notable robustness changes * **\[Consensus]** Deterministic timeout certificate tiebreaking * When TC contains multiple tips with the same round, tiebreak by tip.qc for consistency * Ref: [monad-bft PR #2460](https://github.com/category-labs/monad-bft/pull/2460) * **\[Consensus]** Transaction nonce overflow validation * Ref: [monad-bft PR #2388](https://github.com/category-labs/monad-bft/pull/2388) * **\[Consensus]** More consistent checks on `EpochChange` between block proposer and validator * This addresses an observed chain halt in `testnet-2` * Ref: [monad-bft PR #2494](https://github.com/category-labs/monad-bft/pull/2494) #### Notable performance changes * **\[EVM]** Use komihash in StateDelta maps * Ref: [monad PR #1786](https://github.com/category-labs/monad/pull/1786) * **\[EVM]** JIT compiler: only emit jump table for contracts with indirect jumps * Ref: [monad PR #1749](https://github.com/category-labs/monad/pull/1749) * **\[RPC]** Improve eth call concurrency with atomic sequence numbers * Ref: [monad PR #1759](https://github.com/category-labs/monad/pull/1759) #### Notable internal changes * **\[Node ops]** Fuzzer refactor (`--focus` flag addition) * Ref: [monad PR #1744](https://github.com/category-labs/monad/pull/1744) * **\[Node ops]** Build images with debug symbols * Ref: [monad-bft PR #2459](https://github.com/category-labs/monad-bft/pull/2459) ## v0.11.3-tn1 Revisions [`MONAD_FOUR`](/developer-essentials/changelog#revisions) and [`MONAD_NEXT`](/developer-essentials/changelog#revisions) introduced. | Network | Date released | Comment | | --------------------------------------------------- | ------------- | ------------------------------------------------------- | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-10-13 | Revision is set to `MONAD_FOUR` at 2025-10-14 13:30 GMT | | [`devnet`](/developer-essentials/testnets#tempnet) | 2025-10-08 | Revision is set to `MONAD_NEXT` upon upgrade | Tags or hashes for `testnet` deployment (no protocol differences): * `monad-bft`: [tag `v0.11.3-tn1`](https://github.com/category-labs/monad-bft/releases/tag/v0.11.3-tn1) * `monad`: [hash `3e17265`](https://github.com/category-labs/monad/tree/3e17265fae80710229c657d725d821053cec940b) #### Notable protocol changes - `MONAD_NEXT` * **\[EVM/Network params]** [Opcode Repricing](/developer-essentials/opcode-pricing)) * Ref: [monad PR #1600](https://github.com/category-labs/monad/pull/1600) (for most of the repricings) and [monad PR #1700](https://github.com/category-labs/monad/pull/1700) (for `CREATE`/`CREATE2` repricing) * Opcodes are repriced as discussed in the [Monad Initial Spec Proposal](https://forum.monad.xyz/t/monad-initial-specification-proposal/195) * Increase cost of cold storage access * Precompiles: better align a few underpriced precompile gas costs with the associated hardware / latency costs * Note: this release also includes a few opcode repricings in MISP v1.0.2 which were later removed: * Increase cost of storage slot creation via `SSTORE`, and increase the percent of gas refunded on storage slot deletion via `SSTORE` * Raise the cost of `CREATE` and `CREATE2` * **\[EVM]** Disable CREATE and CREATE2 when executing delegated code * Ref: [monad PR #1601](https://github.com/category-labs/monad/pull/1601) #### Notable protocol changes - `MONAD_FOUR` * **\[Consensus/EVM]** [Staking](/monad-arch/consensus/staking) is live * (many PRs; see [staking](https://github.com/category-labs/monad/tree/main/category/execution/monad/staking)) * Active validator set is now determined by on-chain state of the staking precompile * This takes effect starting from a designated `staking_activation` epoch * Note: staking rewards do not activate until `staking_rewards_activation` epoch (future point in time) * External rewards - enable external (non-block-reward) deposits to the validator pool, see [monad PR #1625](https://github.com/category-labs/monad/pull/1625) * Also added additional events to staking precompile in [monad PR #1742](https://github.com/category-labs/monad/pull/1742) * Adds `ValidatorRewarded`, `EpochChanged`, `ClaimRewards` events * **\[Consensus/EVM]** [Reserve balance](/developer-essentials/reserve-balance) logic is live * Ref: [monad-bft PR #2160](https://github.com/category-labs/monad-bft/pull/2160) and [monad PR #1537](https://github.com/category-labs/monad/pull/1537) * Due to async execution, consensus does not have the latest state available for transaction inclusion logic. Reserve balance is a buffer that enables simpler accounting for a given account to mitigate the possibility of overspending in that delay window. * Per-EOA reserve balance is set to `10 MON` * **\[Consensus/EVM]** [EIP-7702](/developer-essentials/eip-7702) is live * Ref: [monad-bft PR #2160](https://github.com/category-labs/monad-bft/pull/2160), [monad-bft PR #2218](https://github.com/category-labs/monad-bft/pull/2218) and [monad PR #1362](https://github.com/category-labs/monad/pull/1362) * **\[Network params]** Set per-transaction gas limit of 30M gas * Ref: [monad-bft PR #2246](https://github.com/category-labs/monad-bft/pull/2246) * As discussed in the [Monad Initial Spec Proposal](https://forum.monad.xyz/t/monad-initial-specification-proposal/195) * **\[Network params]** Increase block gas limit from 150M gas (375 Mgas/s) to **200M** gas (500 Mgas/s) * Ref: [monad-bft PR #2266](https://github.com/category-labs/monad-bft/pull/2266) * **\[Network params]** Implement [dynamic base fee](/developer-essentials/gas-pricing) * Ref: [monad-bft PR #2207](https://github.com/category-labs/monad-bft/pull/2207) * As specified in the [Monad Initial Spec Proposal](https://forum.monad.xyz/t/monad-initial-specification-proposal/195) * Target gas is 80% of a block * Raise minimum base fee from 50 MON-gwei to **100 MON-gwei** * **\[EVM]** Enable [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) + blockhash buffer * Ref: [monad PR #1520](https://github.com/category-labs/monad/pull/1520) * **\[EVM]** Enable [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951) (P256VERIFY precompile support) * Ref: [monad PR #1518](https://github.com/category-labs/monad/pull/1518) * **\[EVM]** Enable [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537) (BLS12-381 precompiles) * Ref: [monad-bft PR #1342](https://github.com/category-labs/monad/pull/1342) and [monad-bft PR #1350](https://github.com/category-labs/monad/pull/1350) * **\[EVM]** Raise max contract size for `CREATE`/`CREATE2` to 128 kb * Ref: [monad PR #1440](https://github.com/category-labs/monad/pull/1440) * This brings `CREATE`/`CREATE2` to parity with toplevel contract creation transactions, which had their limit raised in `MONAD_TWO`. * Previously, they were unintentionally limited to Ethereum's max initcode size (49.152 kb) due to a bug #### Notable RPC/SDK changes * **\[SDK]** Added support for [Execution Events](/execution-events) * **\[RPC]** Extend `eth_call` support for preStateTracer and stateDiffTracer with `debug_traceCall` * Ref: [monad-bft PR #2275](https://github.com/category-labs/monad-bft/pull/2275) and [monad PR #1471](https://github.com/category-labs/monad/pull/1471) * **\[RPC]** Support the `withLog` parameter with `callTracer` * Ref: [monad-bft PR #2400](https://github.com/category-labs/monad-bft/pull/2400) * If `withLog` is set to true, `callTracer` includes event logs in the trace output #### Notable performance changes * **\[EVM]** Kernel caching of db reads and writes * Ref: [monad PR #1559](https://github.com/category-labs/monad/pull/1559) * Utilize available host memory to cache recent DB operations - this cache should increase performance of execution and RPC * **\[RPC]** Improve RPC Db Node Cache and make it memory bounded * Ref: [monad PR #1581](https://github.com/category-labs/monad/pull/1581) * Note a rename of RPC cli flags: `--eth-call-executor-node-lru-size` to `--eth-call-executor-node-lru-max-mem` and a new flag `--triedb-node-lru-max-mem` with both default to 100MB. #### Notable internal changes * **\[Node ops]** Remove `bft-fullnode` binary; only `bft` binary now * Ref: [monad-bft PR #2072](https://github.com/category-labs/monad-bft/pull/2072) * `bft-fullnode` removed from `deb` pkg for `v0.11` * **\[Node ops]** PeerDiscovery: Introduce precheck for peer self name address * **\[Consensus]** Blocksync: only select blocksync peers from connected nodes * Ref: [monad-bft PR #2401](https://github.com/category-labs/monad-bft/pull/2401) * **\[Consensus]** Raptorcast: DOS protection on decoding state cache * Ref: [monad-bft PR #2092](https://github.com/category-labs/monad-bft/pull/2092) * **\[Node ops]** Secondary raptorcast config change * Ref: [monad-bft PR #2378](https://github.com/category-labs/monad-bft/pull/2378) * Replace ambiguous secondary raptorcast `mode` parameter with `enable_publisher` and `enable_client` * **\[Node ops]** Dynamically reload prioritized full-nodes * Ref: [monad-bft PR #2364](https://github.com/category-labs/monad-bft/pull/2364) * **\[Node ops]** Fix wal2json * Ref: [monad-bft PR #2404](https://github.com/category-labs/monad-bft/pull/2404) * **\[Node ops/RPC]** Support the RPC CLI param `ws-sub-per-conn-limit` * Ref: [monad-bft PR #2161](https://github.com/category-labs/monad-bft/pull/2161) * Sets the maximum number of websocket subscriptions per connection (default to 100) * **\[Node ops/RPC]** Add configuration options for high eth-call pool * Ref: [monad-bft PR #2387](https://github.com/category-labs/monad-bft/pull/2387) ## v0.11.1 Note: `v0.11.1` was a version that was deployed only on `testnet-2` (a temporary network deprecated on 2025-10-27) from roughly 2025-09-16 until 2025-10-08; however, it maps some features differently to revision `MONAD_FOUR` which are no longer memorialized in the codebase. `testnet-2` was reset before deploying `v0.11.3`. For all intents and purposes, this version can be ignored. This changelog will compare `v0.11.3` with `v0.10.4`. Tags or hashes: * `monad-bft`: [tag `v0.11.1`](https://github.com/category-labs/monad-bft/releases/tag/v0.11.1) * `monad`: [hash `7746980`](https://github.com/category-labs/monad/tree/7746980c45b82f812bd8c4d39f4b0a1232ae16de) ## v0.10.4 | Network | Date deployed | Comment | | --------------------------------------------------- | ------------- | ------------------------------- | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-08-18 | Revision is still `MONAD_THREE` | Tags or hashes: * `monad-bft`: [tag `v0.10.4`](https://github.com/category-labs/monad-bft/releases/tag/v0.10.4) * `monad`: hash `39c42e6` #### Notable RPC/SDK changes * **\[RPC]** EIP-4844 related fields in RPC responses are removed. * Block headers returned from RPC will no longer have `blobGasUsed` , `excessBlobGas`, and `parentBeaconBlockRoot`. #### Notable performance changes * **\[Consensus]** Votes are now sent to current round leader. This is a consensus optimization that reduces the effective delay due to a round timeout * Ref: [monad-bft PR #2093](https://github.com/category-labs/monad-bft/pull/2093) * **\[Consensus]** Caching of recently-verified quorum certificates * Ref: [monad-bft PR #2167](https://github.com/category-labs/monad-bft/pull/2167) * **\[Node ops]** Enablement of trace calls is now controlled via `monad` (execution) command line arg `--trace_calls`. * To preserve legacy behavior, `--trace_calls` is currently enabled in the debian package. In the future we recommend disabling for validators and enabling it for RPC and archive nodes. * This allows voting validators to opt out of computing traces since they're only need for RPC nodes. #### Notable internal changes * **\[Consensus]** TC forwarding to prioritized and public (non-dedicated) full nodes * Ref: [monad-bft PR #2149](https://github.com/category-labs/monad-bft/pull/2149) * Prior to `v0.10.4`, all full nodes were subject to lagging behind validators in the event of a timeout because round advancement due to TC was not forwarded (and still isn’t to dedicated full nodes). * As a result, after timeouts, full nodes would frequently forward transactions to the next three leaders relative to a stale state. This results in those transactions often missing and the ensuing blocks being comparatively empty. * **\[Node ops]** Bugfix for secondary raptorcast (round gap crash) that affected validators in Publisher mode * Ref: [monad-bft PR #2090](https://github.com/category-labs/monad-bft/pull/2090) * **\[Node ops]** `ledger-tail` improvements * Ref: [monad-bft PR #2144](https://github.com/category-labs/monad-bft/pull/2144) - Reduced memory usage on startup - `author_dns` field changed to `author_address` to reflect change in v10 addresses - Timeouts and finalizations are tracked and logged * **\[Consensus]** Txpool account preloading bugfix * Ref: [monad-bft PR #2108](https://github.com/category-labs/monad-bft/pull/2108) ## v0.10.3 [`MONAD_THREE`](/developer-essentials/changelog#revisions) revision introduced. | Network | Date deployed | Comment | | --------------------------------------------------- | ------------- | ---------------------------------------------- | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-08-12 | Revision is set to `MONAD_THREE` when upgraded | #### Notable protocol changes - `MONAD_THREE` * **\[Consensus]** Consensus mechanism upgraded from [Fast-HotStuff](https://arxiv.org/abs/2010.11454) to [MonadBFT](/monad-arch/consensus/monad-bft) * This is a major upgrade that adds *(i)* [resilience to tail forking](/monad-arch/consensus/monad-bft#no-tail-forking) and *(ii)* [1-slot speculative finality](/monad-arch/consensus/monad-bft#speculative-finality) * **\[Network params]** Block time reduced from 500 ms to **400 ms** #### Notable RPC/SDK changes * **\[RPC]** Add support for [real-time data](/monad-arch/realtime-data) via [WebSocket](/reference/websockets) and shared memory queue access (docs coming soon): * Geth real-time events (via WebSocket) * Geth real-time events with Monad extensions (via WebSocket) * Real-time data via shared memory queue, for programs on a full node host using the execution event SDK #### Notable performance changes * **\[Execution]** Switch to JIT EVM. The bytecode of expensive or frequently-executed contracts is compiled directly to native code for faster execution * **\[Execution]** Switch to using file pointer-based IPC to execute blocks. Previously, the client was using a write-ahead log (WAL), which did not take advantage of available consensus information and could result in avoidable execution #### Notable internal changes * **\[Node ops]** Adds peer discovery * **\[Node ops]** Adds full node [RaptorCast](/monad-arch/consensus/raptorcast), making the full node network scalable ## v0.9.3 | Network | Date deployed | Comment | | --------------------------------------------------- | ------------- | ----------------------------- | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-05-29 | Revision is still `MONAD_TWO` | #### Notable RPC/SDK changes * **\[RPC]** `eth_call` and `eth_estimateGas` limits * RPC providers can now set individual per-transaction limits on maximum gas for `eth_call` and `eth_estimateGas` * Previously the limit would always be the block gas limit (150M), now the RPC provider may choose (default: 30M) * Controlled with `--eth-call-provider-gas-limit` and `--eth-estimate-gas-provider-gas-limit` * Add a maximum timeout for queueing when executing `eth_call` and `eth_estimateGas` * Controlled with `--eth_call_executor_queuing_timeout` #### Notable performance changes * **\[RPC]** Improve overall `eth_call` performance by maintaining separate queues for cheap and expensive `eth_call` operations, so that cheap `eth_call` operations will not be queued behind expensive ones. * This adds two new RPC error strings: * `failure to submit eth_call to thread pool: queue size exceeded` * `failure to execute eth_call: queuing time exceeded timeout threshold` * **\[RPC]** Added an archive index for `eth_getLogs` to support queries with address and/or topic filters * This enables larger block ranges to be queried efficiently, with work proportional to number of matching logs instead of number of blocks in range * **\[Execution]** Better bounding of TrieDB traversals #### Notable internal changes * **\[Node ops]** Performance improvements for slow statesync client upsert * **\[Node ops]** Bugfix for execution delay and abrupt history length drops caused by premature soft reset * **\[Node ops]** `keystore` QOL improvements * Support for importing from a private key in hex string format (previously required conversion to json file) * More helpful documentation via `--help` command ## testnet-1 active set expansion On 2025-05-02, `testnet-1` experienced an active set expansion #### Notable internal changes * **\[Network params]** Testnet validator set expanded from 72 to **99** nodes ## v0.9.2 | Network | Date deployed | Comment | | --------------------------------------------------- | ------------- | ----------------------------- | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-04-05 | Revision is still `MONAD_TWO` | #### Notable performance changes * **\[RPC]** Update to the `eth_call` execution implementation - uses fewer threads to achieve the same concurrency as in `v0.9.1` #### Notable internal changes * **\[Node ops]** Support for a faster [statesync](/monad-arch/consensus/statesync) mechanism * **\[RPC]** RPC no longer accepts requests while node is statesyncing * **\[Node ops]** Some reliability and efficiency improvements to how statesync traffic is carried between nodes * **\[Node ops]** Bugfixes for execution crashes (`monad::mpt::deserialize_node_from_buffer`, `'Resource temporarily unavailable`) - **\[Node ops]** Bug fix for unbounded blocksync requests that result in node OOM failures - **\[Node ops]** Removal of DNS resolution panic on start up ## v0.9.1 | Network | Date deployed | Comment | | --------------------------------------------------- | ------------- | ------------------------------ | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-03-24 | Revision is set to `MONAD_TWO` | #### Notable internal changes * **\[Node ops]** Bugfix for blocksync errors * **\[Node ops]** Reliability and efficiency improvements to statesync ## v0.9.0 Revision [`MONAD_TWO`](/developer-essentials/changelog#revisions) introduced. | Network | Date deployed | Comment | | --------------------------------------------------- | ------------- | ------------------------------------------------------ | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-03-14 | Revision is set to `MONAD_TWO` at 2025-03-14 19:00 GMT | #### Notable protocol changes - `MONAD_TWO` * **\[EVM]** Max contract size increased from 24kb to **128kb** * [example 123 kb contract](https://testnet.monadvision.com/address/0x0E820425e07E4a992C661E4B970e13002d6e52B9?tab=Contract) #### Notable RPC/SDK changes * **\[RPC]** `debug_traceTransaction` fixes * Fixed a bug where, within one transaction, only the first 100 calls were being traced * Added `error` and `revertReason` fields to response data #### Notable performance changes * **\[Consensus]** Dataplane v2 - simpler and more efficient implementation; small performance improvement in broadcast time * **\[RPC]** Improvements to RPC performance for `eth_call` - **\[RPC]** Removed redundant sender\_recovery operation on raw transactions received via RPC #### Notable internal changes * **\[Node ops]** Statesync improvements to mitigate negative performance effects on upstream validator nodes - **\[RPC]** EIP-2 signature validation added to RPC transaction validation - **\[Node ops]** Miscellaneous tracing, logging and metrics additions - **\[Consensus]** RaptorCast performance improvement when dealing with invalid symbols ## v0.8.1 Revision [`MONAD_ONE`](/developer-essentials/changelog#revisions) introduced. | Network | Date deployed | Comment | | --------------------------------------------------- | ------------- | ------------------------------------------------------ | | [`testnet`](/developer-essentials/testnets#testnet) | 2025-02-14 | Revision is set to `MONAD_ONE` at 2025-02-14 19:00 GMT | #### Notable protocol changes - `MONAD_ONE` * **\[Network params]** Block time reduced from 1s to **500 ms** * **\[Network params]** Block gas limit reduced from 300M to **150M** (to keep gas limit consistent) * **\[EVM]** Transactions are [charged](/developer-essentials/gas-pricing) based on gas limit, not gas consumed #### Notable RPC/SDK changes * **\[RPC]** UX improvements for [transaction status](/monad-arch/transaction-lifecycle). RPC nodes track status of transactions submitted to them in order to provide updates to users. # Testnet Changelog Source: https://docs.monad.xyz/developer-essentials/changelog/testnet This is a curated list of only changes affecting [testnet](/developer-essentials/testnets#testnet). We group changes into * protocol changes (generally requiring a new [Revision](/developer-essentials/changelog#revisions)) * generally tagged with **\[EVM]**, **\[Consensus]**, or **\[Network params]** * RPC/SDK behavioral changes (generally tagged with **\[RPC]**) * performance changes * tagged based on the nature of the change as **\[EVM]**, **\[Consensus]**, or **\[RPC]** * internal/node-ops changes (generally tagged as **\[Node ops]**) ## v0.13.1 \[2026-03-16] Revision: [`MONAD_NINE`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft` (consensus): [tag `v0.13.1`](https://github.com/category-labs/monad-bft/releases/tag/v0.13.1) (`7db6cea2`) * `monad` (execution): [tag `v0.13.1`](https://github.com/category-labs/monad/releases/tag/v0.13.1) (`722cbf5b5`) > **Note:** This is a patch release based on v0.13.0 to fix State Archive node RPC issues. The monad (execution) tag points to the same commit as v0.13.0. #### Notable robustness changes * **\[RPC]** Use latest voted as fallback for last\_proposed * Ref: [monad-bft PR #2857](https://github.com/category-labs/monad-bft/pull/2857) * Fixes State Archive node RPC returning stale block data when `last_proposed` is unavailable ## v0.13.0 \[2026-03-05] Revision: [`MONAD_NINE`](/developer-essentials/changelog#revisions) (upgrade; Tuesday, 2026-03-10 at 14:30 GMT) Tags or hashes: * `monad-bft` (consensus): [tag `v0.13.0`](https://github.com/category-labs/monad-bft/releases/tag/v0.13.0) * `monad` (execution): [tag `v0.13.0+1`](https://github.com/category-labs/monad/releases/tag/v0.13.0+1) > **Note:** The canonical release version is `v0.13.0`. The `+1` build metadata on the monad execution tag indicates a post-release rebuild that includes the reserve balance precompile fallback cost fix ([PR #2109](https://github.com/category-labs/monad/pull/2109)). #### Highlights * **RPC: `latest` block tag: `Finalized` -> `Proposed`** — queries with the `latest` block tag now return data from the latest proposed block, reducing query latency for `eth_getBalance`, `eth_call`, and other state queries. * **RPC: `eth_sendRawTransactionSync`: `Voted` -> `Proposed`** — earlier receipts when using the `eth_sendRawTransactionSync` method. * **Websocket: `newHeads/logs`: `Finalized` -> `Voted`** — earlier notifications for websocket subscribers to either `newHeads` or `logs`. #### Notable protocol changes All updates below are gated by the `MONAD_NINE` revision * **\[EVM]** [MIP-3](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-3.md): Linear memory implementation * Ref: [monad PR #2032](https://github.com/category-labs/monad/pull/2032) * **\[Execution]** [MIP-4](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-4.md): Reserve balance precompile * Ref: [monad PR #2040](https://github.com/category-labs/monad/pull/2040) (scaffolding) * Ref: [monad PR #2065](https://github.com/category-labs/monad/pull/2065) (cached/incremental checks) * Ref: [monad PR #2086](https://github.com/category-labs/monad/pull/2086) (add dippedIntoReserve) * Ref: [monad PR #2106](https://github.com/category-labs/monad/pull/2106) (dippedIntoReserve argument length error) * Ref: [monad PR #2109](https://github.com/category-labs/monad/pull/2109) (fallback cost fix for clean spec) * Ref: [monad PR #2037](https://github.com/category-labs/monad/pull/2037) (skip reserve checks for init-selfdestruct) * **\[EVM]** [MIP-5](https://github.com/monad-crypto/MIPs/blob/main/MIPS/MIP-5.md): Activate Osaka fork (CLZ opcode) * Ref: [monad PR #2024](https://github.com/category-labs/monad/pull/2024) > **Hard Fork:** This release activates MONAD\_NINE at timestamp `1773153000` (testnet) / `1773930600` (mainnet). Nodes must upgrade before activation. #### Notable RPC/SDK changes * **\[RPC]** Latest blocktag uses proposed blocks * Ref: [monad-bft PR #2675](https://github.com/category-labs/monad-bft/pull/2675) * **\[RPC]** Use voted blocks in websocket notifications * Ref: [monad-bft PR #2799](https://github.com/category-labs/monad-bft/pull/2799) * **\[RPC]** Add experimental flag to RPC doc macro * Ref: [monad-bft PR #2773](https://github.com/category-labs/monad-bft/pull/2773) * **\[RPC]** Refactor RPC middleware for improved request handling * Ref: [monad-bft PR #2805](https://github.com/category-labs/monad-bft/pull/2805) * **\[RPC]** Add decompression guard to RPC * Ref: [monad-bft PR #2793](https://github.com/category-labs/monad-bft/pull/2793) #### Notable robustness changes * **\[Consensus]** Add signature verifier for Raptorcast * Ref: [monad-bft PR #2747](https://github.com/category-labs/monad-bft/pull/2747) * **\[Consensus]** Fix reactivate logic in r10 decoder * Ref: [monad-bft PR #2740](https://github.com/category-labs/monad-bft/pull/2740) * **\[Execution]** Fix buffer overflow in C->Rust logging * Ref: [monad-bft PR #2769](https://github.com/category-labs/monad-bft/pull/2769) * **\[Consensus]** Add global connect rate limit to wireauth * Ref: [monad-bft PR #2765](https://github.com/category-labs/monad-bft/pull/2765) * **\[Consensus]** Validate ping/pong source address against name record in peer discovery * Ref: [monad-bft PR #2752](https://github.com/category-labs/monad-bft/pull/2752) * **\[Consensus]** Persist voted\_head in ledger * Ref: [monad-bft PR #2744](https://github.com/category-labs/monad-bft/pull/2744) * **\[Consensus]** Add dual UDP packet sender for dataplane * Ref: [monad-bft PR #2746](https://github.com/category-labs/monad-bft/pull/2746) * **\[Execution]** Fix ThreadSanitizer race by joining bootstrap fiber before thread exit * Ref: [monad PR #2053](https://github.com/category-labs/monad/pull/2053) * **\[Execution]** Include account balance in selfdestruct tracer frame * Ref: [monad PR #2039](https://github.com/category-labs/monad/pull/2039) * **\[Consensus]** Ensure proposed head is on canonical chain * Ref: [monad-bft PR #2756](https://github.com/category-labs/monad-bft/pull/2756) * **\[Execution]** Fix sentinel collision in compact virtual chunk offset * Ref: [monad PR #2083](https://github.com/category-labs/monad/pull/2083) * **\[Execution]** Fix potential race condition in execute\_block\_transactions * Ref: [monad PR #2092](https://github.com/category-labs/monad/pull/2092) #### Notable internal changes * **\[Consensus]** Remove unneeded channels when secondary raptorcast is disabled * Ref: [monad-bft PR #2808](https://github.com/category-labs/monad-bft/pull/2808) * **\[Consensus]** Upgrade alloy to stable release * Ref: [monad-bft PR #2792](https://github.com/category-labs/monad-bft/pull/2792) * **\[Execution]** Add `evm-as` syntactic sugar for VM utilities * Ref: [monad PR #2097](https://github.com/category-labs/monad/pull/2097) * **\[Consensus]** Parametrize wireauth metrics for better observability * Ref: [monad-bft PR #2692](https://github.com/category-labs/monad-bft/pull/2692) * **\[Consensus]** Cleanup block persist implementation * Ref: [monad-bft PR #2767](https://github.com/category-labs/monad-bft/pull/2767) * **\[Consensus]** Fix wip extension in block-persist * Ref: [monad-bft PR #2776](https://github.com/category-labs/monad-bft/pull/2776) * **\[Consensus]** Remove legacy `do_local_insert` * Ref: [monad-bft PR #2728](https://github.com/category-labs/monad-bft/pull/2728) * **\[Consensus]** Add metrics for raptorcast deserialize failures * Ref: [monad-bft PR #2789](https://github.com/category-labs/monad-bft/pull/2789) * **\[RPC]** Reduce RPC event server broadcast channel size * Ref: [monad-bft PR #2815](https://github.com/category-labs/monad-bft/pull/2815) * **\[Consensus]** Pre-TFM base fee cleanup * Ref: [monad-bft PR #2664](https://github.com/category-labs/monad-bft/pull/2664) * **\[Consensus]** Remove pre-TFM reserve balance logic * Ref: [monad-bft PR #2622](https://github.com/category-labs/monad-bft/pull/2622) * **\[RPC]** Simplify hex encoding/decoding in RPC * Ref: [monad-bft PR #2806](https://github.com/category-labs/monad-bft/pull/2806) * **\[Archive]** Add exists(key) to kvstore * Ref: [monad-bft PR #2688](https://github.com/category-labs/monad-bft/pull/2688) ## v0.12.7 Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) #### Notable RPC/SDK changes * **\[RPC]** Allow block hash as block identifier in `eth_estimateGas` * Ref: [monad-bft PR #2676](https://github.com/category-labs/monad-bft/pull/2676) * **\[RPC]** Use `RawValue` for RPC id to preserve original JSON types * Reduces effectiveness of DoS attacks using large JSON arrays/dicts in request IDs * Ref: [monad-bft PR #2455](https://github.com/category-labs/monad-bft/pull/2455) #### Notable robustness changes * **\[Consensus]** Remove consecutive sequence number assertion * Ref: [monad-bft PR #2704](https://github.com/category-labs/monad-bft/pull/2704) * **\[Consensus]** Improve liveness when advancing round using TC from timeout messages * Ref: [monad-bft PR #2701](https://github.com/category-labs/monad-bft/pull/2701) * **\[Consensus]** Disallow creating an empty validator set * Ref: [monad-bft PR #2682](https://github.com/category-labs/monad-bft/pull/2682) #### Notable internal changes * **\[Consensus]** Add security tests for `secp256k1` * Adds Wycheproof tests, malleability tests, and security unit tests * Ref: [monad-bft PR #2706](https://github.com/category-labs/monad-bft/pull/2706) * **\[Node ops]** Update `rkyv` and other dependencies * Ref: [monad-bft PR #2702](https://github.com/category-labs/monad-bft/pull/2702) * **\[Consensus]** Return bound socket addresses synchronously in dataplane * Ref: [monad-bft PR #2653](https://github.com/category-labs/monad-bft/pull/2653) * **\[Consensus]** Rename state backend cache for clarity * Ref: [monad-bft PR #2699](https://github.com/category-labs/monad-bft/pull/2699) ## v0.12.6 \[2026-01-07] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.6`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.6) * `monad`: [tag `v0.12.6`](https://github.com/category-labs/monad/releases/tag/v0.12.6) #### Notable RPC/SDK changes * **\[RPC]** Fix depth bug in selfdestructing call frames * Ref: [monad PR #1977](https://github.com/category-labs/monad/pull/1977) * **\[RPC]** Prestate tracer conformance fixes * Ref: [monad PR #1946](https://github.com/category-labs/monad/pull/1946) #### Notable robustness changes * **\[Consensus]** Add signature verification rate limiting with authenticated peer bypass * Ref: [monad-bft PR #2601](https://github.com/category-labs/monad-bft/pull/2601) * **\[Consensus]** Validate confirm group message before updating peers * Ref: [monad-bft PR #2680](https://github.com/category-labs/monad-bft/pull/2680) * **\[Consensus]** Use Tai64N directly for wireauth timestamp comparison * Ref: [monad-bft PR #2678](https://github.com/category-labs/monad-bft/pull/2678) * **\[Consensus]** Improve RaptorCast decoding cache eviction * Ref: [monad-bft PR #2651](https://github.com/category-labs/monad-bft/pull/2651) * **\[Consensus]** Fix blocksync in-flight request tracking on cache hydration * Ref: [monad-bft PR #2635](https://github.com/category-labs/monad-bft/pull/2635) * **\[Execution]** Update triedb voted metadata before executing proposal * Solves a race condition between Voted updates through websocket and corresponding JSON-RPC calls * Ref: [monad PR #1964](https://github.com/category-labs/monad/pull/1964) * **\[Node ops]** Persist peers periodically for improved discovery resilience * Ref: [monad-bft PR #2633](https://github.com/category-labs/monad-bft/pull/2633) #### Notable performance changes * **\[Consensus]** Boost dataplane throughput with ring buffer implementation * Ref: [monad-bft PR #2596](https://github.com/category-labs/monad-bft/pull/2596) * **\[Execution]** Native implementation for `MLOAD`, `MSTORE`, `MSTORE8`, `CALLDATALOAD` opcodes * Ref: [monad PR #1963](https://github.com/category-labs/monad/pull/1963) * **\[Execution]** Improve snapshot write performance * Ref: [monad PR #1973](https://github.com/category-labs/monad/pull/1973), [monad PR #1960](https://github.com/category-labs/monad/pull/1960) * **\[Execution]** Optimize database history length adjustment with binary search * Ref: [monad PR #1922](https://github.com/category-labs/monad/pull/1922) * **\[Execution]** Unify single buffer and scatter read handling in AsyncIO * Ref: [monad PR #1944](https://github.com/category-labs/monad/pull/1944) #### Notable internal changes * **\[Node ops]** Docker single-node container updates for prebuilt images * Fixes to [Local Docker installation](https://github.com/category-labs/monad-bft/blob/master/README.md#using-pre-built-images) * Ref: [monad-bft PR #2674](https://github.com/category-labs/monad-bft/pull/2674) * **\[Consensus]** Add support for generic txpool sidecars * Enables IPC transaction priority and forwarding controls * Ref: [monad-bft PR #2557](https://github.com/category-labs/monad-bft/pull/2557) * **\[EVM]** Osaka fork preparation * Modexp gas changes and upper bound (EIP-7823/EIP-7883) * Implement `CLZ` opcode * Ref: [monad PR #1970](https://github.com/category-labs/monad/pull/1970), [monad PR #1981](https://github.com/category-labs/monad/pull/1981) * **\[Node ops]** Accept `--hyphen-style` long options in CLI * Ref: [monad PR #2005](https://github.com/category-labs/monad/pull/2005) * **\[Node ops / Archive]** Archive pipeline improvements * Add `WritePolicy` to `KVStore` for conditional write protection (`NoClobber`) * Add BFT-uploading stats logging * Ref: [monad-bft PR #2649](https://github.com/category-labs/monad-bft/pull/2649), [monad-bft PR #2661](https://github.com/category-labs/monad-bft/pull/2661) * **\[Node ops]** Simplify RPC txpool status tracking * Ref: [monad-bft PR #2644](https://github.com/category-labs/monad-bft/pull/2644) * **\[Node ops]** Snapshot tooling improvements in `monad_cli` * Add `--dump_concurrency_limit` parameter * Add sharding support for distributed snapshot creation * Ref: [monad PR #1967](https://github.com/category-labs/monad/pull/1967), [monad PR #1965](https://github.com/category-labs/monad/pull/1965) * **\[Node ops]** Add RaptorCast decoding cache metrics * Ref: [monad-bft PR #2667](https://github.com/category-labs/monad-bft/pull/2667) * **\[Execution]** Add `CommitBuilder` API to execution database * Ref: [monad PR #1968](https://github.com/category-labs/monad/pull/1968) * **\[Execution]** Remove legacy `using_chunks_for_root_offsets` metadata field * Ref: [monad PR #1943](https://github.com/category-labs/monad/pull/1943) * **\[Node ops / Txgen]** Add ERC-4337 + EIP-7702 generator (`erc4337_7702_bundled`) * Ref: [monad-bft PR #2628](https://github.com/category-labs/monad-bft/pull/2628) * **\[Node ops]** Add systemd service scripts for `blockcapd` and execution events archivers * Ref: [monad-bft PR #2590](https://github.com/category-labs/monad-bft/pull/2590) * **\[SDK]** Add `monad_event_resolve_ring_file` API * Ref: [monad PR #1741](https://github.com/category-labs/monad/pull/1741) * **\[Execution]** Enable VM host exception handling outside VM * Ref: [monad PR #1990](https://github.com/category-labs/monad/pull/1990) ## v0.12.5 \[2025-12-16] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.5`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.5) * `monad`: [tag `v0.12.5`](https://github.com/category-labs/monad/releases/tag/v0.12.5) #### Notable network changes * **\[Network reset]** `testnet` reset from genesis starting from `MONAD_EIGHT` * Increases total supply to 100 B, consistent with `mainnet` * Clears a path to eliminate dead code (revisions required for early `testnet`) * Ref: [monad PR #1983](https://github.com/category-labs/monad/pull/1983), [monad-bft PR #2673](https://github.com/category-labs/monad-bft/pull/2673) `testnet` has been reset from genesis as of `2025-12-16` (`v0.12.5`). The changelog below this point (pre-`v0.12.5`) is preserved for posterity. ## v0.12.4 \[2025-12-05] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.4`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.4) * `monad`: [tag `v0.12.4`](https://github.com/category-labs/monad/releases/tag/v0.12.4) *(unchanged from v0.12.3)* #### Notable internal changes * **\[Node ops]** Fix peer discovery port configuration when constructing self name record * Use port from peer discovery config instead of bound socket when constructing self name record. This bug affected node operators using a NAT. * Ref: [monad-bft PR #2655](https://github.com/category-labs/monad-bft/pull/2655) ## v0.12.3 \[2025-12-04] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.3-rc.2`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.3-rc.2) * `monad`: [tag `v0.12.3-rc.2`](https://github.com/category-labs/monad/releases/tag/v0.12.3-rc.2) #### Notable RPC/SDK changes * **\[RPC]** [EIP-7966](https://eips.ethereum.org/EIPS/eip-7966) (`eth_sendRawTransactionSync`) support * Ref: [monad-bft PR #2542](https://github.com/category-labs/monad-bft/pull/2542) * **\[RPC]** Populate `value` field on traces for staking precompile syscalls (added in `0.12.3-rpc-hotfix2`) * Ref: [monad PR #1938](https://github.com/category-labs/monad/pull/1938) #### Notable robustness changes * **\[Consensus]** Wire authentication protocol for UDP * Adds authenticated UDP communication to improve network security * Includes replay window adjustments for improved reliability * Ref: [monad-bft PR #2417](https://github.com/category-labs/monad-bft/pull/2417), [monad-bft PR #2544](https://github.com/category-labs/monad-bft/pull/2544), [monad-bft PR #2626](https://github.com/category-labs/monad-bft/pull/2626), [monad-bft PR #2091](https://github.com/category-labs/monad-bft/pull/2091) * **\[Execution]** Improve `statesync` shutdown and fix protocol corruption * Modified `statesync_server_recv()` to guarantee complete reads * Ref: [monad PR #1930](https://github.com/category-labs/monad/pull/1930) * **\[RPC]** Prevent reserve balance crash in RPC * Add reserve balance violation error messages in call frames * Ref: [monad PR #1932](https://github.com/category-labs/monad/pull/1932), [monad-bft PR #2629](https://github.com/category-labs/monad-bft/pull/2629) * This was already previously released in `v0.12.2-rpc-hotfix` * **\[Consensus]** Add timeout to `txpool` IPC stream * Prevents hanging connections in transaction pool communication * Ref: [monad-bft PR #2619](https://github.com/category-labs/monad-bft/pull/2619) * **\[Consensus]** Reject timeout certificates with empty signers * Adds validation to prevent malformed timeout certificates * Ref: [monad-bft PR #2630](https://github.com/category-labs/monad-bft/pull/2630) * **\[Consensus]** Verify RaptorCast chunk length is non-zero * Adds validation to prevent malformed RaptorCast chunks * Ref: [monad-bft PR #2638](https://github.com/category-labs/monad-bft/pull/2638) * **\[Execution]** Fix MPT restore bug that failed to preserve `version_lower_bound` * Ref: [monad PR #1955](https://github.com/category-labs/monad/pull/1955) * **\[Execution]** Fix move trie version forward bug * Erase versions that fall out of history range before moving forward to newer versions * Ref: [monad PR #1957](https://github.com/category-labs/monad/pull/1957) * **\[Execution]** Fix dangling pointers to intercode * Resolves memory safety issue with `get_code` and `read_code` functions * Ref: [monad PR #1941](https://github.com/category-labs/monad/pull/1941) * **\[Execution]** Add retries to `runloop_monad_ethblocks` * Ref: [monad PR #1953](https://github.com/category-labs/monad/pull/1953) #### Notable performance changes * **\[RPC]** Improve `eth_getLogs` performance * Reduce memory copies and unify receipt-to-logs processing * Ref: [monad-bft PR #2588](https://github.com/category-labs/monad-bft/pull/2588), [monad-bft PR #2591](https://github.com/category-labs/monad-bft/pull/2591), [monad-bft PR #2631](https://github.com/category-labs/monad-bft/pull/2631) * **\[Execution]** Fiber: add support for move-only functors * Improves execution efficiency by supporting move semantics * Ref: [monad PR #1936](https://github.com/category-labs/monad/pull/1936) #### Notable internal changes * **\[RPC / Node ops]** Allow RPC to run without `monad-bft` * Enables standalone RPC operation for improved deployment flexibility * Ref: [monad-bft PR #2613](https://github.com/category-labs/monad-bft/pull/2613) * **\[Node ops]** Add `--root-offsets-chunk-count` flag to `monad-mpt` to configure the number of chunks allocated for root offsets * Each chunk holds approximately 16.5M blocks. Previously, all nodes were hardcoded to use 2 chunks (max TrieDB capacity of \~33M blocks) * New default: 16 **must be a power of 2** * Note that the default value of 16 translates to approximately 268M blocks \~ 1240 days. In most cases the limiting factor is disk capacity which will auto-compact when filled to 80% * Ref: [monad PR #1937](https://github.com/category-labs/monad/pull/1937) * **\[Node ops]** Peer discovery improvements * Full nodes periodically pull validator name records for dynamic validator discovery * Add authenticated UDP port to name record * Ref: [monad-bft PR #2607](https://github.com/category-labs/monad-bft/pull/2607), [monad-bft PR #2538](https://github.com/category-labs/monad-bft/pull/2538) * **\[Node ops / Archive]** Archive infrastructure improvements * Refactor `monad-block-writer` for improved reliability with `--max-blocks-per-iter` configuration * Async backfill with traces-only archive support * Add `require-traces` archiver flag and indexer fallback source * Support for historical execution event archiving with generic directory archiving * **Potentially breaking: Remove `--start-block` from `monad-archiver` systemd service; operators must use imperative CLI to set start block** * Example: `monad-archiver set-start-block --block 1000000 --archive-sink s3://bucket-name/path` * Use `--async-backfill` flag to set the async-backfill marker instead of primary marker * Ref: [monad-bft PR #2610](https://github.com/category-labs/monad-bft/pull/2610), [monad-bft PR #2606](https://github.com/category-labs/monad-bft/pull/2606), [monad-bft PR #2598](https://github.com/category-labs/monad-bft/pull/2598), [monad-bft PR #2514](https://github.com/category-labs/monad-bft/pull/2514), [monad-bft PR #2612](https://github.com/category-labs/monad-bft/pull/2612), [monad-bft PR #2569](https://github.com/category-labs/monad-bft/pull/2569), [monad-bft PR #2623](https://github.com/category-labs/monad-bft/pull/2623) * **\[Node ops]** Networking configuration updates * Use default MTU 1500 * Add HDR histogram for broadcast latency tracking in `monad-executor` and `monad-raptorcast` * Ref: [monad-bft PR #2576](https://github.com/category-labs/monad-bft/pull/2576), [monad-bft PR #2602](https://github.com/category-labs/monad-bft/pull/2602) * **\[Consensus]** RaptorCast improvements * Clear histogram every 30s and keep only p99 for better latency tracking * Fix timer update after receiving control messages to ensure proper keepalives * Ref: [monad-bft PR #2627](https://github.com/category-labs/monad-bft/pull/2627), [monad-bft PR #2637](https://github.com/category-labs/monad-bft/pull/2637) * **\[Consensus]** Dataplane: refactor to use socket handles * Prepares dataplane for future extension with authenticated sockets * Ref: [monad-bft PR #2458](https://github.com/category-labs/monad-bft/pull/2458) * **\[Execution]** Replay monad: always execute first block off latest finalized state * Ref: [monad PR #1927](https://github.com/category-labs/monad/pull/1927) * **\[Node ops]** Txgen improvements: README and guide, nonce gaps and legacy tx options, ERC20 pools, Uniswap v3 mode, NFT sale mode, websocket and RPC request generation * Ref: [monad-bft PR #2568](https://github.com/category-labs/monad-bft/pull/2568), [monad-bft PR #2567](https://github.com/category-labs/monad-bft/pull/2567), [monad-bft PR #2577](https://github.com/category-labs/monad-bft/pull/2577), [monad-bft PR #2600](https://github.com/category-labs/monad-bft/pull/2600), [monad-bft PR #2527](https://github.com/category-labs/monad-bft/pull/2527), [monad-bft PR #2394](https://github.com/category-labs/monad-bft/pull/2394), [monad-bft PR #2608](https://github.com/category-labs/monad-bft/pull/2608) ## v0.12.2 \[2025-11-18] Revision: [`MONAD_EIGHT`](/developer-essentials/changelog#revisions) (upgrade; Wednesday, 2025-11-19 at 14:30 GMT) Tags or hashes: * `monad-bft`: [tag `v0.12.2-rc`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.2-rc) * `monad`: [tag `v0.12.2-rc`](https://github.com/category-labs/monad/releases/tag/v0.12.2-rc) #### Notable protocol changes All updates below are gated by the `MONAD_EIGHT` revision * **\[Execution]** Reserve balance checks now use the final state code hash. This makes checks slightly more liberal (less likely to revert transactions during execution), and also makes the implementation closer to the [Coq model](https://category-labs.github.io/category-research/reserve-balance-coq-proofs/monad.proofs.reservebal.html#execValidatedTx) that was proven correct under some assumptions. * This changes the behavior of the following corner case: * In transaction 1, account X is sent a small amount of MON (e.g. 5 MON) * Then (much later) in transaction 2, someone both deploys a smart contract wallet at account X and sends MON out of that smart contract. * Previously, transaction 2's execution would treat account X as an EOA and apply reserve balance checks on it, thus reverting the transaction because it would be a dipping transaction that was not pre-authorized by consensus. * After this change, account X will be treated as a smart contract account and will be omitted from reserve balance checks. * Note that smart contract addresses are deterministic, making it effectively impossible for an address that receives a code deployment to also be an EOA (i.e. to correspond to a private key). * Ref: [monad PR #1916](https://github.com/category-labs/monad/pull/1916), [monad PR #1917](https://github.com/category-labs/monad/pull/1917) * **\[EVM]** Reduce pagination on staking precompile inverse mappings from 100 to 50 * Applies to `precompile_get_delegations()` and `precompile_get_delegators()` * Ref: [monad PR #1920](https://github.com/category-labs/monad/pull/1920) #### Notable deployment changes * **\[SDK]** Execution events no longer require a special "preview" release to enable all EVM notifications (there are no more `--exec-events` special versions) * **\[SDK]** Fix race condition in event ring create * Ref: [monad PR #1914](https://github.com/category-labs/monad/pull/1914) #### Notable robustness changes * **\[RPC]** Fix `eth_feeHistory` * Ref: [monad-bft PR #2366](https://github.com/category-labs/monad-bft/pull/2366) * **\[Consensus]** Update transaction classification logic to apply emptying before delegation * Improves delegation ordering in `try_apply_block_fees` * Ref: [monad-bft PR #2561](https://github.com/category-labs/monad-bft/pull/2561) * **\[RPC]** Check reserve balance in `eth_call` * Prevent simulated transactions from violating reserve balance * Call `revert_transaction` in `eth_call` implementation * Ref: [monad PR #1912](https://github.com/category-labs/monad/pull/1912) * **\[Execution]** Failsafe sanity checks for reward system transactions * Ref: [monad PR #1921](https://github.com/category-labs/monad/pull/1921) * **\[Node ops]** Fix remotely fetched `forkpoint.toml` write format * Ensures forkpoint files are written in the correct format when fetched from remote sources * Ref: [monad-bft PR #2554](https://github.com/category-labs/monad-bft/pull/2554) * **\[Node ops]** Fix `monad-keystore` key corruption issue * `to_bls` and `to_secp` now consume secret to prevent corrupted keystore files * Add tests for command line keystore app to verify all flags combinations work correctly * Ref: [monad-bft PR #2553](https://github.com/category-labs/monad-bft/pull/2553) * **\[Execution]** Fix bad write offset in `storage_pool` `try_trim_contents()` when bytes to start TRIM is not a multiple of disk page size * Ref: [monad PR #1908](https://github.com/category-labs/monad/pull/1908) * **\[RPC]** Use txpool bridge channel size for ratelimiting * Prevents `try_send` method from failing by using channel capacity for `eth_sendRawTransaction` ratelimiting * Ref: [monad-bft PR #2506](https://github.com/category-labs/monad-bft/pull/2506) * **\[Consensus]** Improve RaptorCast message validation * Add sanity check for `chunk_id` range in message parsing * Reject overflowing `merkle_leaf_idx` in merkle proof construction * Update calculation of `encoded_symbol_capacity` in decoder * Ref: [monad-bft PR #2541](https://github.com/category-labs/monad-bft/pull/2541) * **\[Consensus]** Improve RaptorCast group invite response checks * Add sanity check to prevent processing group invite accept after reject from same full node * Ref: [monad-bft PR #2574](https://github.com/category-labs/monad-bft/pull/2574) * **\[Consensus]** Update txpool insertion policy - make balance check stricter * Ref: [monad-bft PR #2575](https://github.com/category-labs/monad-bft/pull/2575) #### Notable performance changes * **\[Consensus]** Add dataplane warnings when priority queues are overrun * Sets total priority queue capacity to 100MB per priority queue (200MB total max) * Messages will be dropped with warnings logged if capacity is exceeded * Ref: [monad-bft PR #2559](https://github.com/category-labs/monad-bft/pull/2559) * **\[Execution / Node ops]** Remove read-only database `io_uring` SQ poll thread * **IMPORTANT**: Operators with `monad-execution` systemctl overrides should make the appropriate changes to eliminate the `--ro_sq_thread_cpu` parameter as demonstrated in [monad-bft PR #2558](https://github.com/category-labs/monad-bft/pull/2558). Overrides are commonly set when running an RPC or archive node with `--trace_calls` enabled. * Reduces thread contention during statesync, improving block execution performance * Make `ro_sq_thread_cpu` argument optional, remove from default configuration * Ref: [monad PR #1911](https://github.com/category-labs/monad/pull/1911), [monad-bft PR #2558](https://github.com/category-labs/monad-bft/pull/2558) * **\[RPC]** Move recover authority logic inside submitted lambda for `eth_call` * Avoid blocking on fiber promises when called from rust thread, preventing performance issues * Replace parallelization with single call to `recover_authority()` since `eth_call` transactions have single authority * Ref: [monad PR #1827](https://github.com/category-labs/monad/pull/1827) * **\[Execution]** Remove `iopoll` support * Ref: [monad PR #1910](https://github.com/category-labs/monad/pull/1910) #### Notable internal changes * **\[Node ops]** Add `monad-version` crate for git version info in binaries * Ref: [monad-bft PR #2572](https://github.com/category-labs/monad-bft/pull/2572) * **\[Node ops]** Add CLI argument for number of fibers in trace transaction execution pool (default: 100) * Ref: [monad-bft PR #2584](https://github.com/category-labs/monad-bft/pull/2584) * **\[Node ops]** Update block reward for mainnet * Ref: [monad-bft PR #2582](https://github.com/category-labs/monad-bft/pull/2582) * **\[Node ops]** Add block writer binary for testing and debugging * Ref: [monad-bft PR #2546](https://github.com/category-labs/monad-bft/pull/2546) * **\[Node ops]** Make cruft timer retention configurable * Add environment variable support for artifact retention times (override in `/home/monad/.env`) * Ref: [monad-bft PR #2548](https://github.com/category-labs/monad-bft/pull/2548) * **\[Node ops]** Add warning when restoring from snapshot (hard reset) is required * Prompt node operator when local tip is behind by `STATESYNC_BLOCK_THRESHOLD` * Ref: [monad-bft PR #2581](https://github.com/category-labs/monad-bft/pull/2581) * **\[Node ops]** Update `monad-cruft` permissions to monad user * Ref: [monad-bft PR #2556](https://github.com/category-labs/monad-bft/pull/2556) * **\[Node ops]** Fix txpool metrics recording for transaction replacements * Ref: [monad-bft PR #2564](https://github.com/category-labs/monad-bft/pull/2564) * **\[RPC]** Operator can configure RPC worker threads * Ref: [monad-bft PR #2562](https://github.com/category-labs/monad-bft/pull/2562) * **\[Archive]** Add TOML config file support instead of CLI-only configuration * Enables `monad-archiver` binary to use `--config` flag for configuration * CLI arguments continue to work and take precedence over config file values * Ref: [monad-bft PR #2570](https://github.com/category-labs/monad-bft/pull/2570) * **\[Node ops]** Add random mutations to `txgen` for improved testing * Ref: [monad-bft PR #2543](https://github.com/category-labs/monad-bft/pull/2543) * **\[Node ops]** Add reserve balance coherency tests * Ref: [monad-bft PR #2573](https://github.com/category-labs/monad-bft/pull/2573) * **\[SDK]** Record execution events * Includes state access and system transactions * Ref: [monad PR #1906](https://github.com/category-labs/monad/pull/1906), [monad PR #1907](https://github.com/category-labs/monad/pull/1907), [monad PR #1913](https://github.com/category-labs/monad/pull/1913) * **\[SDK]** Explicitly clear flow info that doesn't apply to certain event types * Prevents poorly written event clients from reading invalid data * Ref: [monad PR #1919](https://github.com/category-labs/monad/pull/1919) * **\[Execution]** Improve assertion messages with `MONAD_ASSERT_PRINTF` * Ref: [monad PR #1918](https://github.com/category-labs/monad/pull/1918) * **\[Node ops]** Add statesync server metrics * Add `SyncDone` metric to track successes and failures * Add request timing metric for statesync server * Ref: [monad-bft PR #2555](https://github.com/category-labs/monad-bft/pull/2555) * **\[Node ops]** Disambiguate triedb error logs * Improve error message clarity by making each log call unique * Ref: [monad-bft PR #2579](https://github.com/category-labs/monad-bft/pull/2579) * **\[Node ops]** Update devnet chain configs * Shorten default devnet epoch length from 50,000 blocks to 10,000 blocks and epoch start delay from 5,000 rounds to 1,000 rounds * Ref: [monad-bft PR #2578](https://github.com/category-labs/monad-bft/pull/2578) * **\[Node ops]** Update test infrastructure * Switch `test_state` and `precompiles_test` tests to `TraitsTest` * Add `runloop_monad_ethblocks` to CLI for Monad EVM block replay * Ref: [monad PR #1905](https://github.com/category-labs/monad/pull/1905), [monad PR #1894](https://github.com/category-labs/monad/pull/1894) * **\[Execution]** Remove unused cross-thread pipe messaging infrastructure * Ref: [monad PR #1915](https://github.com/category-labs/monad/pull/1915) * **\[Node ops]** Keep manytrace agent alive in node state * Ref: [monad-bft PR #2583](https://github.com/category-labs/monad-bft/pull/2583) * **\[Node ops]** Update keystore documentation * Ref: [monad-bft PR #2380](https://github.com/category-labs/monad-bft/pull/2380) * **\[Node ops]** Add more logging for block validation errors * Ref: [monad-bft PR #2550](https://github.com/category-labs/monad-bft/pull/2550) * **\[Node ops]** Log round leader when processing certificates * Ref: [monad-bft PR #2580](https://github.com/category-labs/monad-bft/pull/2580) ## v0.12.1 \[2025-11-12] Revision: [`MONAD_SEVEN`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.12.1`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.1) * `monad`: [tag `v0.12.1`](https://github.com/category-labs/monad/releases/tag/v0.12.1) #### Notable RPC/SDK changes * **\[RPC]** Support for prestate and statediff tracing for the following RPC endpoints: * `debug_traceTransaction` * `debug_traceBlockByNumber` * `debug_traceBlockByHash` * Ref: [monad PR #1753](https://github.com/category-labs/monad/pull/1753), [monad-bft PR #2403](https://github.com/category-labs/monad-bft/pull/2403) * **\[RPC]** New endpoint `eth_createAccessList` * Ref: [monad PR #1748](https://github.com/category-labs/monad/pull/1748), [monad-bft PR #2519](https://github.com/category-labs/monad-bft/pull/2519) * **\[RPC]** Fix serialization for `to` address in historical traces * Follow on to [monad-bft PR #2516](https://github.com/category-labs/monad-bft/pull/2516) implemented in `v0.12.0` (in a historical context) * Ref: [monad-bft PR #2523](https://github.com/category-labs/monad-bft/pull/2523), [monad PR #1861](https://github.com/category-labs/monad/pull/1861) * **\[RPC]** Remove artificial block height floor for `eth_call` and RPC queries * Previously limited queries using `latest` and `safe` block tags to `finalized + 1`, now uses actual latest voted block * Ref: [monad-bft PR #2148](https://github.com/category-labs/monad-bft/pull/2148) * **\[RPC]** Fix RPC errors when requesting traces for call frames that include a self-destructing transaction * Ref: [monad PR #1887](https://github.com/category-labs/monad/pull/1887) * **\[RPC]** Fix inconsistent results being reported when using state overrides in RPC calls * Ref: [monad PR #1812](https://github.com/category-labs/monad/pull/1812) * **\[RPC]** Fix for a possible denial-of-service attack on RPC nodes by overriding balances in the staking contract * Ref: [monad PR #1903](https://github.com/category-labs/monad/pull/1903) #### Notable robustness changes * **\[Node ops/Consensus]** Validators limit statesync service to validators and whitelisted full nodes * Full nodes service all requests * Ref: [monad-bft PR #2524](https://github.com/category-labs/monad-bft/pull/2524) * **\[Consensus]** More intelligent txpool eviction logic * Ref: [monad-bft PR #2525](https://github.com/category-labs/monad-bft/pull/2525) * **\[Consensus]** Start vote pacing timer on startup * Ref: [monad-bft PR #2528](https://github.com/category-labs/monad-bft/pull/2528) * **\[Consensus]** Fix historical logs `selfdestruct` issue * Ref: [monad-bft PR #2531](https://github.com/category-labs/monad-bft/pull/2531) * **\[Consensus]** Graceful handling of gas limit check * Ref: [monad-bft PR #2518](https://github.com/category-labs/monad-bft/pull/2518) * **\[Consensus]** Remove 0-stake validator support * Support for 0-stake validators in the validator set was used as a workaround for lack of full-node support in the past and should no longer be supported * Ref: [monad-bft PR #2545](https://github.com/category-labs/monad-bft/pull/2545) #### Notable performance changes * **\[Consensus]** Bias statesync requests to responsive peers * Support dynamic peer expansion/contraction * Nodes no longer panic if all peers are pruned (e.g., not whitelisted by any init peers) * Ref: [monad-bft PR #2536](https://github.com/category-labs/monad-bft/pull/2536) * **\[Consensus]** Drop blocksync requests that hit disk after cache hydration (\~7min after startup) * Ref: [monad-bft PR #2533](https://github.com/category-labs/monad-bft/pull/2533) * **\[Consensus]** Dataplane: replace interval with sleep * Ref: [monad-bft PR #2517](https://github.com/category-labs/monad-bft/pull/2517) * **\[Execution]** Replace data structures in `VersionStack` code to improve performance when pushing and popping EVM call frames * Ref: [monad PR #1802](https://github.com/category-labs/monad/pull/1802), [monad PR #1877](https://github.com/category-labs/monad/pull/1877) #### Notable internal changes * **\[Node ops]** Try fetching remote `forkpoint.toml` and `validators.toml` on startup * **IMPORTANT**: Nodes now automatically attempt to download configuration files from remote locations on startup, no "Soft Reset" required. This remote fetch is subject to a configurable remote threshold * The following env variables must be set to enable this behavior: * `REMOTE_FORKPOINT_URL` * `REMOTE_VALIDATORS_URL` * Ref: [monad-bft PR #2484](https://github.com/category-labs/monad-bft/pull/2484), [monad-bft PR #2534](https://github.com/category-labs/monad-bft/pull/2534) * **\[Node ops]** Serialize forkpoints in both TOML and RLP formats, with RLP as source of truth; cleanup `get_latest_config` branches * Ref: [monad-bft PR #2521](https://github.com/category-labs/monad-bft/pull/2521) * **\[Node ops/Consensus]** Dynamically expand statesync upstream peers * Always initializes to `statesync.init_peers` (no longer defaults to bootstrap peers) * If `expand_to_group` is enabled, expands to validator set (for validators) and/or secondary raptorcast peers (for public / prioritized full nodes) * Ref: [monad-bft PR #2535](https://github.com/category-labs/monad-bft/pull/2535) * **\[Node ops/Consensus]** Full nodes no longer default to blocksync from validator set * Dedicated full nodes must specify upstream as blocksync override * **IMPORTANT**: prioritized/public full nodes can only blocksync *after* joining secondary raptorcast group. `refresh_period` and `invite_lookahead` recommended to be lowered to 20 seconds * Ref: [monad-bft PR #2532](https://github.com/category-labs/monad-bft/pull/2532) * **\[Consensus]** Use epoch field to encode round number for secondary raptorcast * Fixes issue where syncing full nodes receive unsolicited chunks from expired groups * Ref: [monad-bft PR #2442](https://github.com/category-labs/monad-bft/pull/2442) * **\[Consensus]** Add statesync server pending requests metric (better load visibility) * Ref: [monad-bft PR #2530](https://github.com/category-labs/monad-bft/pull/2530) * **\[Node ops]** Import `monad-execution` API changes * Ref: [monad-bft PR #2522](https://github.com/category-labs/monad-bft/pull/2522) ## v0.12.0 \[2025-11-04] Revision: [`MONAD_SEVEN`](/developer-essentials/changelog#revisions) (upgrade; Wednesday, 2025-11-05 at 14:30 GMT) Tags or hashes: * `monad-bft`: [tag `v0.12.0`](https://github.com/category-labs/monad-bft/releases/tag/v0.12.0) * `monad`: [tag `v0.12.0`](https://github.com/category-labs/monad/releases/tag/v0.12.0) #### Notable protocol changes * **\[EVM/Network params]** [Opcode/precompile repricing](/developer-essentials/opcode-pricing) * Opcode pricing: increase cost of cold storage access * Precompile pricing: better align a few underpriced precompile gas costs with the associated hardware / latency costs * Ref: [monad PR #1600](https://github.com/category-labs/monad/pull/1600) (for most of the repricings) and [monad PR #1700](https://github.com/category-labs/monad/pull/1700) (for `CREATE`/`CREATE2` repricing), [monad PR #1830](https://github.com/category-labs/monad/pull/1830) (for reduced v2 scope), [monad PR #1871](https://github.com/category-labs/monad/pull/1871) (activation for `MONAD_SEVEN`) * Gated by `MONAD_SEVEN` revision * **\[Consensus/Network params]** \[Staking] Increase `BLOCK_REWARD_MON` from 1 MON to 25 MON * Note that this is not gated by `MONAD_SEVEN` (execution hard fork) but by a specific epoch start for `testnet` * Ref: [monad-bft PR #2511](https://github.com/category-labs/monad-bft/pull/2511) #### Notable robustness changes * **\[Consensus]** Consensus message prioritization in dataplane * Priority queues for UDP traffic ensure consensus messages are delivered before other traffic * All consensus messages to validators are published and rebroadcast with high priority * Ref: [monad-bft PR #2352](https://github.com/category-labs/monad-bft/pull/2352), [monad-bft PR #2354](https://github.com/category-labs/monad-bft/pull/2354) * **\[Consensus]** DoS protection: Reject repeated invalid consensus messages * Prevents malicious validators from consuming CPU time by repeatedly sending invalid BLS signatures * Ref: [monad-bft PR #2445](https://github.com/category-labs/monad-bft/pull/2445) * **\[Consensus]** Leader synchronization improvements * Forward AdvanceRound messages to current leader to prevent stale views due to message censorship * Ref: [monad-bft PR #2485](https://github.com/category-labs/monad-bft/pull/2485) * **\[Consensus]** Validate group message sender authenticity * Ref: [monad-bft PR #2457](https://github.com/category-labs/monad-bft/pull/2457) #### Notable RPC/SDK changes * **\[RPC]** Fix `eth_syncing` serialization * Ref: [monad-bft PR #2476](https://github.com/category-labs/monad-bft/pull/2476) * **\[RPC]** Update `debug_getRawReceipts` RLP encoding * Ref: [monad-bft PR #2490](https://github.com/category-labs/monad-bft/pull/2490) * **\[RPC]** Failed contract creation should return null to address * Ref: [monad PR #1861](https://github.com/category-labs/monad/pull/1861), [monad-bft PR #2516](https://github.com/category-labs/monad-bft/pull/2516) #### Notable performance changes * **\[Consensus]** Dataplane: io\_uring batching for UDP sends * Replace GSO with io\_uring batching to preserve 64KB burst size while splitting payload * Batches multiple UDP sends into single io\_uring submission * Ref: [monad-bft PR #2317](https://github.com/category-labs/monad-bft/pull/2317) * **\[Consensus]** Dataplane: Restore full 1500 byte MTU * Ref: [monad-bft PR #2438](https://github.com/category-labs/monad-bft/pull/2438) * **\[Consensus]** RaptorCast: Modular packet assembly and round-robin ordering * Ref: [monad-bft PR #2377](https://github.com/category-labs/monad-bft/pull/2377), [monad-bft PR #2469](https://github.com/category-labs/monad-bft/pull/2469), [monad-bft PR #2318](https://github.com/category-labs/monad-bft/pull/2318) * **\[Consensus]** Txpool optimizations * Add configurable limits, remove pending pool, refactor last commit state tracking * Ref: [monad-bft PR #2487](https://github.com/category-labs/monad-bft/pull/2487), [monad-bft PR #2363](https://github.com/category-labs/monad-bft/pull/2363), [monad-bft PR #2475](https://github.com/category-labs/monad-bft/pull/2475) * **\[Archive]** Move indexer read-back to async task * Ref: [monad-bft PR #2486](https://github.com/category-labs/monad-bft/pull/2486) * **\[RPC]** Remove unnecessary lock in request submission path * Ref: [monad-bft PR #2416](https://github.com/category-labs/monad-bft/pull/2416) #### Notable internal changes * **\[Node ops]** Docker support for local Monad installation * Optional `--use-prebuilt` flag to avoid compilation * Ref: [monad-bft PR #2456](https://github.com/category-labs/monad-bft/pull/2456), [monad-bft PR #2489](https://github.com/category-labs/monad-bft/pull/2489) * **\[Node ops]** ledger-tail: New CLI configuration options * Supports custom paths for ledger, forkpoint, node config, and validators files * Fixes default `validators.toml` path * Ref: [monad-bft PR #2439](https://github.com/category-labs/monad-bft/pull/2439) * **\[Node ops]** wal2json: Multi-file support with timestamp filtering * Fast timestamp seeking/filtering across multiple files (e.g., `wal2json -a"3:30pm EDT" -b"4:00pm EDT" wal*`) * Improved performance with parallel processing and reduced locking * Ref: [monad-bft PR #2496](https://github.com/category-labs/monad-bft/pull/2496), [monad-bft PR #2493](https://github.com/category-labs/monad-bft/pull/2493) ## v0.11.6 \[2025-10-30] Revision: [`MONAD_SIX`](/developer-essentials/changelog#revisions) (upgrade; Friday, 2025-10-31 at 13:30 GMT) Revision: [`MONAD_FIVE`](/developer-essentials/changelog#revisions) (upgrade; Tuesday, 2025-10-28 at 13:30 GMT) Tags or hashes: * `monad-bft`: [tag `v0.11.6-tn1`](https://github.com/category-labs/monad-bft/releases/tag/v0.11.6-tn1) * `monad`: [tag `v0.11.6-tn1`](https://github.com/category-labs/monad/releases/tag/v0.11.6-tn1) #### Notable protocol changes - `MONAD_SIX` * **\[EVM]** EIP-2935 bugfix * Ref: [monad PR #1846](https://github.com/category-labs/monad/pull/1846) #### Notable protocol changes - `MONAD_FIVE` * **\[Consensus/EVM]** \[Staking] Lower `ACTIVE_VALIDATOR_STAKE` from 25,000,000 MON to **10,000,000 MON** * Ref: [monad PR #1810](https://github.com/category-labs/monad/pull/1810) * **\[EVM]** \[Staking] Add `getProposerValId()` method to return the `val_id` of the most recent block proposer * Ref: [monad PR #1815](https://github.com/category-labs/monad/pull/1815) #### Notable RPC/SDK changes * **\[RPC]** Fix race condition in block number queries * Return triedb block number in ChainState to prevent errors when `eth_getBlockNumber` returns blocks not yet available in triedb * Fixes common wallet workflow issue with sequential `eth_getBlockNumber` and `eth_getBalance` calls * Ref: [monad-bft PR #2393](https://github.com/category-labs/monad-bft/pull/2393) * **\[SDK]** Add call frame recording to execution events * Ref: [monad PR #1541](https://github.com/category-labs/monad/pull/1541) * These will not be recorded unless the `--trace_calls` command line argument is passed to execution * **\[RPC]** Add `monad_eth_call_executor_get_state()` API for better visibility into `eth_call` requests * Ref: [monad PR #1764](https://github.com/category-labs/monad/pull/1764) * Returns counts of currently executing/queued requests and error counts #### Notable robustness changes * **\[Consensus]** Deterministic timeout certificate tiebreaking * When TC contains multiple tips with the same round, tiebreak by tip.qc for consistency * Ref: [monad-bft PR #2460](https://github.com/category-labs/monad-bft/pull/2460) * **\[Consensus]** Transaction nonce overflow validation * Ref: [monad-bft PR #2388](https://github.com/category-labs/monad-bft/pull/2388) * **\[Consensus]** More consistent checks on `EpochChange` between block proposer and validator * This addresses an observed chain halt in `testnet-2` * Ref: [monad-bft PR #2494](https://github.com/category-labs/monad-bft/pull/2494) #### Notable performance changes * **\[EVM]** Use komihash in StateDelta maps * Ref: [monad PR #1786](https://github.com/category-labs/monad/pull/1786) * **\[EVM]** JIT compiler: only emit jump table for contracts with indirect jumps * Ref: [monad PR #1749](https://github.com/category-labs/monad/pull/1749) * **\[RPC]** Improve eth call concurrency with atomic sequence numbers * Ref: [monad PR #1759](https://github.com/category-labs/monad/pull/1759) #### Notable internal changes * **\[Node ops]** Fuzzer refactor (`--focus` flag addition) * Ref: [monad PR #1744](https://github.com/category-labs/monad/pull/1744) * **\[Node ops]** Build images with debug symbols * Ref: [monad-bft PR #2459](https://github.com/category-labs/monad-bft/pull/2459) ## v0.11.3 \[2025-10-14] Revision: [`MONAD_FOUR`](/developer-essentials/changelog#revisions) (upgrade; takes effect at 2025-10-14 13:30 GMT) Tags or hashes: * `monad-bft`: [tag `v0.11.3-tn1`](https://github.com/category-labs/monad-bft/releases/tag/v0.11.3-tn1) * `monad`: [hash `3e17265`](https://github.com/category-labs/monad/tree/3e17265fae80710229c657d725d821053cec940b) #### Notable protocol changes - `MONAD_FOUR` * **\[Consensus/EVM]** [Staking](/monad-arch/consensus/staking) is live * (many PRs; see [staking](https://github.com/category-labs/monad/tree/main/category/execution/monad/staking)) * Active validator set is now determined by on-chain state of the staking precompile * This takes effect starting from a designated `staking_activation` epoch * Note: staking rewards do not activate until `staking_rewards_activation` epoch (future point in time) * External rewards - enable external (non-block-reward) deposits to the validator pool, see [monad PR #1625](https://github.com/category-labs/monad/pull/1625) * Also added additional events to staking precompile in [monad PR #1742](https://github.com/category-labs/monad/pull/1742) * Adds `ValidatorRewarded`, `EpochChanged`, `ClaimRewards` events * **\[Consensus/EVM]** [Reserve balance](/developer-essentials/reserve-balance) logic is live * Ref: [monad-bft PR #2160](https://github.com/category-labs/monad-bft/pull/2160) and [monad PR #1537](https://github.com/category-labs/monad/pull/1537) * Due to async execution, consensus does not have the latest state available for transaction inclusion logic. Reserve balance is a buffer that enables simpler accounting for a given account to mitigate the possibility of overspending in that delay window. * Per-EOA reserve balance is set to `10 MON` * **\[Consensus/EVM]** [EIP-7702](/developer-essentials/eip-7702) is live * Ref: [monad-bft PR #2160](https://github.com/category-labs/monad-bft/pull/2160), [monad-bft PR #2218](https://github.com/category-labs/monad-bft/pull/2218) and [monad PR #1362](https://github.com/category-labs/monad/pull/1362) * **\[Network params]** Set per-transaction gas limit of 30M gas * Ref: [monad-bft PR #2246](https://github.com/category-labs/monad-bft/pull/2246) * As discussed in the [Monad Initial Spec Proposal](https://forum.monad.xyz/t/monad-initial-specification-proposal/195) * **\[Network params]** Increase block gas limit from 150M gas (375 Mgas/s) to **200M** gas (500 Mgas/s) * Ref: [monad-bft PR #2266](https://github.com/category-labs/monad-bft/pull/2266) * **\[Network params]** Implement [dynamic base fee](/developer-essentials/gas-pricing) * Ref: [monad-bft PR #2207](https://github.com/category-labs/monad-bft/pull/2207) * As specified in the [Monad Initial Spec Proposal](https://forum.monad.xyz/t/monad-initial-specification-proposal/195) * Target gas is 80% of a block * Raise minimum base fee from 50 MON-gwei to **100 MON-gwei** * **\[EVM]** Enable [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) + blockhash buffer * Ref: [monad PR #1520](https://github.com/category-labs/monad/pull/1520) * **\[EVM]** Enable [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951) (P256VERIFY precompile support) * Ref: [monad PR #1518](https://github.com/category-labs/monad/pull/1518) * **\[EVM]** Enable [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537) (BLS12-381 precompiles) * Ref: [monad-bft PR #1342](https://github.com/category-labs/monad/pull/1342) and [monad-bft PR #1350](https://github.com/category-labs/monad/pull/1350) * **\[EVM]** Raise max contract size for `CREATE`/`CREATE2` to 128 kb * Ref: [monad PR #1440](https://github.com/category-labs/monad/pull/1440) * This brings `CREATE`/`CREATE2` to parity with toplevel contract creation transactions, which had their limit raised in `MONAD_TWO`. * Previously, they were unintentionally limited to Ethereum's max initcode size (49.152 kb) due to a bug #### Notable RPC/SDK changes * **\[SDK]** Added support for [Execution Events](/execution-events) * **\[RPC]** Extend `eth_call` support for preStateTracer and stateDiffTracer with `debug_traceCall` * Ref: [monad-bft PR #2275](https://github.com/category-labs/monad-bft/pull/2275) and [monad PR #1471](https://github.com/category-labs/monad/pull/1471) * **\[RPC]** Support the `withLog` parameter with `callTracer` * Ref: [monad-bft PR #2400](https://github.com/category-labs/monad-bft/pull/2400) * If `withLog` is set to true, `callTracer` includes event logs in the trace output #### Notable performance changes * **\[EVM]** Kernel caching of db reads and writes * Ref: [monad PR #1559](https://github.com/category-labs/monad/pull/1559) * Utilize available host memory to cache recent DB operations - this cache should increase performance of execution and RPC * **\[RPC]** Improve RPC Db Node Cache and make it memory bounded * Ref: [monad PR #1581](https://github.com/category-labs/monad/pull/1581) * Note a rename of RPC cli flags: `--eth-call-executor-node-lru-size` to `--eth-call-executor-node-lru-max-mem` and a new flag `--triedb-node-lru-max-mem` with both default to 100MB. #### Notable internal changes * **\[Node ops]** Remove `bft-fullnode` binary; only `bft` binary now * Ref: [monad-bft PR #2072](https://github.com/category-labs/monad-bft/pull/2072) * `bft-fullnode` removed from `deb` pkg for `v0.11` * **\[Node ops]** PeerDiscovery: Introduce precheck for peer self name address * **\[Consensus]** Blocksync: only select blocksync peers from connected nodes * Ref: [monad-bft PR #2401](https://github.com/category-labs/monad-bft/pull/2401) * **\[Consensus]** Raptorcast: DOS protection on decoding state cache * Ref: [monad-bft PR #2092](https://github.com/category-labs/monad-bft/pull/2092) * **\[Node ops]** Secondary raptorcast config change * Ref: [monad-bft PR #2378](https://github.com/category-labs/monad-bft/pull/2378) * Replace ambiguous secondary raptorcast `mode` parameter with `enable_publisher` and `enable_client` * **\[Node ops]** Dynamically reload prioritized full-nodes * Ref: [monad-bft PR #2364](https://github.com/category-labs/monad-bft/pull/2364) * **\[Node ops]** Fix wal2json * Ref: [monad-bft PR #2404](https://github.com/category-labs/monad-bft/pull/2404) * **\[Node ops/RPC]** Support the RPC CLI param `ws-sub-per-conn-limit` * Ref: [monad-bft PR #2161](https://github.com/category-labs/monad-bft/pull/2161) * Sets the maximum number of websocket subscriptions per connection (default to 100) * **\[Node ops/RPC]** Add configuration options for high eth-call pool * Ref: [monad-bft PR #2387](https://github.com/category-labs/monad-bft/pull/2387) ## v0.10.4 \[2025-08-18] Revision: [`MONAD_THREE`](/developer-essentials/changelog#revisions) (unchanged) Tags or hashes: * `monad-bft`: [tag `v0.10.4`](https://github.com/category-labs/monad-bft/releases/tag/v0.10.4) * `monad`: [hash `39c42e6`](https://github.com/category-labs/monad/tree/39c42e6ea5b2fccf8f4aa951da9d6281885511a5) #### Notable RPC/SDK changes * **\[RPC]** EIP-4844 related fields in RPC responses are removed. * Block headers returned from RPC will no longer have `blobGasUsed` , `excessBlobGas`, and `parentBeaconBlockRoot`. #### Notable performance changes * **\[Consensus]** Votes are now sent to current round leader. This is a consensus optimization that reduces the effective delay due to a round timeout * Ref: [monad-bft PR #2093](https://github.com/category-labs/monad-bft/pull/2093) * **\[Consensus]** Caching of recently-verified quorum certificates * Ref: [monad-bft PR #2167](https://github.com/category-labs/monad-bft/pull/2167) * **\[Node ops]** Enablement of trace calls is now controlled via `monad` (execution) command line arg `--trace_calls`. * To preserve legacy behavior, `--trace_calls` is currently enabled in the debian package. In the future we recommend disabling for validators and enabling it for RPC and archive nodes. * This allows voting validators to opt out of computing traces since they're only needed for RPC nodes. #### Notable internal changes * **\[Consensus]** TC forwarding to prioritized and public (non-dedicated) full nodes * Ref: [monad-bft PR #2149](https://github.com/category-labs/monad-bft/pull/2149) * Prior to `v0.10.4`, all full nodes were subject to lagging behind validators in the event of a timeout because round advancement due to TC was not forwarded (and still isn’t to dedicated full nodes). * As a result, after timeouts, full nodes would frequently forward transactions to the next three leaders relative to a stale state. This results in those transactions often missing and the ensuing blocks being comparatively empty. * **\[Node ops]** Bugfix for secondary raptorcast (round gap crash) that affected validators in Publisher mode * Ref: [monad-bft PR #2090](https://github.com/category-labs/monad-bft/pull/2090) * **\[Node ops]** `ledger-tail` improvements * Ref: [monad-bft PR #2144](https://github.com/category-labs/monad-bft/pull/2144) - Reduced memory usage on startup - `author_dns` field changed to `author_address` to reflect change in v10 addresses - Timeouts and finalizations are tracked and logged * **\[Consensus]** Txpool account preloading bugfix * Ref: [monad-bft PR #2108](https://github.com/category-labs/monad-bft/pull/2108) ## v0.10.3 \[2025-08-12] Revision: [`MONAD_THREE`](/developer-essentials/changelog#revisions) (upgrade; takes effect immediately - not time-gated) #### Notable protocol changes - `MONAD_THREE` * **\[Consensus]** Consensus mechanism upgraded from [Fast-HotStuff](https://arxiv.org/abs/2010.11454) to [MonadBFT](/monad-arch/consensus/monad-bft). This is a major upgrade that adds *(i)* [resilience to tail forking](/monad-arch/consensus/monad-bft#no-tail-forking) and *(ii)* [1-slot speculative finality](/monad-arch/consensus/monad-bft#speculative-finality) * **\[Network params]** Block time reduced from 500 ms to **400 ms** #### Notable RPC/SDK changes * **\[RPC]** Add support for [real-time data](/monad-arch/realtime-data) via [WebSocket](/reference/websockets) and shared memory queue access (docs coming soon): * Geth real-time events (via WebSocket) * Geth real-time events with Monad extensions (via WebSocket) * Real-time data via shared memory queue, for programs on a full node host using the execution event SDK #### Notable performance changes * **\[Execution]** Switch to JIT EVM. The bytecode of expensive or frequently-executed contracts is compiled directly to native code for faster execution * **\[Execution]** Switch to using file pointer-based IPC to execute blocks. Previously, the client was using a write-ahead log (WAL), which did not take advantage of available consensus information and could result in avoidable execution #### Notable internal changes * **\[Node ops]** Adds peer discovery * **\[Node ops]** Adds full node [RaptorCast](/monad-arch/consensus/raptorcast), making the full node network scalable ## v0.9.3 \[2025-05-29] Revision: [`MONAD_TWO`](/developer-essentials/changelog#revisions) (unchanged) #### Notable RPC/SDK changes * **\[RPC]** `eth_call` and `eth_estimateGas` limits * RPC providers can now set individual per-transaction limits on maximum gas for `eth_call` and `eth_estimateGas` * Previously the limit would always be the block gas limit (150M), now the RPC provider may choose (default: 30M) * Controlled with `--eth-call-provider-gas-limit` and `--eth-estimate-gas-provider-gas-limit` * Add a maximum timeout for queueing when executing `eth_call` and `eth_estimateGas` * Controlled with `--eth_call_executor_queuing_timeout` #### Notable performance changes * **\[RPC]** Improve overall `eth_call` performance by maintaining separate queues for cheap and expensive `eth_call` operations, so that cheap `eth_call` operations will not be queued behind expensive ones. * This adds two new RPC error strings: * `failure to submit eth_call to thread pool: queue size exceeded` * `failure to execute eth_call: queuing time exceeded timeout threshold` * **\[RPC]** Added an archive index for `eth_getLogs` to support queries with address and/or topic filters * This enables larger block ranges to be queried efficiently, with work proportional to number of matching logs instead of number of blocks in range * **\[Execution]** Better bounding of TrieDB traversals #### Notable internal changes * **\[Node ops]** Performance improvements for slow statesync client upsert * **\[Node ops]** Bugfix for execution delay and abrupt history length drops caused by premature soft reset * **\[Node ops]** `keystore` QOL improvements * Support for importing from a private key in hex string format (previously required conversion to json file) * More helpful documentation via `--help` command ## testnet-1 active set expansion \[2025-05-02] #### Notable internal changes * **\[Network params]** Testnet validator set expanded from 72 to **99** nodes ## v0.9.2 \[2025-04-05] Revision: [`MONAD_TWO`](/developer-essentials/changelog#revisions) (unchanged) #### Notable performance changes * **\[RPC]** Update to the `eth_call` execution implementation - uses fewer threads to achieve the same concurrency as in `v0.9.1` #### Notable internal changes * **\[Node ops]** Support for a faster [statesync](/monad-arch/consensus/statesync) mechanism * **\[RPC]** RPC no longer accepts requests while node is statesyncing * **\[Node ops]** Some reliability and efficiency improvements to how statesync traffic is carried between nodes * **\[Node ops]** Bugfixes for execution crashes (`monad::mpt::deserialize_node_from_buffer`, `'Resource temporarily unavailable`) - **\[Node ops]** Bug fix for unbounded blocksync requests that result in node OOM failures - **\[Node ops]** Removal of DNS resolution panic on start up ## v0.9.1 \[2025-03-24] Revision: [`MONAD_TWO`](/developer-essentials/changelog#revisions) (unchanged) #### Notable internal changes * **\[Node ops]** Bugfix for blocksync errors * **\[Node ops]** Reliability and efficiency improvements to statesync ## v0.9.0 \[2025-03-14] Revision: [`MONAD_TWO`](/developer-essentials/changelog#revisions) (upgrade; takes effect at 2025-03-14 at 19:00 GMT) #### Notable protocol changes - `MONAD_TWO` * **\[EVM]** Max contract size increased from 24kb to **128kb** * [example 123 kb contract](https://testnet.monadvision.com/address/0x0E820425e07E4a992C661E4B970e13002d6e52B9?tab=Contract) #### Notable RPC/SDK changes * **\[RPC]** `debug_traceTransaction` fixes * Fixed a bug where, within one transaction, only the first 100 calls were being traced * Added `error` and `revertReason` fields to response data #### Notable performance changes * **\[Consensus]** Dataplane v2 - simpler and more efficient implementation; small performance improvement in broadcast time * **\[RPC]** Improvements to RPC performance for `eth_call` - **\[RPC]** Removed redundant sender\_recovery operation on raw transactions received via RPC #### Notable internal changes * **\[Node ops]** Statesync improvements to mitigate negative performance effects on upstream validator nodes - **\[RPC]** EIP-2 signature validation added to RPC transaction validation - **\[Node ops]** Miscellaneous tracing, logging and metrics additions - **\[Consensus]** RaptorCast performance improvement when dealing with invalid symbols ## v0.8.1 \[2025-02-14] Revision: [`MONAD_ONE`](/developer-essentials/changelog#revisions) (upgrade; takes effect at 2025-02-14 at 19:00 GMT) #### Notable protocol changes - `MONAD_ONE` * **\[Network params]** Block time reduced from 1s to **500 ms** * **\[Network params]** Block gas limit reduced from 300M to **150M** (to keep gas limit consistent) * **\[EVM]** Transactions are [charged](/developer-essentials/gas-pricing) based on gas limit, not gas consumed #### Notable RPC/SDK changes * **\[RPC]** UX improvements for [transaction status](/monad-arch/transaction-lifecycle). RPC nodes track status of transactions submitted to them in order to provide updates to users. # Differences between Monad and Ethereum Source: https://docs.monad.xyz/developer-essentials/differences To take into account these differences, there is a custom [Monad Foundry](/tooling-and-infra/toolkits/monad-foundry) to ensure that your local development environment matches Monad's onchain behavior. This list assembles notable behavioral differences between Monad and Ethereum from the perspective of a smart contract developer. ## Virtual Machine 1. The maximum contract code size limit is 128 KB (up from 24 KB in Ethereum). Consequently, the max init code size limit is 256 KB (up from 48 KB in Ethereum). 2. A few opcodes and precompiles are repriced, to reweight relative scarcities of resources due to Monad optimizations. See [Opcode Pricing](/developer-essentials/opcode-pricing). 3. The `secp256r1` (P256) verification precompile at `0x0100` per [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951) is supported, enabling on-chain verification of WebAuthn/passkey signatures. See [Precompiles](/developer-essentials/precompiles#p256-signature-verification). ## Transactions 1. Transactions are charged based on gas limit rather than gas usage, i.e. total gas deducted from the sender's balance is `value + gas_bid * gas_limit`. As discussed in [Gas in Monad](/developer-essentials/gas-pricing), this is a DoS-prevention measure for asynchronous execution. 2. Consensus and execution utilize the [Reserve Balance](/developer-essentials/reserve-balance) mechanism to ensure that all transactions included in consensus can be paid for. This mechanism places light restrictions on transaction inclusion at consensus time, and defines select conditions under which a transaction will revert at execution time. 3. Due to the Reserve Balance mechanism, you may see transactions in the blockchain which ultimately fail due to trying to spend too much MON relative to account balance. These transactions still pay for gas and are valid transactions whose result is execution reversion. This isn't a protocol difference, as many reverting Ethereum transactions are included in the chain, but it may be different from expectation. [Longer discussion](/developer-essentials/reserve-balance#transactions-that-are-included-but-revert). 4. Transaction type 3 (EIP-4844 aka blob transactions) is not supported. 5. There is no global mempool. For efficiency, transactions are forwarded to the next few leadersas described in [Local Mempool](/monad-arch/consensus/local-mempool). ## EIP-7702 Delegation 1. If an EOA is EIP-7702-delegated, its balance cannot be lowered below 10 MON due to the [Reserve Balance](/developer-essentials/reserve-balance) rules. If the delegation is removed, dipping below 10 MON is allowed. [Discussion](/developer-essentials/eip-7702#delegated-eoas-cant-dip-below-10-mon). 2. If an EOA is EIP-7702-delegated, when it is called as a smart contract, the `CREATE` and `CREATE2` opcodes are banned. [Discussion](/developer-essentials/eip-7702#delegated-contract-code-cannot-call-createcreate2). ## Historical Data Due to Monad's high throughput, full nodes do not provide access to arbitrary historic state, as this would require too much storage. See [Historical Data](/developer-essentials/historical-data) for a fuller discussion. ## RPC See: [RPC Differences](/reference/rpc-differences) # EIP-7702 on Monad Source: https://docs.monad.xyz/developer-essentials/eip-7702 ## Summary [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) is supported on Monad with the same workflow as in Ethereum: users sign a message authorizing the delegation to a specific account, which they or someone else can submit using transaction type `0x04`. After that occurs, the EOA becomes "delegated", i.e. it can be called like a smart contract account with code equal to the account it has delegated to. EIP-7702-delegated accounts behave the same on Monad as on Ethereum in most cases. The two main nuances are: 1. If an EOA is EIP-7702-delegated, transactions that would reduce its balance to below 10 MON will unconditionally revert. * If the balance is below 10 MON but is unchanged or increased by this transaction, the transaction succeeds * If the delegation is removed, dipping below 10 MON is allowed under certain conditions * [Details](#delegated-eoas-cant-dip-below-10-mon) 2. When the EOA is treated like a smart contract, that code cannot call `CREATE` or `CREATE2`. [Details](#delegated-contract-code-cannot-call-createcreate2) ## EIP-7702 Primer [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) allows Externally Owned Accounts (EOAs) to add code to themselves, granting themselves the ability to add new capabilities previously reserved for smart contract accounts, such as transaction batching, gas sponsorship, and alternative authentication. To do this, the EOA signs an authorization designating a specific address as the source of its code. EIP-7702 introduces a new transaction type (type `0x04`) that submits this authorization. The authorization can be submitted by the EOA themselves, or by anyone else. EIP-7702 enables account abstraction directly on an EOA. It is an extension of EIP-4337, which introduced standards for smart contract wallets with flexible validation logic, but which had to assume a separate UserOp mempool and bundler infrastructure for submitting transactions. In Ethereum's account-based model, there are two separate roles - "fee payer" (transaction submitter, i.e. who pays for gas to execute a transaction) and "asset owner" (spend authorizer, i.e. who holds keys with the power to spend from a balance). Originally, EOAs played both roles. Under EIP-4337, the roles were split - a smart contract wallet holds title to assets (and has its own logic for validating that spend was authorized), but fees must still be paid by an EOA, hence the roles are definitively split. With EIP-7702, EOAs are allowed to have code, thus allowing the same account to play both roles again. In essence, EIP-7702 makes it possible for today's EOAs to gain smart wallet-like powers such as multisig, social recovery, session keys, and gas sponsorship **without abandoning their current accounts**, bridging the gap between the old EOA model and the future of full account abstraction. ## EIP-7702 on Monad EIP-7702-delegated accounts behave the same on Monad as on Ethereum in most cases. The two main nuances are: 1. If an EOA is EIP-7702-delegated, transactions that would reduce its balance to below 10 MON will unconditionally revert. * If the balance is below 10 MON but is unchanged or increased by this transaction, the transaction succeeds * If the delegation is removed, dipping below 10 MON is allowed under certain conditions * [Details](#delegated-eoas-cant-dip-below-10-mon) 2. When the EOA is treated like a smart contract, that code cannot call `CREATE` or `CREATE2`. [Details](#delegated-contract-code-cannot-call-createcreate2) ### Delegated EOAs can't dip below 10 MON In Monad, the [Reserve Balance](/developer-essentials/reserve-balance) rules carve out a budget of 10 MON for consensus-time balance checks for inflight transactions from each EOA. ("Inflight" means transactions seen by consensus since the delayed view of state, 3 blocks ago.) Execution protects that budget by reverting if the balance of any EOA would *dip below* 10 MON by an amount greater than the transaction's max gas fee. (*"Dip below"* means *"decrement **and** drop below"*; if an EOA's MON balance is unchanged, that doesn't count as dipping.) * An [exception](/developer-essentials/reserve-balance#addressing-the-drawback) is made to this execution-time policy for **undelegated** EOAs where a transaction hasn't been seen from this EOA in several blocks. That exception is what allows undelegated EOAs to submit transactions that would cause their balance to dip below 10 MON by amounts greater than the transaction's max gas fee. * However, this exception can't be made for EIP-7702-delegated accounts. Delegation breaks the invariant that an EOA's balance can only be reduced by transactions signed by that EOA. There is not a reliable way to be sure (at the time of consensus) that another inflight transaction hasn't spent funds from an EIP-7702-delegated account, therefore the exception made for undelegated accounts doesn't apply to **delegated** accounts. An delegated account may be emptied by undelegating first. To be clear, EIP-7702-delegated accounts are **not** required to have a 10 MON balance. For example, a delegated EOA *A* with a balance of 5 MON can still be called by a gas sponsor, and the transaction will succeed as long as *A* ends with 5 MON or more still. This is discussed further [here](/developer-essentials/reserve-balance#eip-7702-delegated-accounts). ### Delegated contract code cannot call `CREATE/CREATE2` There is another difference: when contract code is executing in the context of an EIP‑7702‑delegated EOA (for example, because a contract `CALL`s that EOA’s address), the `CREATE` and `CREATE2` opcodes are not permitted. Any attempt to execute `CREATE` or `CREATE2` in such a frame causes that call frame to revert, and the caller (if any) observes the call as failed (the `CALL`/`DELEGATECALL`/`CALLCODE` returns 0). This prevents delegated code from changing the EOA’s nonce in ways that would make it difficult to statically validate transactions from that account. In contrast, normal contract‑creation transactions sent from a delegated EOA (transactions with no `to` field, where the transaction `data` is treated as init code) are allowed and behave as on Ethereum. Other than those differences, EIP-7702-delegated accounts behave normally with respect to both ordinary transactions sent by the EOA, and transactions that treat the EOA as a smart contract. ## FAQ An EIP-7702 transaction is an [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) transaction with TransactionType `0x04`. This transaction type is only used to set code; once the code is set for an EOA, subsequent transactions will most likely use the default transaction type (TransactionType `0x02`; [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)) transactions to interact with the EOA. Here is an example of an EIP-7702 transaction in a block explorer: explorer_example No, the EOA can sign an "authorization tuple" which can then be used by the sponsoring entity to send the transaction. This will allow EOAs to behave like smart contracts without any funds for gas! On successful delegation to a smart contract, the following code is deployed at the EOA's address. `0xef0100` (3 bytes) + `smart_contract_address` (20 bytes) Example: If `0xabc...` (EOA) delegates to `0x493...` (smart contract), then code at `0xabc...` (EOA) is `0xef0100493...` Thus, you may determine the delegated address by inspecting the code. The chain of delegation is not followed; only the immediate code that the first EOA is pointing to is used. Initiate a `0x04` type transaction from the EOA with `0x000...` (dead address) as the new delegated account. No, the delegation remains valid in perpetuity unless another `0x04` transaction is sent changing the delegation to a different (or null) account. Yes; after delegating with EIP-7702, any EOA may behave like an EIP-4337 smart account. Simply initiate a transaction of type `0x04` while pointing to an address containing code for the 4337-compatible smart account. If that's an Ethereum precompile, the `CALL`, `STATICCALL`, `DELEGATECALL`, and `CALLCODE` opcodes, when called with enough gas, proceed with execution as if the EOA has no code. If that's a Monad precompile, calls revert as if the EOA has code starting with an invalid instruction. ```ts theme={null} import { createWalletClient, http, parseEther } from 'viem' import { monadTestnet } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' const account = privateKeyToAccount('0x...') const walletClient = createWalletClient({ account, chain: monadTestnet, transport: http(), }) const authorization = await walletClient.signAuthorization({ account, contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2' }) const hash = await walletClient.sendTransaction({ authorizationList: [authorization], data: '0xdeadbeef', to: walletClient.account.address, }) ``` # Gas Pricing Source: https://docs.monad.xyz/developer-essentials/gas-pricing ## Summary Monad, like Ethereum, charges for processing transactions based on the complexity of the transaction. Complexity is measured in units of **gas**. This page summarizes how gas is charged, i.e. the conversion between the gas of a transaction and the amount of MON that a user will have to pay. A separate page, [Opcode Pricing](/developer-essentials/opcode-pricing), describes how much each opcode costs in units of gas. | Feature | Detail | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Gas charged** | The gas charged for a transaction is the **gas limit**. [Discussion](#gas-limit-not-gas-used) | | **Price per gas** | EIP-1559-compatible, i.e. price paid per unit of gas is the sum of a system-controlled base fee and a user-specified priority fee. [Discussion](#eip-1559-compatibility) | | **Base fee** | Base fee (aka `base_price_per_gas`) follows a dynamic controller, similar to the EIP-1559 controller but with slower increases and faster decreases. [Details](#base_price_per_gas-controller) | | **Minimum base fee** | 100 MON-gwei (`100 * 10^-9 MON`) | | **Block gas limit** | 200M gas | | **Transaction gas limit** | 30M gas | | **Opcode pricing** | See [Opcode Pricing](/developer-essentials/opcode-pricing) | | **Transaction ordering** | Default Monad client behavior is to order transactions according to a Priority Gas Auction (descending total gas price). | These changes are covered formally in the [Monad Initial Spec Proposal](https://category-labs.github.io/category-research/monad-initial-spec-proposal.pdf) ## Gas definitions A common point of confusion among users is the distinction between **gas** of a transaction (units of work) and the **gas price** of a transaction (price in native tokens per unit of work). | Feature | Definition | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Gas** | A unit of work. Gas measures the amount of work the network has to do to process something.

Since the network has multiple kinds of resources (network bandwidth, CPU, SSD bandwidth, and state growth), gas is inherently a projection from many dimensions into a single one. | | **Gas price (price\_per\_gas)** | The **price** (in native tokens) paid **to process one unit** of gas. | | **Gas limit** | The maximum **number of units of gas** that a transaction is allowed to consume. | ## Gas limit, not gas used In Monad, the gas charged for a transaction is the gas limit set in the transaction, rather than the gas used in the course of execution. This is a design decision to support asynchronous execution. Under asynchronous execution, leaders build blocks (and validators vote on block validity) prior to executing. If the protocol charged `gas_used`, a user could submit a transaction with a large `gas_limit` that actually consumes very little gas. This transaction would take up a lot of space toward the block gas limit but wouldn't pay very much for taking up that space, opening up a DOS vector. ``` gas_paid = gas_limit * price_per_gas ``` ## EIP-1559 Compatibility Monad supports EIP-1559. EIP-1559 (type 2) transactions have the parameters `priority_price_per_gas` and `max_price_per_gas`, which, together with `base_price_per_gas` (a system parameter that changes each block), determine the gas bid for the transaction: ``` price_per_gas = min(base_price_per_gas + priority_price_per_gas, max_price_per_gas) ``` Notes: * `base_price_per_gas` is a system parameter that changes each block. Every transaction in the same block will have the same `base_price_per_gas` * Users specify `priority_price_per_gas` and `max_price_per_gas` when signing a transaction * Since everyone in the same block will pay the same `base_price_per_gas`, the `priority_price_per_gas` is a way for users to pay more to prioritize their transactions. * Since users don't determine `base_price_per_gas`, the `max_price_per_gas` is a safeguard that limits the amount they may end up paying. Of course, if that value is set too low, the transaction will not end up being chosen for inclusion. [This](https://www.blocknative.com/blog/eip-1559-fees) article provides another good explanation of EIP-1559 gas pricing. ## `base_price_per_gas` controller Monad uses a different controller for `base_price_per_gas` than Ethereum: $$ \begin{align*} \mathrm{block\_gas}_{k} &= \sum\limits_{\mathrm{tx} \in \mathrm{block}_k}\mathrm{gas\_limit}_\mathrm{tx}\\ \mathrm{base\_price\_per\_gas}_{k+1} &= \max\left\{\text{min\_base\_price\_per\_gas}, \mathrm{base\_price\_per\_gas}_k \cdot \exp \left( \eta_k \cdot \frac{\mathrm{block\_gas}_{k} - \text{target}}{\text{block\_gas\_limit} - \text{target}} \right) \right\} \\ \eta_k &= \frac{\text{max\_step\_size}\cdot\epsilon}{\epsilon+\sqrt{\mathrm{moment}_k - \mathrm{trend}_k^2}} \\ \mathrm{trend}_{k+1} &= \beta\cdot \mathrm{trend}_k + (1-\beta)\cdot \left(\text{target}-\mathrm{block\_gas}_{k} \right) \\ \mathrm{moment}_{k+1} &= \beta\cdot \mathrm{moment}_k + (1-\beta)\cdot \left(\text{target}-\mathrm{block\_gas}_{k} \right)^2 \end{align*} $$ This inductive formula starts with $$ \begin{align*} \mathrm{base\_price\_per\_gas}_{0} &= 0 \\ \mathrm{moment}_{0} &= 0 \\ \mathrm{trend}_{0} &= 0 \end{align*} $$ and with the following parameters: $$ \begin{align*} \mathrm{max\_step\_size} &= 1/28 \\ \mathrm{target} &= 160\text{M}\ \text{(80\% full)}\ \\ \beta &= 0.96 \\ \epsilon &= \mathrm{target} = 160\text{M} \end{align*} $$ And $$ \text{min\_base\_price\_per\_gas} = 100\ \text{MON-gwei}\ (100 \times 10^{-9}\ \text{MON}) $$ Compared to the `base_price_per_gas` controller in Ethereum, this controller increases more slowly and decreases more quickly. This is to avoid underutilization of blockspace due to an overpriced `base_price_per_gas`. For a more comprehensive discussion of Monad controller design considerations and behavior, check out [this](https://www.category.xyz/blogs/redesigning-a-base-fee-for-monad) blog post from Category Labs. ## Recommendations for developers ### Set the gas limit explicitly if it is constant Many on-chain actions have a fixed gas cost. The simplest example is that a transfer of native tokens always costs 21,000 gas, but there are many others. For actions where the gas cost of the transaction is known ahead of time, it is recommended to set it directly prior to handing the transaction off to the wallet. This offers several benefits: * It reduces latency and gives users a better experience, since the wallet doesn't have to call `eth_estimateGas` and wait for the RPC to respond. * It retains greater control over the user experience, avoiding cases where the wallet sets a high gas limit in a corner case as described in the warning below. Some wallets, including MetaMask, are known to have the following behavior: when `eth_estimateGas` is called and the contract call reverts, they set the gas limit for this transaction to a very high value. This is the wallet's way of giving up on setting the gas limit and accepting whatever gas usage is at execution time. However, it doesn't make sense on Monad where the full gas limit is charged. Contract call reversion happens whenever the user is trying to do something impossible. For example, a user might be trying to mint an NFT that has minted out. If the gas limit is known ahead of time, setting it explicitly is best practice, since it ensures the wallet won't handle this case unexpectedly. # Historical Data Source: https://docs.monad.xyz/developer-essentials/historical-data ## Summary Monad full nodes provide access to all historic transactional data (blocks, transactions, receipts, events, and traces). Monad full nodes do not provide access to arbitrary historic state.
There is a special RPC service at [`https://rpc-mainnet.monadinfra.com`](https://rpc-mainnet.monadinfra.com) that provides access to historical data. The following methods at that service support queries referring to historical state:
## Background Blockchains are stateful systems; there are two main kinds of data: 1. **transactional data** (the list of transactions and their artifacts); and 2. **state data** (the current state of the world, resulting from applying those transactions sequentially; stored in a merkle trie). Transactional data consists of * blocks * transactions * receipts produced by executing those transactions * events (logs) emitted by smart contracts in the course of execution * detailed traces from each transaction's execution State data consists of * for each account, its native token balance * for each smart contract, the storage mapping (which maps storage slots to values) A typical node in a blockchain holds the current state, which is constantly being updated as new transactions are added. Recent historical states may be available as well, depending on how costly each incremental version is and how much disk space is available. For reference, imagine maintaining a MySQL or Postgres table, where each `INSERT` or `UPDATE` query is a transaction. If the table is small enough, then it may be feasible to cache every new version of the table, but if it's a large table, you would probably expect to only have access to the current version. The following describes historical data access in Monad: ## Transactional data Monad full nodes provide access to all historic transactional data (blocks, transactions, receipts, events, and traces).[^1] ## State In Ethereum, a "full node" offers chain state for the current block and each block up to 128 blocks ago, while an "archive node" offers per-block chain state since genesis. That is, an Ethereum "archive node" is a differently-configured full node, run on a box with a large disk. This terminology is described further [here](https://chainstack.com/evm-nodes-a-dive-into-the-full-vs-archive-mode/). In Monad, every "full node" is an "archive node" in the sense that every node maintains as many historical per-block state tries as it can. This means that the lookback depends on the size of disk chosen by the RPC provider. For a 2 TB SSD, this recently has corresponded to about 40,000 blocks, although it depends on the amount of state diffs in each block. Due to Monad's high throughput, **full nodes do not provide access to arbitrary historic state**, as this would require too much storage.[^2] Methods like `eth_call` may reference recent states up to the point where the state trie was evicted. When writing smart contracts, it is recommended to use events to log any state that will be needed later, or use a [smart contract indexer](/tooling-and-infra/indexers/indexing-frameworks) to compute it off-chain. [^1]: Implementation detail: recent transactional data is stored directly on the node, while older data is stored in a separate archive node as configured and operated by the RPC provider. [^2]: With sufficient SSD capacity, a Monad full node would behave similarly to an Ethereum "archive" node in providing access to historical state since genesis. But in practice, due to larger changesets for each block (up to 5,000 transactions per block vs \~200 for Ethereum, i.e. 25x larger blocks) and more frequent blocks (0.4s for Monad vs 12s for Ethereum, i.e. 30x more frequent) no RPC provider is currently offering access to arbitrarily-far-back historical state. # Developer Essentials Source: https://docs.monad.xyz/developer-essentials/index ## Quick Reference Chain config, RPC, block explorers and canonical deployments Token addresses and bridge links Everything you need to know when deploying on Monad Third-party infra supporting Monad Testnet JSON-RPC API ## Finer Details Supported transaction types and format (TLDR: same as Ethereum, except no EIP-4844 transaction type) Why the gas limit is charged + details of the base fee controller Opcode pricing adjustments Supported precompiles, including P256 signature verification and staking Staking behavior + staking precompile reference How the reserve balance ensures safety under asynchronous execution EIP-7702 reference Data retention policy for state and ledger data ## Best Practices Recommendations for building high-performance apps ## Monad's Architecture in Depth One-page summary of Monad architecture ## Changelog Revision list and changelog ## Testnet Links and canonical deployments for testnets ## Get Support Join other Monad developers on Discord Join other Monad developers on Telegram # Network Information - Mainnet Source: https://docs.monad.xyz/developer-essentials/network-information/index
Name Value
Network Name `Monad Mainnet`
Chain ID `143`
Currency Symbol `MON`
RPC URL [see below](#public-rpc-endpoints)
Block Explorer (MonadVision) [https://monadvision.com](https://monadvision.com)
Block Explorer (Monadscan) [https://monadscan.com](https://monadscan.com)
Block Explorer (Socialscan) [https://monad.socialscan.io](https://monad.socialscan.io)
Network Visualization [https://gmonads.com](https://gmonads.com)
Current version / revision [`v0.13.1`](/developer-essentials/changelog/releases#v0131) / [`MONAD_NINE`](/developer-essentials/changelog#revisions)
Other [block explorers](/tooling-and-infra/block-explorers) supported: * Detailed traces: [Phalcon Explorer](https://blocksec.com/explorer) and [Tenderly](https://dashboard.tenderly.co/explorer) * UserOps: [Jiffyscan](https://jiffyscan.xyz/?network=monad) ### Public RPC Endpoints Public RPC endpoints are rate-limited but should be sufficient for basic usage. If you need a higher-limit RPC endpoint, please see [RPC Providers](/tooling-and-infra/rpc-providers). Websocket endpoints start with `wss://`. See [Websocket Reference](/reference/websockets) for further information.
RPC URL Provider Rate Limits Batch Call Limit Notes
`https://rpc.monad.xyz`
`wss://rpc.monad.xyz`
QuickNode 25 rps 100
`https://rpc1.monad.xyz`
`wss://rpc1.monad.xyz`
Alchemy 15 rps 100 `debug_` and `trace_` methods disabled
`https://rpc2.monad.xyz`
`wss://rpc2.monad.xyz`
Goldsky Edge 300 per 10s 10 historical state lookups (e.g. `eth_call`) supported; see [discussion](/developer-essentials/historical-data)
`https://rpc3.monad.xyz`
`wss://rpc3.monad.xyz`
Ankr 300 per 10s 10 `debug_` methods disabled
`https://rpc-mainnet.monadinfra.com`
`wss://rpc-mainnet.monadinfra.com`
MF 20 rps 1 historical state lookups (e.g. `eth_call`) supported; see [discussion](/developer-essentials/historical-data)
See [RPC Limits](/reference/rpc-limits) for additional detail on method-specific limits. ### Supported Infrastructure See the [Tooling and Infrastructure](/tooling-and-infra) page for a list of providers supporting mainnet. ### Canonical Contracts
Name Address
Wrapped MON [`0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A`](https://monadvision.com/address/0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A)
[Create2Deployer](https://github.com/pcaversaccio/create2deployer) [`0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2`](https://monadvision.com/address/0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2)
[CreateX](https://github.com/pcaversaccio/createx) [`0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed`](https://monadvision.com/address/0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed)
[ERC-2470 Singleton Factory](https://eips.ethereum.org/EIPS/eip-2470) [`0xce0042b868300000d44a59004da54a005ffdcf9f`](https://monadvision.com/address/0xce0042b868300000d44a59004da54a005ffdcf9f)
[ERC-4337 EntryPoint v0.6](https://github.com/eth-infinitism/account-abstraction/blob/v0.6.0/contracts/core/EntryPoint.sol) [`0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`](https://monadvision.com/address/0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789)
[ERC-4337 SenderCreator v0.6](https://github.com/eth-infinitism/account-abstraction/blob/v0.6.0/contracts/core/SenderCreator.sol) [`0x7fc98430eAEdbb6070B35B39D798725049088348`](https://monadvision.com/address/0x7fc98430eAEdbb6070B35B39D798725049088348)
[ERC-4337 EntryPoint v0.7](https://github.com/eth-infinitism/account-abstraction/releases) [`0x0000000071727De22E5E9d8BAf0edAc6f37da032`](https://monadvision.com/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032)
[ERC-4337 SenderCreator v0.7](https://github.com/eth-infinitism/account-abstraction/blob/v0.7.0/contracts/core/SenderCreator.sol) [`0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C`](https://monadvision.com/address/0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C)
[ERC-6492 UniversalSigValidator](https://eips.ethereum.org/EIPS/eip-6492) [`0xdAcD51A54883eb67D95FAEb2BBfdC4a9a6BD2a3B`](https://monadvision.com/address/0xdAcD51A54883eb67D95FAEb2BBfdC4a9a6BD2a3B)
[Foundry Deterministic Deployer](https://getfoundry.sh/guides/deterministic-deployments-using-create2/) [`0x4e59b44847b379578588920ca78fbf26c0b4956c`](https://monadvision.com/address/0x4e59b44847b379578588920ca78fbf26c0b4956c)
[Multicall3](https://www.multicall3.com/) [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://monadvision.com/address/0xcA11bde05977b3631167028862bE2a173976CA11)
[MultiSend](https://github.com/safe-fndn/safe-smart-account/blob/v1.3.0/contracts/libraries/MultiSend.sol) [`0x998739BFdAAdde7C933B942a68053933098f9EDa`](https://monadvision.com/address/0x998739BFdAAdde7C933B942a68053933098f9EDa)
[MultiSendCallOnly](https://github.com/safe-fndn/safe-smart-account/blob/v1.3.0/contracts/libraries/MultiSendCallOnly.sol) [`0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B`](https://monadvision.com/address/0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B)
[Permit2](https://github.com/Uniswap/permit2) [`0x000000000022d473030f116ddee9f6b43ac78ba3`](https://monadvision.com/address/0x000000000022d473030f116ddee9f6b43ac78ba3)
[Safe](https://github.com/safe-fndn/safe-smart-account/blob/v1.3.0/contracts/GnosisSafe.sol) [`0x69f4D1788e39c87893C980c06EdF4b7f686e2938`](https://monadvision.com/address/0x69f4D1788e39c87893C980c06EdF4b7f686e2938)
[SafeL2](https://github.com/safe-fndn/safe-smart-account/blob/v1.3.0/contracts/GnosisSafeL2.sol) [`0xfb1bffC9d739B8D520DaF37dF666da4C687191EA`](https://monadvision.com/address/0xfb1bffC9d739B8D520DaF37dF666da4C687191EA)
[SafeSingletonFactory](https://github.com/safe-fndn/safe-singleton-factory/blob/main/source/deterministic-deployment-proxy.yul) [`0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7`](https://monadvision.com/address/0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7)
[SimpleAccount](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol) [`0x68641DE71cfEa5a5d0D29712449Ee254bb1400C2`](https://monadvision.com/address/0x68641DE71cfEa5a5d0D29712449Ee254bb1400C2)
[Simple7702Account](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/Simple7702Account.sol) [`0xe6Cae83BdE06E4c305530e199D7217f42808555B`](https://monadvision.com/address/0xe6Cae83BdE06E4c305530e199D7217f42808555B)
[Sub Zero VanityMarket](https://github.com/Philogy/sub-zero-contracts) [`0x000000000000b361194cfe6312EE3210d53C15AA`](https://monadvision.com/address/0x000000000000b361194cfe6312EE3210d53C15AA)
[Zoltu Deterministic Deployment Proxy](https://github.com/Zoltu/deterministic-deployment-proxy) [`0x7A0D94F55792C434d74a40883C6ed8545E406D12`](https://monadvision.com/address/0x7A0D94F55792C434d74a40883C6ed8545E406D12)
### Ecosystem contract addresses See the [protocols](https://github.com/monad-crypto/protocols) repo. ### Tokens See [Tokens and Bridges](/developer-essentials/network-information/tokens-and-bridges) or the [token-list](https://github.com/monad-crypto/token-list) repo. # Tokens and Bridges Source: https://docs.monad.xyz/developer-essentials/network-information/tokens-and-bridges This page is generated from the [**token-list**](https://github.com/monad-crypto/token-list) repo. See the repo (or the [raw json](https://raw.githubusercontent.com/monad-crypto/token-list/refs/heads/main/tokenlist-mainnet.json)) for a fuller list of tokens. Assets issued natively on another chain are bridged to Monad via a few different bridges, including LayerZero OFT, Chainlink CCIP, Hyperlane, and the 2/2 NTT Bridge, typically designated by the asset issuer. To bridge an asset to Monad, users typically have two options: 1. Use a frontend integrating the native bridge, e.g. * [Stargate](https://stargate.finance/?dstChain=monad\&dstToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) for LayerZero OFT * [Transporter](https://app.transporter.io/?tab=token\&to=monad) for Chainlink CCIP * [Nexus](http://nexus.hyperlane.xyz/) for Hyperlane * [MonadBridge](https://monadbridge.com/?toChain=Monad\&toToken=WETH\&fromChain=Ethereum\&fromToken=ETH) for 2/2 NTT 2. Use a bridge aggregator that incorporates multiple solvers and cross-chain swaps, and has selective coverage of native bridges: * [Jumper](https://jumper.exchange/?fromChain=1\&toChain=143\&toToken=0x0000000000000000000000000000000000000000) - incorporates multiple solvers and select OFTs and CCIP-bridged assets via [Glacis](https://li.fi/knowledge-hub/li-fi-partners-with-glacis-to-accelerate-the-adoption-of-interop-token/). Option 1 ensures the native (wrapping/unwrapping) mechanism is considered, but Option 2 may be convenient if bridging from a remote chain or executing a cross-chain swap. The tables below link to the appropriate bridge (option 1) for each asset. Notable markets are linked based on TVL/volume. ## Bridged Assets ### Dollar-related
Symbol Name Address Bridge + Link Bridgeable from Select Markets
`AUSD` Agora USD [`0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a`](https://monadvision.com/token/0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a\&dstChain=monad\&dstToken=0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a)) Eth, Avax, Base [Curve 3pool](https://www.curve.finance/dex/monad/pools/factory-stable-ng-2)
[Uni AUSD/USDC](https://app.uniswap.org/explore/pools/monad/0xd112fde908d7342135fc7297cc53d25bf7a11d6c6e21fe7ac3e73c40f70827e8)
`USDC` USD Coin [`0x754704Bc059F8C67012fEd69BC8A327a5aafb603`](https://monadvision.com/token/0x754704Bc059F8C67012fEd69BC8A327a5aafb603) Circle CCTP ([link](https://monadbridge.com/?fromChain=Ethereum\&fromToken=USDC\&toChain=Monad\&toToken=USDC)) 17 chains [Curve 3pool](https://www.curve.finance/dex/monad/pools/factory-stable-ng-2)
`USDT0` Tether USD [`0xe7cd86e13AC4309349F30B3435a9d337750fC82D`](https://monadvision.com/token/0xe7cd86e13AC4309349F30B3435a9d337750fC82D) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0xdAC17F958D2ee523a2206206994597C13D831ec7\&dstChain=monad\&dstToken=0xe7cd86e13AC4309349F30B3435a9d337750fC82D)) 18 chains [Curve 3pool](https://www.curve.finance/dex/monad/pools/factory-stable-ng-2)
[Uni AUSD/USDT0](https://app.uniswap.org/explore/pools/monad/0xe56868928b91fcd5ebeada3d0ec8767f2bbfeb1e7da181203d13f6af76b03bf9)
`USD1` USD1 [`0x111111d2bf19e43C34263401e0CAd979eD1cdb61`](https://monadvision.com/token/0x111111d2bf19e43C34263401e0CAd979eD1cdb61) Chainlink CCIP ([Transporter](https://app.transporter.io/?from=mainnet\&tab=token\&to=monad\&token=USD1)) Eth [PCS USD1/USDC](https://pancakeswap.finance/liquidity/pool/monad/0x8CcB070b6F871AbA552972c76D3b7DF8d88Ffa1a)
`thBILL` Theo Short Duration UST Fund [`0xfDD22Ce6D1F66bc0Ec89b20BF16CcB6670F55A5a`](https://monadvision.com/token/0xfDD22Ce6D1F66bc0Ec89b20BF16CcB6670F55A5a) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0x5FA487BCa6158c64046B2813623e20755091DA0b\&dstChain=monad\&dstToken=0xfDD22Ce6D1F66bc0Ec89b20BF16CcB6670F55A5a)) Eth, Arb, Base, +4 [Uni thBILL/USDC](https://app.uniswap.org/explore/pools/monad/0xeF6F8aa2d7018b3A3b33cf214DebD87e9638e043)
`wsrUSD` Wrapped srUSD [`0x4809010926aec940b550D34a46A52739f996D75D`](https://monadvision.com/token/0x4809010926aec940b550D34a46A52739f996D75D) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0xd3fD63209FA2D55B07A0f6db36C2f43900be3094\&dstChain=monad\&dstToken=0x4809010926aec940b550D34a46A52739f996D75D)) Eth
`yzUSD` Yuzu USD [`0x9dcB0D17eDDE04D27F387c89fECb78654C373858`](https://monadvision.com/token/0x9dcB0D17eDDE04D27F387c89fECb78654C373858) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0x387167e5C088468906Bcd67C06746409a8E44abA\&dstChain=monad\&dstToken=0x9dcB0D17eDDE04D27F387c89fECb78654C373858)) Eth, Plasma
`syzUSD` Staked Yuzu USD [`0x484be0540aD49f351eaa04eeB35dF0f937D4E73f`](https://monadvision.com/token/0x484be0540aD49f351eaa04eeB35dF0f937D4E73f) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0x6DFF69eb720986E98Bb3E8b26cb9E02Ec1a35D12\&dstChain=monad\&dstToken=0x484be0540aD49f351eaa04eeB35dF0f937D4E73f)) Eth, Plasma
### Eth-related
Symbol Name Address Bridge + Link Bridgeable from Select Markets
`WETH` Wrapped Ether [`0xEE8c0E9f1BFFb4Eb878d8f15f368A02a35481242`](https://monadvision.com/token/0xEE8c0E9f1BFFb4Eb878d8f15f368A02a35481242) NTT 2/2 ([MonadBridge](https://monadbridge.com/?fromChain=Ethereum\&fromToken=ETH\&toChain=Monad\&toToken=WETH)) many [Uni WETH/MON](https://app.uniswap.org/explore/pools/monad/0x3783b51e33900eb366a9e8473c76cda441e7170d2e5d96927f30c16a7add93aa)
[Uni USDC/WETH](https://app.uniswap.org/explore/pools/monad/0xad408916c1c310da9c258d4c128a7bf50fd9edc42a218cc970da39cfc8a05d93)
`ezETH` Renzo Restaked ETH [`0x2416092f143378750bb29b79eD961ab195CcEea5`](https://monadvision.com/token/0x2416092f143378750bb29b79eD961ab195CcEea5) Hyperlane ([Nexus](https://nexus.hyperlane.xyz/?token=ezETH\&origin=ethereum\&destination=monad)) many [Curve ezETH/WETH](https://www.curve.finance/dex/monad/pools/factory-stable-ng-15)
`rETH` Rocket Pool ETH [`0xC50f2e735eDd9dCD8Ccd41EcFE9894E679e3195f`](https://monadvision.com/token/0xC50f2e735eDd9dCD8Ccd41EcFE9894E679e3195f) Chainlink CCIP ([Transporter](https://app.transporter.io/?from=mainnet\&tab=token\&to=monad\&token=RETH)) Eth
`wstETH` Lido Wrapped Staked ETH [`0x10Aeaf63194db8d453d4D85a06E5eFE1dd0b5417`](https://monadvision.com/token/0x10Aeaf63194db8d453d4D85a06E5eFE1dd0b5417) Chainlink CCIP ([Transporter](https://app.transporter.io/?tab=token\&to=monad\&token=wstETH)) Eth, Ink, Plasma [Uni wstETH/WETH](https://app.uniswap.org/explore/pools/monad/0x55d7ed991392eb9597a76a5f41dfb964e291452c15107c0e64fd3d25925394ce)
`weETH` Wrapped EtherFi ETH [`0xA3D68b74bF0528fdD07263c60d6488749044914b`](https://monadvision.com/token/0xA3D68b74bF0528fdD07263c60d6488749044914b) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee\&dstChain=monad\&dstToken=0xA3D68b74bF0528fdD07263c60d6488749044914b)) Eth, Base [Uni weETH/WETH](https://app.uniswap.org/explore/pools/monad/0x2884b37c4a144e7047a1377ba7201d4b8ea318f0240369e01dc400f04e6cac40)
`pufETH` pufETH [`0x37D6382B6889cCeF8d6871A8b60E667115eDDBcF`](https://monadvision.com/token/0x37D6382B6889cCeF8d6871A8b60E667115eDDBcF) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0xD9A442856C234a39a81a089C06451EBAa4306a72\&dstChain=monad\&dstToken=0x37D6382B6889cCeF8d6871A8b60E667115eDDBcF)) Eth, Base, +3
### BTC-related
Symbol Name Address Bridge + Link Bridgeable from Select Markets
`cbBTC` Coinbase Wrapped BTC [`0xd18B7EC58Cdf4876f6AFebd3Ed1730e4Ce10414b`](https://monadvision.com/token/0xd18B7EC58Cdf4876f6AFebd3Ed1730e4Ce10414b) Chainlink CCIP ([Transporter](https://app.transporter.io/?from=base\&tab=token\&to=monad\&token=cbBTC)) Base [Curve cbBTC/WBTC/LBTC](https://www.curve.finance/dex/monad/pools/0xd2634e05ebed90bd0a6c0e93d48d7bd8036653b1)
[Uni USDC/cbBTC](https://app.uniswap.org/explore/pools/monad/0x7fc6232a9ec6cc4e9434640dcde5ee08ccae3b07de3247bf788fc9e2051b449e)
`WBTC` Wrapped Bitcoin [`0x0555E30da8f98308EdB960aa94C0Db47230d2B9c`](https://monadvision.com/token/0x0555E30da8f98308EdB960aa94C0Db47230d2B9c) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599\&dstChain=monad\&dstToken=0x0555E30da8f98308EdB960aa94C0Db47230d2B9c)) Eth, Base, +15 [Curve cbBTC/WBTC/LBTC](https://www.curve.finance/dex/monad/pools/0xd2634e05ebed90bd0a6c0e93d48d7bd8036653b1)
[Uni MON/WBTC](https://app.uniswap.org/explore/pools/monad/0x1c93dd2f2f47439330150bf728c3beeaad71de45420a49183214898b044b65d1)
[Uni WBTC/USDC](https://app.uniswap.org/explore/pools/monad/0xd77c0f253764f5d5fbc78e13888afcc35c839262e6b21cd02baa9d8551a9898a)
`LBTC` Lombard Staked Bitcoin [`0xecAc9C5F704e954931349Da37F60E39f515c11c1`](https://monadvision.com/token/0xecAc9C5F704e954931349Da37F60E39f515c11c1) Chainlink CCIP ([Transporter](https://app.transporter.io/?from=mainnet\&tab=token\&to=monad\&token=LBTC)) Eth, Avax, Base, BNB, Sonic [Curve cbBTC/WBTC/LBTC](https://www.curve.finance/dex/monad/pools/0xd2634e05ebed90bd0a6c0e93d48d7bd8036653b1)
[Curve Bitcoin Converter](https://www.curve.finance/dex/monad/pools/factory-stable-ng-8)
`BTC.b` BTC.b [`0xB0F70C0bD6FD87dbEb7C10dC692a2a6106817072`](https://monadvision.com/token/0xB0F70C0bD6FD87dbEb7C10dC692a2a6106817072) Chainlink CCIP ([Transporter](https://app.transporter.io/?from=mainnet\&tab=token\&to=monad\&token=BTC.b)) Eth, Avax [Curve Bitcoin Converter](https://www.curve.finance/dex/monad/pools/factory-stable-ng-8)
`SolvBTC` Solv BTC [`0xaE4EFbc7736f963982aACb17EFA37fCBAb924cB3`](https://monadvision.com/token/0xaE4EFbc7736f963982aACb17EFA37fCBAb924cB3) Chainlink CCIP ([Transporter](https://app.transporter.io/?from=mainnet\&tab=token\&to=monad\&token=SolvBTC)) Eth
`xSolvBTC` xSolvBTC [`0xc99F5c922DAE05B6e2ff83463ce705eF7C91F077`](https://monadvision.com/token/0xc99F5c922DAE05B6e2ff83463ce705eF7C91F077) Chainlink CCIP ([Transporter](https://app.transporter.io/?from=mainnet\&tab=token\&to=monad\&token=xSolvBTC)) Eth
### Others
Symbol Name Address Bridge + Link Bridgeable from Select Markets
`WSOL` Wrapped SOL [`0xea17E5a9efEBf1477dB45082d67010E2245217f1`](https://monadvision.com/token/0xea17E5a9efEBf1477dB45082d67010E2245217f1) Wormhole ([Portal](https://monadbridge.com/?fromChain=Solana\&fromToken=SOL\&toChain=Monad\&toToken=SOL)) many
`XAUt0` Tether Gold [`0x01bFF41798a0BcF287b996046Ca68b395DbC1071`](https://monadvision.com/token/0x01bFF41798a0BcF287b996046Ca68b395DbC1071) LayerZero OFT ([Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0x68749665FF8D2d112Fa859AA293F07A622782F38\&dstChain=monad\&dstToken=0x01bFF41798a0BcF287b996046Ca68b395DbC1071)) Eth, Arb, Sol, +10 [Uni XAUt0/AUSD](https://app.uniswap.org/explore/pools/monad/0xe1a8600687e4d06ca4787e5d0ccdacb1d360bfc9ca6ca2a49a688e14d0ef37b4)
## Natively-Issued Assets
Symbol Name Address
`WMON` Wrapped MON [`0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A`](https://monadvision.com/token/0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A)
`mEDGE` Midas mEDGE [`0x1c8eE940B654bFCeD403f2A44C1603d5be0F50Fa`](https://monadvision.com/token/0x1c8eE940B654bFCeD403f2A44C1603d5be0F50Fa)
## Market Data * [Defined](https://www.defined.fi/tokens/discover?network=mon) * [GeckoTerminal](https://www.geckoterminal.com/monad/pools) * [MonadVision Tokens](https://monadvision.com/tokens) * [DeFiLlama Monad](https://defillama.com/chain/monad) ## Bridge Integrations See [Cross-Chain](/tooling-and-infra/cross-chain) for a list of supported bridges and bridge aggregators. ## MON on other blockchains
Name Blockchain Address Notes
`WMON` Solana [`CrAr4RRJMBVwRsZtT62pEhfA9H5utymC2mVx8e7FreP2`](https://orb.helius.dev/address/CrAr4RRJMBVwRsZtT62pEhfA9H5utymC2mVx8e7FreP2)
`WMON` Ethereum [`0x6917037f8944201b2648198a89906edf863b9517`](https://etherscan.io/token/0x6917037f8944201b2648198a89906edf863b9517) 2/2 NTT
# Opcode Pricing Source: https://docs.monad.xyz/developer-essentials/opcode-pricing ## Summary Monad is a highly optimized system that introduces efficiencies across all dimensions - compute, state access, and bandwidth utilization. However, the multiplier relative to legacy EVM systems is not equal across all dimensions. As a result, some opcode gas price changes are needed so that applications can unlock the full potential of the chain. To minimize the number of gas price changes, rather than adjusting the gas pricing of almost all opcodes down, Monad instead adjusts a few opcode prices up. This has the same relative effect as discounting almost all opcodes. The following costs are changed: * [Cold access to state](#cold-access-cost) * [A few precompiles](#precompiles) All other costs are as on Ethereum; [evm.codes](https://www.evm.codes/) is a helpful reference. These changes are covered formally in the [Monad Initial Spec Proposal](https://category-labs.github.io/category-research/monad-initial-spec-proposal.pdf) ## Why are changes needed? The EVM's current pricing model needs adaptation to support a high-performance, low-fee regime. The pricing model assigns a weight (gas amount) to each opcode based on perceived costliness to the system, then charges the user only based on the calculated sum of weights. As resource scarcity changes - and especially in the event of a completely new system - those weightings must be revised. The changes described in this page make the minimal set of adjustments to allow Monad to deliver high performance and low fees, while minimizing disruption to users and protecting the system against DOS attacks. ## Cold access cost To account for the relatively higher cost of state reads from disk when compared to computation in the Monad execution client, the cost for "cold" account and storage access costs changes: | Access Type | Ethereum | Monad | | ----------- | -------- | ----- | | Account | 2600 | 10100 | | Storage | 2100 | 8100 | The following opcodes are impacted because of the differed gas costs: * Account access: `BALANCE`, `EXTCODESIZE`, `EXTCODECOPY`, `EXTCODEHASH`, `CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`, `SELFDESTRUCT` * Storage access: `SLOAD`, `SSTORE` Gas costs for warm account access (100 gas) and storage access (100 gas) are the same on Monad as on Ethereum. ## Precompiles A few [precompiles](/developer-essentials/precompiles) have been repriced to accurately reflect their relative costs in execution. | Precompile | Address | Ethereum | Monad | Multiplier | | ------------ | ------- | ------------------- | ------------------- | ---------- | | `ecRecover` | `0x01` | 3000 | 6000 | 2 | | `ecAdd` | `0x06` | 150\* | 300\* | 2 | | `ecMul` | `0x07` | 6000\* | 30,000\* | 5 | | `ecPairing` | `0x08` | 45,000\* | 225,000\* | 5 | | `blake2f` | `0x09` | $\text{rounds} * 1$ | $\text{rounds} * 2$ | 2 | | `point eval` | `0x0a` | 50,000 | 200,000 | 4 | ∗: Per input/operation, as defined in the respective precompile specification # Precompiles Source: https://docs.monad.xyz/developer-essentials/precompiles How to use Monad's precompiled contracts Precompiles are contracts at predefined addresses that provide cryptographic and utility functions implemented natively rather than in EVM bytecode. They are callable like any other contract via `CALL` or `STATICCALL`. Monad supports all Ethereum precompiles as of the Fusaka fork (`0x01` to `0x11`), plus two additional precompiles: * **`0x0100`** — [P256 signature verification](#p256-signature-verification-0x0100) ([EIP-7951](https://eips.ethereum.org/EIPS/eip-7951)) * **`0x1000`** — [Staking precompile](#staking-precompile-0x1000) ## Ethereum precompiles ### Cryptographic and hashing | Address | Name | Gas | Description | | ------- | ----------- | ----------------------- | ----------------------------- | | `0x01` | `ecRecover` | `6000` | ECDSA public key recovery | | `0x02` | `sha256` | `60 + 12 * word_size` | SHA-256 hash function | | `0x03` | `ripemd160` | `600 + 120 * word_size` | RIPEMD-160 hash function | | `0x09` | `blake2f` | `rounds * 2` | BLAKE2 compression function F | ### Arithmetic and utility | Address | Name | Gas | Description | | ------- | ---------- | -------------------------------------------------- | ------------------------------------------ | | `0x04` | `identity` | `15 + 3 * word_size` | Returns the input unchanged | | `0x05` | `modexp` | [see evm.codes](https://www.evm.codes/precompiled) | Arbitrary-precision modular exponentiation | ### Elliptic curve alt\_bn128 | Address | Name | Gas | Description | | ------- | ----------- | --------- | ------------------------------------ | | `0x06` | `ecAdd` | `300` | Point addition on alt\_bn128 | | `0x07` | `ecMul` | `30,000` | Scalar multiplication on alt\_bn128 | | `0x08` | `ecPairing` | `225,000` | Bilinear pairing check on alt\_bn128 | ### KZG commitment | Address | Name | Gas | Description | | ------- | ------------ | --------- | ------------------------------------------------------------------------------------------------------------- | | `0x0a` | `point_eval` | `200,000` | KZG commitment verification ([EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile)) | ### BLS12-381 These precompiles provide operations on the BLS12-381 curve per [EIP-2537](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md). | Address | Name | Gas | Description | | ------- | --------------------- | ----------------------------------------------------------------------------- | --------------------------------------- | | `0x0b` | `bls12_g1_add` | `375` | Point addition in G1 | | `0x0c` | `bls12_g1_msm` | [see EIP-2537](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md) | Multi-scalar multiplication in G1 | | `0x0d` | `bls12_g2_add` | `600` | Point addition in G2 | | `0x0e` | `bls12_g2_msm` | [see EIP-2537](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md) | Multi-scalar multiplication in G2 | | `0x0f` | `bls12_pairing_check` | [see EIP-2537](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md) | Pairing check over (G1, G2) point pairs | | `0x10` | `bls12_map_fp_to_g1` | `5500` | Map base field element to G1 point | | `0x11` | `bls12_map_fp2_to_g2` | `23800` | Map extension field element to G2 point | ## P256 signature verification Address `0x0100` verifies signatures on the `secp256r1` (P256) elliptic curve per [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951). EIP-7951 supersedes [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md). If you are migrating from a chain that uses RIP-7212 at `0x100`, note that the address and interface are identical — only the EIP designation has changed. The P256 curve is used by WebAuthn, Apple Secure Enclave, Android Keystore, and hardware security modules. With this precompile, you can verify passkey signatures on-chain, enabling biometric authentication flows for smart accounts without relying on off-chain signature verification. ### Input and output The precompile accepts exactly **160 bytes**: | Offset | Size | Parameter | Description | | ------ | -------- | --------- | ----------------------- | | 0 | 32 bytes | `hash` | Message hash | | 32 | 32 bytes | `r` | Signature component r | | 64 | 32 bytes | `s` | Signature component s | | 96 | 32 bytes | `qx` | Public key x-coordinate | | 128 | 32 bytes | `qy` | Public key y-coordinate | All values are big-endian encoded as 256-bit unsigned integers. **Returns:** * `0x0000...0001` (32 bytes) on a valid signature * Empty bytes on an invalid signature or malformed input **Gas cost:** 6900 ### Solidity example ```solidity theme={null} address constant P256_VERIFY = address(0x0100); function verifyP256Signature( bytes32 hash, uint256 r, uint256 s, uint256 qx, uint256 qy ) internal view returns (bool) { (bool success, bytes memory result) = P256_VERIFY.staticcall( abi.encodePacked(hash, r, s, qx, qy) ); return success && result.length == 32 && abi.decode(result, (uint256)) == 1; } ``` For passkey infrastructure and embedded wallet providers on Monad, see [Embedded Wallets](/tooling-and-infra/wallet-infra/embedded-wallets). ## Staking precompile Address `0x1000` provides the interface for validator delegation, undelegation, reward claims, and staking queries. Gas costs vary by function. * [Staking overview](/reference/staking/overview) — key concepts, common actions, and constraints * [Staking API reference](/reference/staking/api) — full method signatures, parameters, gas costs, events, and ABI ## Gas pricing differences A few precompiles (`0x01`, `0x06`, `0x07`, `0x08`, `0x09`, `0x0a`) are repriced relative to Ethereum to reflect their relative costs in Monad's execution environment. The gas values in the tables above reflect Monad's pricing. See [Opcode pricing](/developer-essentials/opcode-pricing#precompiles) for a comparison. ## Source code See the [precompile implementation](https://github.com/category-labs/monad/blob/main/category/execution/ethereum/precompiles.cpp). # Reserve Balance Source: https://docs.monad.xyz/developer-essentials/reserve-balance ## Introduction The **Reserve Balance** mechanism is a set of light constraints - at consensus time on which transactions can be **included**, and at execution time on which transactions **don't revert** - which allow Monad to simultaneously support [asynchronous execution](/monad-arch/consensus/asynchronous-execution) and [EIP-7702](/developer-essentials/eip-7702). The Reserve Balance mechanism is designed to preserve safety under asynchronous execution without interfering with normal usage patterns. **Most users and developers need not worry about the Reserve Balance constraints**, however we provide the details here for those encountering corner cases. ## Summary Asynchronous execution means that nodes achieve consensus on a block proposal prior to executing the transactions in that block. Execution is required to be completed in the next `k` (delay factor) blocks. (Currently `k=3`. *NOTE*: In the context of discussing [block states](/monad-arch/consensus/block-states) and [asynchronous execution](/monad-arch/consensus/asynchronous-execution), letter `D` is usually used to denote the same parameter.) Because consensus operates on a `k`-block delayed view of the global state, it is necessary to adjust the consensus and execution rules slightly to allow consensus to safely build and validate blocks that include only transactions whose gas costs can be paid for. Monad introduces the **Reserve Balance** mechanism to allow consensus and execution to collaborate across a multi-block lag to ensure that all EOAs must have enough MON in their account to pay for gas for any transaction included in the blockchain. Throughout this document, an **inflight transaction** refers to a transaction that has been included in a block less than `k` blocks ago. Here is a very brief summary of the rules: * From the perspective of a particular EOA, MON spent from that EOA in the course of a transaction is partitioned into two parts: **gas spend** and **value spend**. * In the case where the EOA was the sender: * **gas spend** is `gas_price * gas_limit`; * **value spend** is the `value` parameter on that transaction. * In the case where the EOA wasn't the sender (where they previously delegated via EIP-7702 and some other EOA submitted a transaction which called this EOA): * **gas spend** is 0; * **value spend** is whatever MON is sent out during the course of executing this EOA's code. * Let `user_reserve_balance = 10 MON` * *Execution time*: during execution, transactions revert due to **value spend** when that account's ending balance (before refunds) dips below `user_reserve_balance`, except in certain circumstances described below. * *Consensus time*: For each account, consensus has a budget for the **gas spend** for all inflight transactions; this budget is `user_reserve_balance` (or the account's balance from the lagged execution state, whichever is lower). The budget is further reduced if the first inflight transaction earned the exception mentioned above, by that transaction's **value spend**. When performing block validity checks for block `n`, consensus checks that the budget is not exceeded. See also the formal definition in the [Monad Initial Spec](https://category-labs.github.io/category-research/monad-initial-spec-proposal.pdf) proposal from Category Labs. ## Parameters | Parameter | Value | | ---------------------- | ------ | | `user_reserve_balance` | 10 MON | ## Why is reserve balance needed? Monad has asynchronous execution: consensus is allowed to progress with building and validating blocks without waiting for execution to catch up. Specifically, proposing and validating consensus block `n` only requires knowledge of the state obtained after applying block `n-k`. While asynchronous execution has performance benefits, it introduces a novel challenge: how is consensus supposed to know the validity of a block if it does not have the latest state? Let’s illustrate this challenge with an example (for our examples, we will use `k = 3`): Consensus is validating block 4, which contains a transaction `t` from Alice with the relevant fields as: ``` sender=Alice, to=Bob, value=100, gas=1 ``` Consensus only has the state that was obtained by executing block 1: ``` block=1, balances={Alice: 110} ``` If consensus simply accepts block 4 as valid because Alice appears to have enough balance, it risks a safety failure. For instance, Alice may have already spent her balance in transaction `t’` in block 2. This creates a denial-of-service (DoS) vector, as Alice could cause consensus to include many transactions for free. ## First attempt at a solution One idea is for the consensus client to statically inspect transactions in blocks 2 and later, checking if Alice has spent any value in her transactions. This would let consensus reject block 4 as invalid if any transaction before `t` (such as `t'`) in blocks 2, 3, or 4 originates from Alice and spends some value or gas. While this is a fine solution on the face of it, it suffers from two shortcomings: 1. Suppose, as part of smart contract execution in blocks 2 or 3, Alice received a lot of currency. She would have had enough balance to pay for transaction `t` despite `t'` existing, if only we had the latest state. So, rejecting transactions based solely on static checks is overly restrictive. 2. It is not only restrictive, it is also not safe with EIP-7702. With EIP-7702, Alice could have her account delegated to a smart contract, which can transfer out currency from Alice’s account in a way that is not statically inspectable by consensus. Concretely in our example, Alice does not need to send a transaction like `t'` from her account in order to spend currency from her account, if her account is delegated. A spend could potentially be triggered by a transaction submitted by anyone else. So our static check would not succeed and it may be unsafe to accept block 4 as valid even if we don’t see any other transaction from Alice in blocks 2, 3 and 4. ## Reserve balance as the solution ### Simple version Intuitively, the core idea of reserve balance is as follows: if consensus and execution agree ahead of time that, for each EOA, execution will prevent the ending balance from dropping below a certain pre-determined threshold known to consensus (`user_reserve_balance`), up to the sender's **gas spend** allowance, then consensus can safely include a series of transactions whose running **gas spend** stays below `user_reserve_balance`, without knowing the latest state and without being vulnerable to the DoS vector described above. This concept can be generalized as follows: * *Execution time*: after execution (before gas refunds), a reserve-balance check is applied to the ending balance. For a non-sender account, the ending balance must not be lower than `min(balance at transaction start, user_reserve_balance)`. For the sender, the ending balance may be lower by at most the transaction's **gas spend** (unless the emptying exception below applies). Excessive intermediate debits during execution are allowed as long as the ending balance is sufficient. * *Consensus time*: For each account, consensus has a budget for the **gas spend** for all inflight transactions; this budget is `user_reserve_balance` (or the account's balance from the lagged execution state, whichever is lower). When performing block validity checks for block `n`, consensus checks that the budget is not exceeded. In Monad, `user_reserve_balance` is currently set to `10 MON` for each EOA. The above rule is sufficient to ensure all transactions included in consensus can be paid for, thus solving our problem. However, it has a drawback, which is that EOAs can't spend all of their MON, and EOAs with balances below `user_reserve_balance` won't be able to send any successful transactions. For instance, the following behaviors might be desired, but are currently blocked by the above rule (with `user_reserve_balance` set to `10 MON`): * Alice has a balance of 5 MON and wants to send 4.99 MON to Bob (plus pay 0.01 MON in gas) * Alice has a balance of 20 MON and wants to swap 18 MON into a memecoin (plus pay 0.01 MON in gas) To address this, we add some additional conditions under which transactions are allowed. ### Addressing the drawback First let's define an "emptying transaction": A transaction is an **"emptying transaction"** iff * the sender is undelegated. * the sender has not sent any other transaction within the past `k` blocks. * nobody has sent a delegation or undelegation request for the sender within the past `k` blocks, including in this transaction. Such transactions are eligible for the emptying exception (see rules below). Notice that if a user account is **not** EIP-7702-delegated, then consensus can simply inspect transactions statically in order to estimate the lowest a user’s balance can possibly go. (This is because an undelegated user’s account can only be debited due to value transfers and gas fees specified in the transaction data). Therefore, we add the following exception to the reversion rule: 3. *Execution policy*: for each **undelegated** account sender: * *if* a transaction is an emptying transaction * *then* allow that transaction to proceed anyway (an "emptying transaction"). 4. *Consensus policy*: for each **undelegated** account sender: * *if* a transaction is an emptying transaction * *then* statically inspect that transaction's total MON needs (i.e. `gas_bid * gas_limit + value`), and take into account the fact that execution will still allow this transaction through. This means that for any subsequent transactions in the next `k` blocks, the reserve balance that consensus is working with will be lower by `value`. This rule lets execution allow consistently-undelegated accounts to dip below the reserve balance once every `k` blocks. Since `k` blocks is 1.2 seconds, this policy should allow most small accounts to still interact with the blockchain normally. Note that an account is not eligible for the exception criteria if there was recent (within `k` blocks) undelegation request for the account even if it was already undelegated at that time. See [Full specification](#full-specification) for details. The additional policy allows both of the examples mentioned at the end of the previous section, as long as they are the first transaction sent by the sender in `k` blocks. ## Discussion ### EIP-7702-delegated accounts If an EOA is not EIP-7702-delegated, then the reserve balance is rarely invasive, even if the EOA's balance is very low, because most transactions are the first transaction in `k=3` blocks sent by that EOA. But what if the EOA *is* EIP-7702-delegated? What is the impact? * The only transactions that will revert are ones in which the balance of an EIP-7702-delegated EOA **dips below** 10 MON. * *'Dips below'* means *'decrements **and** drops below'*. * Transactions where the EOA's balance ends above or at 10 MON are fine. * Transactions where the EOA's balance is unchanged or increases are fine. * **Only** transactions that decrement the balance **and** result in a final balance below 10 MON are reverted. * Delegated EOAs cannot use the emptying exception described above. For example, many sponsored gas workflows have users set up EIP-7702-delegated EOAs that don't interact with MON at all (i.e. they start out empty, and only receive MON involuntarily). Under a typical such workflow, a sponsor submits a transaction that calls into the EOA as smart contract. This workflow works fine in Monad; it's fine for the EOA in question to have a zero or very small balance. The only case where a sponsored gas workflow will be blocked is if the EOA's balance is (say) 6 MON, and the sponsored transaction calls code that tries to transfer (say) 1 MON out of the EOA. ### Transactions that are included but revert Because of the Reserve Balance rules, you may see transactions *included* in the chain whose execution *reverts*, such as transactions trying to transfer out more MON than are in the account balance. These transactions are still *valid* transactions that pay for gas, but the *result* of these transactions is nothing except for gas being decremented from the sender. They are included because at the time of consensus, the proposer cannot be sure that the account isn't going to receive more MON from someone else, and the sender has the budget to pay for gas. Ethereum includes many transactions whose execution reverts, so this is not a protocol difference. However, in practice, Ethereum block builders may screen out transactions with insufficient balance to process a transfer out, so this behavior may be different from what you're accustomed to seeing. ## Full specification See the [reserve balance spec](https://category-labs.github.io/category-research/monad-initial-spec-proposal.pdf) for the formal set of Reserve Balance rules. Algorithms 1 and 2 implement this check for consensus and execution, respectively. Algorithm 3 implements the mechanism to detect the dipping into the reserve balance (Algorithm 2 uses Algorithm 3 to revert transactions that dip). Algorithm 4 specifies the criteria for emptying transactions: * The sender account must be undelegated in the prior `k` blocks. This is checked statically by verifying the account was undelegated in a known state in the past `k` blocks, and there have been no delegation or undelegation requests in the last `k` blocks (this can be inspected statically). * There must not be another transaction from the same sender in the prior k blocks. Here is a quick summary of the reserve balance rules at consensus time: #### If the account is not delegated and there are no inflight transactions If the account is not delegated, and there are no previous inflight transactions, then consensus checks that the gas fee for this transaction is less than the balance from the lagged state. $$ \text{gas\_fees}(\text{tx}) \leq \text{balance} $$ #### If the account is not delegated and has one emptying inflight transaction If the account is not delegated, and there is one previous inflight transaction, then consensus has to take into account the inflight transaction's total MON expenditures (including `value`): $$ \text{let adjusted\_balance} = \text{balance} - (\text{first\_tx.value} + \text{gas\_fees}(\text{first\_tx})) $$ $$ \text{let } \text{reserve} = \min(\text{user\_reserve\_balance}(t.\text{sender}), \text{adjusted\_balance}) \quad $$ A new transaction can only be included if the sum of all inflight transactions' gas fees (excluding the first one) is less than the reserve: $$ \sum_{tx \in I[1:]} \text{gas\_fees}(tx) \leq \text{reserve} $$ #### All other cases The reserve is equal to minimum of systemwide reserve balance (`10 MON`) or the account's balance at block `n - k`: $$ \text{reserve} = \min(\text{user\_reserve\_balance}(t.\text{sender}), \text{balance}) $$ A new transaction can only be included if the sum of all inflight transactions' gas fees is less than the reserve: $$ \sum_{tx \in I} \text{gas\_fees}(tx) \leq \text{reserve} $$ ## Adjusting the reserve balance The reserve balance is currently the same for every account (`10 MON`). In a future version, the protocol could allow users, through a stateful precompile, to customize their reserve balance. ## Coq proofs The safety of the reserve balance specification has been formally proved in Coq. The full proofs documentation is available [here](https://category-labs.github.io/category-research/reserve-balance-coq-proofs). The consensus check is formalized in Coq as `consensusAcceptableTxs`. The predicate, `consensusAcceptableTxs s ltx`, defines the criteria for the consensus module to accept the list of transactions `ltx` on top of state `s`. The proof shows that `consensusAcceptableTxs s ltx` implies that when the execution module executes all the transactions in ltx one by one on top of s, none of them will fail due to having insufficient balance to cover gas fees. The proof is by induction on the list `ltx`: one can think of this as doing natural induction on the length of `ltx`. The proof in the inductive step involves unfolding the definitions of the consensus and execution checks and considering all the cases. In each case, the estimates of effective reserve balance in consensus checks is shown to be conservative with respect to what happens in execution. ## Additional examples To test your understanding, here are some examples along with the expected outcome. Each example is independent. In the following examples, we use `start_block = 2`, meaning the initial balances and reserves are after block 1. We also specify the reserve balance parameter for each example, although it is a constant system wide parameter. For each transaction, the expected result is indicated by a code: * **2**: Successfully executed * **1**: Included but reverted during execution (due to reserve balance dip) * **0**: Excluded by consensus ### Example 1: Basic transaction inclusion Initial state: ``` Alice: balance = 100, reserve = 10 Bob: balance = 5, reserve = 10 ``` Transactions: ``` Block 2: [ Alice: send 1 MON, fee 0.05 — Expected: 2 Bob: send 2 MON, fee 0.05 — Expected: 2 ] ``` Final balances: ``` Alice: 98.95 Bob: 2.95 ``` ### Example 2: Low reserve balance but high balance Initial state: ``` Alice: balance = 100, reserve = 1 ``` Transactions: ``` Block 2: [ Alice: send 3 MON, fee 2 — Expected: 2 (emptying transaction) Alice: send 3 MON, fee 2 — Expected: 0 (excluded) ] ``` Final balance: ``` Alice: 95.0 ``` ### Example 3: Multi-block, low reserve but high balance Initial state: ``` Alice: balance = 100, reserve = 1 ``` Transactions: ``` Block 2: [ Alice: send 3 MON, fee 2 — Expected: 2 ] Block 5: [ Alice: send 3 MON, fee 2 — Expected: 2 ] ``` Final balance: ``` Alice: 90.0 ``` ### Example 4: Comprehensive Initial state: ``` Alice: balance = 100, reserve = 1 ``` Transactions: ``` Block 2: [ Alice: send 99 MON, fee 0.1 — Expected: 2 (large emptying transaction) ] Block 3: [ Alice: send 0.5 MON, fee 0.99 — Expected: 0 (excluded) ] Block 4: [ Alice: send 0.8 MON, fee 0.1 — Expected: 1 (included but reverted) ] Block 5: [ Alice: send 0 MON, fee 0.9 — Expected: 0 (excluded) Alice: send 5 MON, fee 0.1 — Expected: 1 (included but reverted) Alice: send 5 MON, fee 0.8 — Expected: 0 (excluded) ] ``` Final balance: ``` Alice: 0.70 ``` ### Example 5: Edge case — zero value transactions Initial state: ``` Alice: balance = 2, reserve = 1 ``` Transactions: ``` Block 2: [ Alice: send 0 MON, fee 0.5 — Expected: 2 Alice: send 0 MON, fee 0.6 — Expected: 2 Alice: send 0 MON, fee 0.5 — Expected: 0 (exceeds reserve) ] ``` Final balance: ``` Alice: 0.9 ``` ### Example 6: Reserve balance boundary Initial state: ``` Alice: balance = 10, reserve = 2 ``` Transactions: ``` Block 2: [ Alice: send 1 MON, fee 2 — Expected: 2 (matches reserve) Alice: send 0 MON, fee 0.01 — Expected: 2 ] ``` Final balance: ``` Alice: 6.99 ``` ### Example 7: Account delegated in the interim Initial state: ``` Alice: balance = 15, reserve = 10 ``` Transactions: ``` Block 1: [] Block 2: [ Alice is delegated Bob on behalf of Alice: send 5 MON, fee 0 — Expected: 2 (executed) Alice is undelegated ] Block 3: [ Alice: send 3 MON, fee 0.1 — Expected: 1 (included but reverted) ] Block 4: [] Block 5: [ Alice: send 0 MON, fee 8 - Expected: 2 (executed) ] ``` Final balance: ``` Alice: 1.9 ``` **NOTE**: If execution would not have reverted transaction in `Block 3` (by checking delegation status in prior `k` blocks), consensus would include transaction in `Block 5` which would later run out of MON for the fee. Also, consider `Block 2` is empty instead. Transaction in `Block 3` is then an emptying transaction, which proceeds with execution, but transaction in `Block 5` is excluded as not having enough reserve for fee. ### Example 8: Account receives MON before second inflight tx Initial state: ``` Alice: balance = 15, reserve = 10 ``` Transactions: ``` Block 1: [] Block 2: [ Alice: send 6 MON, fee 0.1 - Expected: 2 (executed, emptying tx) ] Block 3: [ Alice receives 6 MON from a smart contract Alice: send 7 MON, fee 0.1 — Expected: 1 (included but reverted, cannot be 2nd emptying so soon) ] Block 4: [] Block 5: [ Alice: send 0 MON, fee 8 - Expected: 2 (executed) ] ``` Final balance: ``` Alice: 6.8 ``` **NOTE**: If execution would not have reverted the 2nd transaction in `Block 3` (from Alice; by checking existence of emptying transactions in prior `k` blocks), consensus would include transaction in `Block 5` which would later run out of MON for the fee. Also, consider `Block 2` is empty instead. Transaction in `Block 3` (from Alice) is then an emptying transaction, which proceeds with execution, but transaction in `Block 5` is excluded as one potentially not having enough reserve for fee - consensus doesn't see Alice being credited in `Block 3`. # Deployment Summary for Developers Source: https://docs.monad.xyz/developer-essentials/summary Deployment Summary for Developers This page summarizes what you need to know when developing or deploying smart contracts for Monad. ## Start with * [Network Information](/developer-essentials/network-information) - for RPC & block explorer URLs, and canonical contract deployments. * [Differences between Monad and Ethereum](/developer-essentials/differences) * [`protocols` repo](https://github.com/monad-crypto/protocols) (add yours!) * [`token-list` repo](https://github.com/monad-crypto/token-list) (add yours!) * [RPC API](/reference/json-rpc/) - an interactive reference ## Supported Tooling & Infra Here are some of the most commonly-requested tools: | Tool | Status | Notes | | ------------------------------------------------------- | ------ | ----------------------------------------------------------------------- | | [Tenderly](https://dashboard.tenderly.co/explorer) | ✅ | | | [Safe](https://app.safe.global) | ✅ | | | [Monadscan](https://monadscan.com) (by Etherscan) | ✅ | | | [MonadVision](https://monadvision.com) (by Blockvision) | ✅ | | | [Foundry](https://getfoundry.sh) | ✅ | Use the [Monad Foundry](/tooling-and-infra/toolkits/monad-foundry) fork | | [Viem](https://github.com/wevm/viem) | ✅ | `viem >= 2.40.0` |
See [Tooling and Infra](/tooling-and-infra) for a complete list of what is supported, including: | Category | | ---------------------------------------------------------------------------------------------------------- | | [AA Infra](/tooling-and-infra/wallet-infra/account-abstraction) | | [Analytics](/tooling-and-infra/analytics) | | [Block Explorers](/tooling-and-infra/block-explorers) | | [Cross-Chain](/tooling-and-infra/cross-chain) | | [Custody](/tooling-and-infra/custody) | | [Embedded Wallets](/tooling-and-infra/wallet-infra/embedded-wallets) | | [Indexers - Common Data](/tooling-and-infra/indexers/common-data) (e.g. token balances, transfers, trades) | | [Indexing Frameworks (incl Subgraphs)](/tooling-and-infra/indexers/indexing-frameworks) | | [Onramps](/tooling-and-infra/onramps) | | [Oracles](/tooling-and-infra/oracles) | | [RPC Providers](/tooling-and-infra/rpc-providers) | ## Accounts
Address space Same address space as Ethereum (last 20 bytes of ECDSA public key)
EIP-7702 Supported. See [EIP-7702 reference](/developer-essentials/eip-7702)
## Smart Contracts For deployment and verification guides, see: * [Deploy a Contract](/guides/deploy-smart-contract) * [Verify a Contract](/guides/verify-smart-contract) * [MonadVision verification guide](https://docs.blockvision.org/reference/verify-smart-contract-on-monad-explorer)
Opcodes All [opcodes](https://www.evm.codes/) as of the Fusaka fork are supported.
Precompiles All Ethereum precompiles as of the Fusaka fork (`0x01` to `0x11`), plus P256 signature verification at `0x0100` ([EIP-7951](https://eips.ethereum.org/EIPS/eip-7951)) and the staking precompile at `0x1000`. See [Precompiles](/developer-essentials/precompiles)
Max contract size 128 kb (up from 24.5 kb in Ethereum)
## Transaction types Full article: [Transactions](/developer-essentials/transactions)
Transaction types Supported:
  • 0 ("legacy")
  • 1 ("EIP-2930")
  • 2 ("EIP-1559", the "default" on Ethereum)
  • 4 (["EIP-7702"](/developer-essentials/eip-7702))
Not supported:
  • 3 ("EIP-4844")
## Gas limits
Per-transaction gas limit 30M gas
Block gas limit 200M gas
Block gas target 80% (160M gas)
Gas throughput 500M gas/sec (200M gas/block divided by 0.4 sec/block)
## Gas pricing Full article: [Gas pricing](/developer-essentials/gas-pricing)
Gas charged The **gas limit** is what is charged. That is: total tokens deducted from the sender's balance is `value + gas_price * gas_limit`. See [discussion](/developer-essentials/gas-pricing#gas-limit-not-gas-used).
EIP-1559 dynamics Monad is EIP-1559-compatible; base fee and priority fee work as on Ethereum. [EIP-1559 explainer](/developer-essentials/gas-pricing#eip-1559-compatibility)
Base fee Min base fee of 100 MON-gwei (100 \* 10^-9 MON).
The base fee controller is similar to Ethereum's but stays elevated for less time ([details](/developer-essentials/gas-pricing#base_price_per_gas-controller)).
## Opcode pricing Full article: [Opcode Pricing](/developer-essentials/opcode-pricing) Opcode pricing is the same as on Ethereum (see: [evm.codes](https://www.evm.codes/)), **except** for the below repricings needed to reweight relative scarcities of resources due to Monad optimizations.
Item Ethereum Monad Notes
Cold access cost - account 2,600 10,100 Affected opcodes: `BALANCE`, `EXTCODESIZE`, `EXTCODECOPY`, `EXTCODEHASH`, `CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`, `SELFDESTRUCT`
See [details](/developer-essentials/opcode-pricing#cold-access-cost)
Cold access cost - storage 2,100 8,100 Affected opcodes: `SLOAD`, `SSTORE`. See [details](/developer-essentials/opcode-pricing#cold-access-cost)
`ecRecover`, `ecAdd`, `ecMul`, `ecPairing`, `blake2f`, `point_eval` precompiles See [details](/developer-essentials/opcode-pricing#precompiles)

Precompiles `0x01`, `0x06`, `0x07`, `0x08`, `0x09`, `0x0a`
## Timing considerations
Block frequency 400 ms
`TIMESTAMP` opcode As in Ethereum, `TIMESTAMP` is a second-granularity unix timestamp. Since blocks are every 400 ms, this means that 2-3 blocks will likely have the same timestamp.
Finality Blocks are finalized after two blocks (800 ms). Once a block is finalized, it cannot be reorged. See [MonadBFT](/monad-arch/consensus/monad-bft) for a fuller discussion.
Speculative finality Blocks can be [speculatively finalized](/monad-arch/consensus/monad-bft#speculative-finality) after one block (400 ms), when it is marked as being in the `Voted` stage.

Speculative finality can revert under very rare circumstances (see fuller discussion [here](/monad-arch/consensus/monad-bft#speculative-finality)).
## Mempool Full article: [Local Mempool](/monad-arch/consensus/local-mempool) Monad does not have a global mempool, as this approach is not suitable for high-performance blockchains. Each validator maintains a local mempool with transactions that it is aware of. When an RPC receives a transaction, it forwards it strategically to upcoming leaders, repeating this process if it doesn't observe the transaction getting included. Although this is an important part of Monad's design, it is not one that should generally affect smart contract developers in their system designs. ## Parallel Execution and JIT Compilation Monad utilizes [parallel execution](/monad-arch/execution/parallel-execution) and [JIT compilation](/monad-arch/execution/native-compilation) for efficiency, but smart contract developers don't need to change anything to account for this. In Monad, transactions are still linearly ordered, and the only correct outcome of execution is the result as if the transactions were serially executed. All aspects of parallel execution can be treated by smart contract developers as implementation details. See [further discussion](/monad-arch/execution/parallel-execution). ## Asynchronous Execution Full article: [Asynchronous Execution](/monad-arch/consensus/asynchronous-execution) Monad utilizes asynchronous execution for efficiency, but most developers shouldn't need to change anything. Developers with significant off-chain financial logic (e.g. exchanges, bridges, and stablecoin/RWA issuers) should wait until blocks reach the [`Verified`](/monad-arch/consensus/block-states#verified) phase (aka state root finality), three blocks later than [`Finalized`](/monad-arch/consensus/block-states#finalized), to be sure that the entire network agrees with their own node's local execution of a finalized block. Asynchronous execution is a technique that allows Monad to substantially increase execution throughput by decoupling consensus from execution. In asynchronous execution, validators **vote first, execute later** - because once the transaction order is determined, the state is determined. Afterward, each node executes locally. There is a [delayed merkle root](/monad-arch/consensus/asynchronous-execution#delayed-merkle-root) three blocks later which confirms that the network got the same state trie as local execution. From the developer perspective: * Someone submits a transaction through your frontend which interacts with your smart contract. You make note of the hash. * A block gets [`Proposed`](/monad-arch/consensus/block-states#proposed) with the transaction included. * The block gets [`Voted`](/monad-arch/consensus/block-states#voted) (speculatively finalized) one block later. (`T+1`) * The block gets [`Finalized`](/monad-arch/consensus/block-states#finalized) one block later (`T+2`) * The block gets [`Verified`](/monad-arch/consensus/block-states#verified) (state root finalized) three blocks later (`T+5`) You listen for transaction receipts by calling [`eth_getTransactionReceipt`](/reference/json-rpc/eth_getTransactionReceipt). Receipts will first be available after a block becomes `Proposed` (speculatively executed). Your choice of when to update your UI to give feedback to the user depends on risk preference, but for most applications it is reasonable to do so when the block becomes `Proposed` because speculative finality reversion is extremely rare. A more conservative approach would be to wait until the block is `Finalized`, since then you will never have to handle a reorg. Waiting until `Verified` is not generally necessary (except for the aforementioned developers with off-chain financial logic). ## Reserve balance Full article: [Reserve Balance](/developer-essentials/reserve-balance) Monad introduces the Reserve Balance mechanism to enable Asynchronous Execution. The Reserve Balance mechanism places light restrictions on when transactions can be included at consensus time, and imposes some conditions under which transactions will revert at execution time. The Reserve Balance mechanism is designed to preserve safety under asynchronous execution without interfering with normal usage patterns. Most users and developers need not worry about the Reserve Balance constraints. | Parameter | Value | | ----------------------- | ------ | | Default reserve balance | 10 MON | ## EIP-7702 EIP-7702 is supported; see the full notes [here](/developer-essentials/eip-7702). There are two caveats: 1. If an EOA is EIP-7702-delegated, its balance cannot be lowered below 10 MON due to the [Reserve Balance](/developer-essentials/reserve-balance) rules. (If the delegation is removed, dipping below 10 MON is allowed.) [Discussion](/developer-essentials/eip-7702#delegated-eoas-cant-dip-below-10-mon). 2. If an EOA is EIP-7702-delegated, when it is called as a smart contract, the `CREATE` and `CREATE2` opcodes are banned. [Discussion](/developer-essentials/eip-7702#delegated-contract-code-cannot-call-createcreate2). ## Reading blockchain data The following methods are supported for reading blockchain data:
JSON-RPC See [RPC API](/reference/json-rpc). Monad supports all standard RPC methods from Ethereum. Differences are noted in [RPC Differences](/reference/rpc-differences). For rate limits, see [here](/reference/rpc-limits).
WebSocket See the [WebSocket Guide](/reference/websockets).

Monad implements the `eth_subscribe` method with the following subscription types:
  • `newHeads` and `logs`
  • `monadNewHeads` and `monadLogs` (similar, but include additional data for tracking each block's progression through consensus)
The `syncing` and `newPendingTransactions` subscription types are not supported. For more details see [Real-time Data Sources](/monad-arch/realtime-data/data-sources).
Execution Events See [Execution Events](/execution-events).

The Execution Events system allows developers to build high-performance applications that receive lowest-latency event data from a Monad node via shared memory queue.
You can also use the supported [Indexers](/tooling-and-infra/indexers). ## Historic data Monad full nodes provide access to all historic ledger data (blocks, transactions, receipts, events, and traces). Monad full nodes do not provide access to arbitrary historic state as discussed [here](/developer-essentials/historical-data). There is a special RPC service at [`https://rpc-mainnet.monadinfra.com`](https://rpc-mainnet.monadinfra.com) that provides access to historical data. ## Recommended Open Source Tooling Versions * `foundry`: use the [Monad Foundry](/tooling-and-infra/toolkits/monad-foundry) fork, which incorporates Monad gas pricing and precompiles * `viem >= 2.40.0` (just to bring in [monad.ts](https://github.com/wevm/viem/blob/main/src/chains/definitions/monad.ts)) * `alloy-chains >= 0.2.20` ## Canonical contract addresses See [Canonical Contracts](/developer-essentials/network-information#canonical-contracts-on-testnet) ## Source code * [`monad-bft`](https://github.com/category-labs/monad-bft) (consensus) * [`monad`](https://github.com/category-labs/monad) (execution) ## Running a full node See [Node Operations](/node-ops) ## Need Help? Please ask in the [developer discord](https://discord.gg/monaddev). We are here to help! # Network Information - Testnets Source: https://docs.monad.xyz/developer-essentials/testnets ## Summary | Network | Purpose | | ------------------- | ----------------------------------------------------------------------- | | [testnet](#testnet) | Primary testnet environment with hundreds of apps deployed | | [tempnet](#tempnet) | Transient network subject to resets; used as a sandbox for new features | ## testnet `testnet` was [reset from genesis on 2025-12-16](/developer-essentials/changelog/testnet#v0125-2025-12-16). Canonical contracts listed below will be redeployed.
Name Value
Chain ID `10143`
Network Name `Monad Testnet`
Currency Symbol `MON`
RPC URL [see below](#public-rpc-endpoints)
Block Explorer (MonadVision) [https://testnet.monadvision.com](https://testnet.monadvision.com)
Block Explorer (Monadscan) [https://testnet.monadscan.com](https://testnet.monadscan.com/)
Block Explorer (Socialscan) [https://monad-testnet.socialscan.io](https://monad-testnet.socialscan.io/)
Network visualization [https://www.gmonads.com/?network=testnet](https://www.gmonads.com/?network=testnet)
App hub [https://testnet.monad.xyz/](https://testnet.monad.xyz/)
Faucet [https://faucet.monad.xyz](https://faucet.monad.xyz)
Current version / revision [`v0.13.1`](/developer-essentials/changelog/testnet#v0131-2026-03-16) / [`MONAD_NINE`](/developer-essentials/changelog#revisions)
Changelog [(link)](/developer-essentials/changelog/testnet)
### Public RPC Endpoints Websocket endpoints start with `wss://`. See [Websocket Reference](/reference/websockets) for further information.
RPC URL Provider Rate Limits Batch Requests Archive Support Notes
`https://testnet-rpc.monad.xyz`
`wss://testnet-rpc.monad.xyz`
QuickNode 50 rps 100 25 rps for `eth_call` and `eth_estimateGas`
`https://rpc.ankr.com/monad_testnet` Ankr 300 reqs / 10s

12000 reqs / 10 min
100 `debug_*` methods are not allowed
`https://rpc-testnet.monadinfra.com`
`wss://rpc-testnet.monadinfra.com`
Monad Foundation 20 rps not allowed
See [RPC Limits](/reference/rpc-limits) for additional detail on method-specific limits. ### Canonical Contracts
Name Address
Wrapped MON [`0xFb8bf4c1CC7a94c73D209a149eA2AbEa852BC541`](https://testnet.monadvision.com/address/0xFb8bf4c1CC7a94c73D209a149eA2AbEa852BC541)
CreateX [`0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed`](https://testnet.monadvision.com/address/0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed)
Foundry Deterministic Deployer [`0x4e59b44847b379578588920ca78fbf26c0b4956c`](https://testnet.monadvision.com/address/0x4e59b44847b379578588920cA78FbF26c0B4956C)
[ERC-6492 UniversalSigValidator](https://eips.ethereum.org/EIPS/eip-6492) [`0xdAcD51A54883eb67D95FAEb2BBfdC4a9a6BD2a3B`](https://testnet.monadvision.com/address/0xdAcD51A54883eb67D95FAEb2BBfdC4a9a6BD2a3B)
EntryPoint v0.6 [`0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`](https://testnet.monadvision.com/address/0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789)
EntryPoint v0.7 [`0x0000000071727De22E5E9d8BAf0edAc6f37da032`](https://testnet.monadvision.com/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032)
Multicall3 [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://testnet.monadvision.com/address/0xcA11bde05977b3631167028862bE2a173976CA11)
Permit2 [`0x000000000022d473030f116ddee9f6b43ac78ba3`](https://testnet.monadvision.com/address/0x000000000022d473030f116ddee9f6b43ac78ba3)
SafeSingletonFactory [`0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7`](https://testnet.monadvision.com/address/0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7)
#### Safe v1.4.1 Contracts
Name Address
Safe [`0x41675C099F32341bf84BFc5382aF534df5C7461a`](https://testnet.monadvision.com/address/0x41675C099F32341bf84BFc5382aF534df5C7461a)
SafeL2 [`0x29fcB43b46531BcA003ddC8FCB67FFE91900C762`](https://testnet.monadvision.com/address/0x29fcB43b46531BcA003ddC8FCB67FFE91900C762)
SafeProxyFactory [`0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67`](https://testnet.monadvision.com/address/0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67)
MultiSend [`0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526`](https://testnet.monadvision.com/address/0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526)
MultiSendCallOnly [`0x9641d764fc13c8B624c04430C7356C1C7C8102e2`](https://testnet.monadvision.com/address/0x9641d764fc13c8B624c04430C7356C1C7C8102e2)
CompatibilityFallbackHandler [`0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99`](https://testnet.monadvision.com/address/0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99)
SignMessageLib [`0xd53cd0aB83D845Ac265BE939c57F53AD838012c9`](https://testnet.monadvision.com/address/0xd53cd0aB83D845Ac265BE939c57F53AD838012c9)
CreateCall [`0x9b35Af71d77eaf8d7e40252370304687390A1A52`](https://testnet.monadvision.com/address/0x9b35Af71d77eaf8d7e40252370304687390A1A52)
SimulateTxAccessor [`0x3d4BA2E0884aa488718476ca2FB8Efc291A46199`](https://testnet.monadvision.com/address/0x3d4BA2E0884aa488718476ca2FB8Efc291A46199)
### Testnet Tokens (partial list) See [tokenlist-testnet.json](https://github.com/monad-crypto/token-list/blob/main/tokenlist-testnet.json). ## tempnet `tempnet` runs the `devnet` ChainConfig with version `v0.12.3`.
Name Value
Purpose Transient network; sandbox for new features. Currently a sandbox for the [Opcode pricing changes](/developer-essentials/opcode-pricing)
Chain ID `20143`
RPC URL Please submit [this form](https://tally.so/r/wLlvlj). You will need to join the [Monad Developer Discord](https://discord.gg/monaddev)
Block Explorer n/a
Faucet Please submit [this form](https://tally.so/r/wLlvlj). You will need to join the [Monad Developer Discord](https://discord.gg/monaddev)
Current version / revision [`v0.12.3`](/developer-essentials/changelog/testnet#v0123-2025-12-04) / [`MONAD_EIGHT`](/developer-essentials/changelog#revisions)
# Transactions Source: https://docs.monad.xyz/developer-essentials/transactions ## Summary * Same address space and transaction format/fields as Ethereum, so the same wallet software is supported. * Transaction types 0 ("legacy"), 1 ("EIP-2930"), 2 ("EIP-1559"), and 4 ("EIP-7702") are currently supported. * Pre-[EIP-155](https://eips.ethereum.org/EIPS/eip-155) transactions are allowed on the protocol level, as in Ethereum and many other EVM-compatible blockchains. As a result, users are discouraged from using an Ethereum address that had previously sent pre-EIP-155 transactions. ## Address space Same address space as Ethereum (last 20 bytes of ECDSA public key) ## Transaction format [Same as Ethereum](https://ethereum.org/en/developers/docs/transactions/). Monad transactions use the same typed transaction envelope introduced in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718), encoded with [RLP](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/). ## Transaction types These [transaction types](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope) are supported: * Type 0 ("legacy") * Type 1 (["EIP-2930"](https://eips.ethereum.org/EIPS/eip-2930)) * Type 2 (["EIP-1559"](https://eips.ethereum.org/EIPS/eip-1559); the default in Ethereum) * Type 4 (["EIP-7702"](https://eips.ethereum.org/EIPS/eip-7702)) (see [EIP-7702 on Monad](/developer-essentials/eip-7702)) These types are not supported: * Type 3 ("EIP-4844") ## Access lists Access lists ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)) are supported but not required. ## Transactions without a chain\_id [EIP-155](https://eips.ethereum.org/EIPS/eip-155) introduced a transaction standard that includes a chain id, to prevent transactions from one blockchain from being replayed on another one. Transactions on Monad should always set the chain id, except for one very specific corner case: **The corner case:** Some standard smart contracts such as ERC-1820 use a keyless deployment method (also known as Nick's method) that exploits replayability, as discussed [here](https://eips.ethereum.org/EIPS/eip-1820#deployment-method). In this method, a transaction is submitted on Ethereum but is intended to be replayed on other chains in order to have the contract deployed at the same address on other blockchains. In order to support this use case, pre-EIP-155 transactions are still allowed on the protocol level (i.e. according to consensus rules) on Monad. This makes Monad consistent with most blockchains including Ethereum. (Blockchains that have tried disallowing pre-EIP-155 transactions at the protocol level have typically ended up reversing course, e.g. [Celo](https://github.com/celo-org/celo-blockchain/issues/1734).) However, because of this, please heed the following warning: Because replay of pre-EIP-155 transactions is allowed, it is discouraged to send funds to an Ethereum address that had previously sent pre-EIP-155 transactions. # Advanced topics Source: https://docs.monad.xyz/execution-events/advanced ## When are events published? Execution events are recorded roughly "as they are happening" inside the execution daemon: you see a `BLOCK_START` event at roughly the same moment that the execution daemon beings processing a new block, followed by the start of the first transaction (a `TXN_HEADER_START` event) about 1 millisecond later. Most transaction-related events are recorded less than one microsecond after the transaction they describe has completed. Execution of a typical transaction will emit a few dozen events, but large transactions can emit hundreds of events. The `TXN_EVM_OUTPUT` event -- which is recorded as soon as the transaction is finished -- provides a summary accounting of how many more events related to that transaction will follow (how many logs, how many call frames, etc.), so that memory to store the subsequent event data can be preallocated. For example in Rust, [`Vec::reserve`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve) is often called here. An event like `TXN_EVM_OUTPUT` is referred as a "header event" in the documentation: it is an event whose content describes some summary information and the number of subsequent, related events that will be recorded later with more details. All these events are recorded as soon as the transaction is "committed" to the currently-executing block. This happens before the block has finished executing, and should not be confused with the unrelated notion of "commitment" in the consensus algorithm. Although there are complex speculative execution optimizations inside the execution daemon, the recording of a transaction takes place when all work on a particular transaction has finished. This is referred to as "transaction commit" time. This is a different than the block-at-a-time style update you would see in, for example, the Geth real-time events WebSocket protocol (which our RPC server also [supports](/reference/websockets)). Certain properties of the block (its hash, its state root, etc.) are not known at the time you see a transaction's events, because the rest of the block is still executing. If you would like block-at-a-time updates, the Rust SDK contains [some utilities](/execution-events/rust-api#block-level-utilities) which will aggregate the events back into complete, block-oriented updates. One thing to be careful of: although transactions are always committed to a block in index order, they might be recorded out of order. That is, you must assume that the set of execution events that make up transactions 2 and 3 could be "mixed together" in any order. This is because of optimizations in the event recording code path. However, *for a particular transaction* (e.g., transaction 3) events pertaining to that transaction are always recorded in the same order: first all of the logs, then all the call frames, then all the state access records. Each of these is recorded in *index order*, i.e., log 2 is always recorded before log 3. Consider the following diagram: ``` ╔═Events═════════════════════════════╗ ║ ║ ║ ┌───────────────────────────────┐ ║ ║ │ event type: TXN_EVM_OUTPUT │ ║ ║ │ transaction: 1 │ ║ ║ │ log count: 2 │ ║ ║ └───────────────────────────────┘ ║ ║ ║ ║ ┌───────────────────────────────┐ ║ ║ │ event type: TXN_LOG │ ║ ║ │ transaction: 1 │ ║ ║ │ log index: 0 │ ║ ║ │ │ ║ ║ └───────────────────────────────┘ ║ ║ ║ ║ ┌───────────────────────────────┐ ║ ║ │ event type: TXN_EVM_OUTPUT │ ║ ║ │ transaction: 0 │ ║ ║ │ log count: 3 │ ║ ║ └───────────────────────────────┘ ║ ║ ║ ║ ┌───────────────────────────────┐ ║ ║ │ event type: TXN_LOG │ ║ ║ │ transaction: 0 │ ║ ║ │ log index: 0 │ ║ ║ │ │ ║ ║ └───────────────────────────────┘ ║ ║ ║ ║ ┌───────────────────────────────┐ ║ ║ │ event type: TXN_LOG │ ║ ║ │ transaction: 0 │ ║ ║ │ log index: 1 │ ║ ║ │ │ ║ ║ └───────────────────────────────┘ ║ ║ ║ ║ ┌───────────────────────────────┐ ║ ║ │ event type: TXN_LOG │ ║ ║ │ transaction: 1 │ ║ ║ │ log index: 1 │ ║ ║ │ │ ║ ║ └───────────────────────────────┘ ║ ║ ║ ║ ┌───────────────────────────────┐ ║ ║ │ event type: TXN_LOG │ ║ ║ │ transaction: 0 │ ║ ║ │ log index: 2 │ ║ ║ │ │ ║ ║ └───────────────────────────────┘ ║ ║ ║ ╚════════════════════════════════════╝ ``` A few things to note here: * Unlike most diagrams in the documentation, the events are shown in a simplified, "merged" form; in real events, some of this information is stored in the event descriptor and some is stored in the event payload, but they're combined to make the diagram simpler * It shows two transactions, with transaction indices 0 and 1. Although transaction 0 completes first in the EVM, its `TXN_EVM_OUTPUT` event is recorded *after* than the `TXN_EVM_OUTPUT` of transaction 1 * Events from the transactions are interleaved: sometimes the next one relates to transaction 0, sometimes to transaction 1, and there is no meaningful order between them * Despite the transactions being out-of-order with respect to each other, all the events associated with a particular transaction are always in relative order, i.e., the log indicies for a particular transaction will always be seen in `log_index` order, as above This is easy to understand if you imagine all of a transaction's events being recorded by a different thread. For a particular transaction, its thread always records that transaction's events in order, but the "transaction threads" themselves race against each other, recording in a non-deterministic order. This is similar to what really happens, except the transactions are recorded on [fibers](https://en.wikipedia.org/wiki/Fiber_\(computer_science\)) rather than full threads. ## Sequence numbers and the lifetime detection algorithm All event descriptors are tagged with an incrementing sequence number starting at 1. Sequence numbers are 64-bit unsigned integers which do not repeat unless the execution daemon is restarted. Zero is not valid sequence number. Also note that the sequence number modulo the descriptor array size equals the array index where the *next* event descriptor will be located. This is shown below with a concrete example where the descriptor array size is 64. Note that the last valid index in the array is 63, then access wraps around to the beginning of the array at index 0. ``` ◇ │ ╔═...═════════════════════════Event descriptor array═══╬═══════════════════...═╗ ║ │ ║ ║ ┌─Event────────┐┌─Event────────┐┌─Event────────┐ │ ┌─Event─────────┐ ║ ║ │ ││ ││ │ │ │ │ ║ ║ │ seqnum = 318 ││ seqnum = 319 ││ seqnum = 320 │ │ │ seqnum = 256 │ ║ ║ │ ││ ││ │ │ │ │ ║ ║ └──────────────┘└──▲───────────┘└──────────────┘ │ └───────────────┘ ║ ║ 61 │ 62 63 │ 0 ║ ╚═...════════════════════╬═════════════════════════════╬═══════════════════...═╝ │ │ ■ ◇ Next event Ring buffer wrap-around to ┌──────────────────────────────┐ zero is here │last read sequence number │ │(last_seqno) is initially 318 │ └──────────────────────────────┘ ``` In this example: * We keep track of the "last seen sequence number" (`last_seqno`) which has value `318` to start; being the "last" sequence number means we have already finished reading the event with this sequence number, which lives at array index `61` * `318 % 64` is `62`, so we will find the potential next event at that index *if* it has been produced * Observe that the sequence number of the item at index `62` is `319`, which is the last seen sequence number plus 1 (`319 == 318 + 1`). This means that event `319` has been produced, and its data can be safely read from that slot * When we're ready to advance to the next event, the last seen sequence number will be incremented to `319`. As before, we can find the *next* event (if it has been produced) at `319 % 64 == 63`. The event at this index bears the sequence number `320`, which is again the last seen sequence number + 1, therefore this event is also valid * When advancing a second time, we increment the last seen sequence number to `320`. This time, the event at index `320 % 64 == 0` is *not* `321`, but is a smaller number, `256`. This means the next event has not been written yet, and we are seeing an older event in the same slot. We've seen all of the currently available events, and will need to check again later once a new event is written * Alternatively we might have seen a much larger sequence number, like `384` (`320 + 64`). This would mean that we consumed events too slowly, so slowly that the 63 events in the range `[321, 384)` were produced in the meantime. These were subsequently overwritten, and are now lost. They can be replayed using services external to event ring API, but *within* the event ring API itself there is no way to recover them ## Lifetime of an event payload, zero copy vs. memcpy APIs Because of the descriptor overwrite behavior, an event descriptor might be overwritten by the execution daemon while a reader is still examining its data. To deal with this, the reader API makes a copy of the event descriptor. If it detects that the event descriptor changed during the copy operation, it reports a gap. Copying an event descriptor is fast, because it is only a single cache line in size. This is not the case for event payloads, which could potentially be very large. This means a `memcpy(3)` of an event payload could be expensive, and it would be advantageous to read the payload bytes directly from the payload buffer's shared memory segment: a "zero-copy" API. This exposes the user to the possibility that the event payload could be overwritten while still using it, so two solutions are provided: 1. A simple detection mechanism allows payload overwrite to be detected at any time: the writer keeps track of the minimum payload offset value (*before* modular arithmetic is applied) that is still valid. If the offset value in the event descriptor is smaller than this, it is no longer safe to read the event payload 2. A payload `memcpy`-style API is also provided. This uses the detection mechanism above in the following way: first, the payload is copied to a user-provided buffer. Before returning, it checks if the lifetime remained valid after the copy finished. If so, then an overwrite did not occur during the copy, so the copy must be valid. Otherwise, the copy is invalid The reason to prefer the zero-copy APIs is that they do less work. The reason to prefer memcpy APIs is that it is not always easy (or possible) to "undo" the work you did if you find out later that the event payload was corrupted by an overwrite while you were working with it. The most logical thing to do in that case is start by copying the data to stable location, and if the copy isn't valid, to never start the operation. An example user of the zero-copy API is the `eventwatch` example C program, which can turn events into printed strings that are sent to `stdout`. The expensive work of formatting a hexdump of the event payload is performed using the original payload memory. If an overwrite happened during the string formatting, the hexdump output buffer will be wrong, but that is OK: it will not be sent to `stdout` until the end. Once formatting is complete, `eventwatch` checks if the payload expired and if so, writes an error to `stderr` instead of writing the formatted buffer to `stdout`. Whether you should copy or not depends on the characteristics of the reader, namely how easily it can deal with "aborting" processing. ## Location of event ring files For performance reasons, we prefer that event ring files be created on a [hugetlbfs](https://www.kernel.org/doc/html/v4.18/admin-guide/mm/hugetlbpage.html#hugetlbpage) in-memory filesystem. Files created on such a filesystem will be backed by physically-contiguous large pages, which improves performance by about 15% in internal benchmarks. This can be a hassle though: it is unusual for a program to require that a file be placed on a *particular kind* of filesystem, and this requirement adds some overhead. In practice, this means additional configuration steps that a system administrator must perform when setting up a Monad node, and some additional concepts that SDK users must learn about. The issues are: 1. A hugetlbfs filesystem must be mounted somewhere on the host; usually by default (e.g., on a Ubuntu default installation) there will not be a hugetlbfs filesystem already present 2. Whomever configures a hugetlbfs filesystem must make sure that any user that needs to open the event ring file has the appropriate permissions 3. The path to the event ring file (which will be somewhere on that filesystem) must be passed into all programs that need to open it; since we don't know where the administrator will mount the filesystem, we can't easily hard-code a location for it in either the documentation or the source code To simplify the developer experience as much as possible, we follow three conventions. Each convention adds more "convenience default behavior" so that everything will "just work" for most users, but you are free to ignore any of the conventions and do things in your own way. The event ring library does not require a hugetlbfs filesystem: it can work with *any* kind of regular file. The C function that maps an event ring's shared memory segments -- `monad_event_ring_mmap` -- only takes a file descriptor, and does not know or care where this descriptor comes from. The only constraints on it are those placed by the `mmap(2)` system call itself. These conventions are about adding a reasonable default for how the mount point is set up, and helper functions for finding event ring files in that location. You should try to use them because they provide a performance benefit, but you are free to come up with a file descriptor in any way you wish and it will work with `monad_event_ring_mmap`. ### Convention 1: libhugetlbfs in the node setup guide The [official guide](https://validator-docs.vercel.app/docs/full_node/events-and-websockets) for setting up a local Monad node for execution events recommends the use of `libhugetlbfs`. `libhugetlbfs` is both a C library and a set of admin tools using that library that follow a particular configuration convention. The idea is to standardize some rules for how mount points and permissions are managed for hugetlbfs filesystems. There are three parts to the basic idea: 1. Each user (or group if you want to do it that way) gets its own separately-mounted hugetlbfs filesystem. The mount point is located in a well-defined place under `/var/lib/hugetlbfs/user/`[^1] 2. `hugeadm`, a program that a system administrator runs, is a configuration front-end for tasks like listing hugetlbfs mounts, creating new mounts, etc. 3. The C library, `libhugetlbfs`, helps client programs "find" hugetlbfs mounts that the current user has permission to access The setup guide for the Monad node tells the user to install the `libhugetlbfs` command line tools and to set up a "user mount" for the `monad` user. The guide also recommends that all users be given access to enter this directory, so that data consumer applications that run as non-`monad` users can open the file. [^1]: Other configuration schemes are possible too, see [`man hugeadm`](https://linux.die.net/man/8/hugeadm) ### Convention 2: "default" event ring directory The event ring library introduces the concept of a "default event ring directory." This is the default directory where event ring files should be created, and thus where reader applications should look for them. This default can come from one of two places: 1. You can provide it manually OR 2. If you don't provide it, the library will use a conventional location The conventional location is a subdirectory called `event-rings`, created directly under whatever hugetlbfs mount point is returned by `libhugeltbfs`[^2], i.e., it is: ``` /event-rings ``` If you follow the setup guide to the letter, this should be: ``` /var/lib/hugetlbfs/user/monad/pagesize-2MB/event-rings ``` But depending on how your system is setup, `libhugetlbfs` could return a different path. For example, you might see something like this: ``` /dev/hugepages/event-rings ``` This is because `libhugeltbfs` scrapes the contents of `/proc/mounts` and returns only one path that the current user has [access](https://man7.org/linux/man-pages/man2/access.2.html) to. What if the user has access to multiple hugetlbfs mounts? There is no logic to prefer one flavor of path over another, it only depends on their relative ordering in the `/proc/mounts` file. [^2]: The exact path might be user-dependent, and is determined by the function `hugetlbfs_find_path_for_size` #### Providing the default event ring directory manually You may wish to use this "open from the default directory" configuration idiom while by-passing libhugetlbfs. The two reasons to do that are: 1. If you don't want the event ring file to be present on a hugetlbfs file system at all; this is usually when you want to create an event ring file larger than the hugetlbfs mount point (or the system's underlying pool of huge pages) would allow 2. If you do not want to use libhugetlbfs as a library dependency of your project, in which case you will want to set the CMake `MONAD_EVENT_USE_LIBHUGETLBFS` option to `OFF` ### Convention 3: event ring filename resolution The "default directory" concept is used in the final convention, which is a "convenience" API call for turning user input for an event ring file into the path where your program will attempt to open that file. It allows users to specify a filename such as `xyz` and have it be translated to a full (and ugly) path like this: ``` /var/lib/hugetlbfs/user/monad/pagesize-2MB/event-rings/xyz ``` while still allowing the user to be able to specify *any* file, including one not in the default directory, if they wish. Here is how event ring file inputs are resolved by the the C function `monad_event_ring_resolve_file` and the Rust function `EventRingPath::resolve`: * If a "pure" filename is provided (i.e., a filename with no `/` character), it is resolved relative to a provided `default_path` directory * Otherwise (i.e., if the file contains any `/` character), it is resolved relative to the current working directory; if `/` is the first character, it is resolved as an absolute path This is similar to how a UNIX shell resolves a command name. A "pure" name with no path characters is resolved relative to the entries in the `$PATH` environment variable (i.e., it searches the default command directories). The presence of a path-separator character causes the input to be treated like a specific path relative to the current directory, which disables this "search". This familiar principal applies here. Furthermore: * In C you usually pass the sentinel value `MONAD_EVENT_DEFAULT_HUGETLBFS` (which is just an alias for `nullptr`) as the `default_path` parameter; this causes `libhugetlbfs` to figure out what the default hugetlbfs root path should be[^3]; in Rust this is just `EventRingPath::resolve` * You can provide your own `default_path` value, which can be on any path on any filesystem; this is required if you don't want `libhugetlbfs` as a dependency; in Rust this is `EventRingPath::resolve_with_default_path` Resolution does not try to open a file: it just standardizes the convention for how to build a path string from the two inputs. Namely, it does not check whether the computed file path exists or not. Remember that the event ring library itself only cares about file descriptors, and none of its APIs (even the "helper" APIs) attempt to [open(2)](https://man7.org/linux/man-pages/man2/open.2.html) a file. They just provide "reasonable default" ways of locating files that programs can opt into. If your host needs to set up your filesystem mounts differently, you are free to do that. #### Examples The table below shows how the C function `monad_event_ring_resolve_file` behaves. `` is the process' current working directory and `` is the mount point returned by `libhugetlbfs`. | `default_path` value | `input` value | resolve file returns... | Notes | | --------------------------------- | --------------------- | ------------------------------------------ | ------------------------------------------------------------- | | `MONAD_EVENT_DEFAULT_HUGETLBFS` | `"xyz"` | `"/event-rings/xyz"` | | | `MONAD_EVENT_DEFAULT_HUGETLBFS` | `"a/b/c"` | `"/a/b/c"` | `default_path` only affects "pure" file names | | `MONAD_EVENT_DEFAULT_HUGETLBFS ` | `"/d/e/f"` | `"/d/e/f"` | absolute paths always remain absolute | | `MONAD_EVENT_DEFAULT_HUGETLBFS` | `"monad-exec-events"` | `"/event-rings/monad-exec-events"` | the default event ring file name used by the execution daemon | | `"/tmp/my-event-ring-path"` | `"xyz"` | `"/tmp/my-event-ring-path/xyz"` | intermediate directories will be created if not existing | | `"/tmp/my-event-ring-path"` | `"a/b/c"` | `"/a/b/c"` | | | `"/tmp/my-event-ring-path"` | `"/d/e/f"` | `"/d/e/f"` | | In Rust, `EventRingPath::resolve` behaves like the `MONAD_EVENT_DEFAULT_HUGETLBFS` rows, and `EventRingPath::resolve_with_default_path` takes an explicit `basepath` argument and behaves like the bottom three rows. [^3]: The actual function used is the event ring library's utility function `monad_event_open_hugetlbfs_dir_fd`, which adds in the `event-rings` subdirectory path component and creates it if it does not already exist # C API Source: https://docs.monad.xyz/execution-events/c-api ## Core concepts There are two central objects in the event ring C API. They are: 1. **struct `monad_event_ring`** - represents an event ring whose shared memory segments have been mapped into the address space of the current process; the primary thing the client does with this object is use it to initialize iterators that point into the event ring, using the `monad_event_ring_init_iterator` function 2. **struct `monad_event_iterator`** - the star of the show: this iterator object is used to read sequential events. The iterator's `try_next` operation copies the current event descriptor (if it is available) and if successful, advances the iterator. Conceptually, it behaves like the expression `descriptor = *i++`, if an event descriptor is ready immediately (it does nothing otherwise) The easiest way to understand the API is to compile and run the included `eventwatch` example program. This program dumps ASCII representations of execution events to `stdout`, as they are written by a execution daemon running on the same host. In `eventwatch`, the event descriptors are fully decoded, but the event payloads are only shown in hexdump form, because this simple program that does not include pretty-printing logic for all event payload types. The program is only 250 lines of code, and reading through it should explain how the various API calls fit together. The SDK also includes C++20 [`std::formatter`](https://en.cppreference.com/w/cpp/utility/format/formatter.html) specializations which can fully decode event payloads into human-readable form. These are used by the `monad-event-cli` utility program. ## Using the API in your project `libmonad_event` is designed for third party integration, so it does not have any library dependencies aside from a recent version of glibc. This also means it has no dependency on the rest of the monad repository or on its build system: the sole requirement is a C compiler supporting C23. The "Getting start" guide to building the C example program [discusses several ways](/execution-events/getting-started/c#how-can-my-code-use-libmonad_eventa) to use the SDK library as a third-party dependency in your code. Alternatively, the source files that make up the library target can be copied into your own codebase. A Rust client library is [also available](/execution-events/rust-api). ## API overview ### Event ring APIs | API | Purpose | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `monad_event_ring_mmap` | Given a file descriptor to an open event ring file, map its shared memory segments into the current process, initializing a `struct monad_event_ring` | | `monad_event_ring_init_iterator` | Given a pointer to a `struct monad_event_ring`, initialize an iterator that can read from the event ring | | `monad_event_ring_try_copy` | Given a specific sequence number, try to copy the event descriptor for it, if it hasn't been overwritten | | `monad_event_ring_payload_peek` | Get a zero-copy pointer to an event payload | | `monad_event_ring_payload_check` | Check if an event payload referred to by a zero-copy pointer has been overwritten | | `monad_event_ring_memcpy` | `memcpy` the event payload to a buffer, succeeding only if the payload is not expired | | `monad_event_ring_get_last_error` | Return a human-readable string describing the last error that occurred on this thread | All functions which can fail will return an `errno(3)` domain error code diagnosing the reason for failure. The function `monad_event_ring_get_last_error` can be called to provide a human-readable string explanation of what failed. ### Event iterator APIs | API | Purpose | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | | `monad_event_iterator_try_next` | If an event descriptor if is available, copy it and advance the iterator; behaves like `*i++`, but only if `*i` is ready | | `monad_event_iterator_try_copy` | Copy the event descriptor at the current iteration point, without advancing the iterator | | `monad_event_iterator_reset` | Reset the iterator to point to the most recently produced event descriptor; used for gap recovery | | `monad_exec_iter_consensus_prev` | Rewinds an iterator to the previous consensus event (`BLOCK_START`, `BLOCK_QC`, `BLOCK_FINALIZED`, or `BLOCK_VERIFIED`) | | `monad_exec_iter_block_number_prev` | Rewinds an iterator to the previous consensus event for the given block number | | `monad_exec_iter_block_id_prev` | Rewinds an iterator to the previous consensus event for the given block ID | | `monad_exec_iter_rewind_for_simple_replay` | Rewinds an iterator to replay events you may have missed, based on the last finalized block you saw | ### Event ring utility APIs | API | Purpose | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `monad_event_ring_check_content_type` | Check if the binary layouts of event definitions used by the library match what is recorded in a mapped event ring | | `monad_event_ring_find_writer_pids` | Find processes that have opened an event ring file descriptor for writing; used for detecting publisher exit | | `monad_check_path_supports_map_hugetlb` | Check if a path is on a filesystem that allows its files to be mmap'ed with `MAP_HUGETLB` | | `monad_event_open_hugetlbfs_dir_fd` | Open the default hugetlbfs directory where event ring files are created[^1] | | `monad_event_resolve_ring_file` | If a path contains no `/` character (i.e., if it is a "pure" filename), resolve it relative to some default event ring directory[^2] | | `monad_event_is_snapshot_file` | Check if a path refers to an event ring snapshot file | | `monad_event_decompress_snapshot_fd` | Decompress the event ring snapshot contained in the given file descriptor | | `monad_event_decompress_snapshot_mem` | Decompress the event ring snapshot contained in the given memory buffer | [^1]: By default, this returns the a path on a hugetlbfs mount, as computed by libhugetlbfs [^2]: If compiling with `MONAD_EVENT_USE_LIBHUGETLBFS=OFF`, a default event ring directory must be specified; see [here](/execution-events/advanced#location-of-event-ring-files) for details ## Library organization Event ring files in `libmonad_event`: | File | Contains | | ------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `event_ring.{h,c}` | Definitions of core shared memory structures for event rings, and the API that initializes and mmaps event ring files | | `event_iterator.h` | Defines the basic event iterator object and its API | | `event_iterator_inline.h` | Definitions of the `event_iterator.h` functions, all of which are inlined for performance reasons | | `event_metadata.h` | Structures that describe event metadata (string names of events, descriptions of events, etc.) | | `exec_iter_help.h` | API for rewinding the the iterator to point to block executions or consensus events | Execution event files in `libmonad_event`: | File | Contains | | ------------------------------ | ------------------------------------------------------------------------------------------------------------- | | `base_ctypes.h` | Definitions of basic vocabulary types common in Ethereum data (e.g., 256 bit integer types, etc). | | `eth_ctypes.h` | Definitions of structures used in the Ethereum virtual machine | | `exec_event_ctypes.h` | Definition of execution event payload structures, and the event type enumeration `enum monad_exec_event_type` | | `exec_event_ctypes_metadata.c` | Defines static metadata about execution events, and the schema hash value array | | `monad_ctypes.h` | Definitions of Monad blockchain extensions to Ethereum | Supporting files in `libmonad_event`: | File | Contains | | ----------------------- | ------------------------------------------------------------------------------------------------------------- | | `event_ring_util.{h,c}` | Convenience functions that are useful in most event ring programs, but which are not part of the core API | | `format_err.{h,c}` | Helper utility from the execution codebase used to implement the `monad_event_ring_get_last_error()` function | | `srcloc.h` | Helper utility used with the `format_err.h` API, for capturing source code locations in C | Other files in the SDK: | File | Contents | | ----------------- | ------------------------------------------------------------------------------------------------------------------ | | `eventwatch.c` | A sample program that shows how to use the API | | `*_fmt.hpp` files | Files ending in `_fmt.hpp` are used with C++ `` and contain `std::formatter` specializations for SDK types | | `hex.hpp` | `` hexdump utility used by the `_fmt.hpp` files to dump `uint8_t[]` values | # Consensus events Source: https://docs.monad.xyz/execution-events/consensus-events As explained [here](/monad-arch/consensus/asynchronous-execution), Monad's consensus and execution services are decoupled, and execution is asynchronous with the respect to consensus: the two don't have to move in lock step, and can working on different blocks. Also, execution can [speculatively execute](/monad-arch/consensus/asynchronous-execution#speculative-execution) blocks whose consensus outcome is not yet known. Execution events are "trace" information reported directly from the EVM during execution, so they report real-time data on *speculative* basis: the event data may relate to a block that is never finalized. Dealing with real-time data on a speculative basis is discussed in detail on [this page](/monad-arch/realtime-data/spec-realtime). The "takeaway" from that part of the documentation is the following: if you consume speculative real-time data, then you need to understand [block commit states](/monad-arch/consensus/block-states) and how the real-time data protocol you're using communicates the changes in block states. For example, for the Monad WebSocket extension feeds, [this section](/reference/websockets#monadnewheads-and-monadlogs) explains how the block IDs and commit states are announced for the `monadNewHeads` subscription. This documentation page explains how it is done with execution events. ## Block tags As explained [here](/monad-arch/realtime-data/spec-realtime#block-numbers-and-block-ids), blocks must be identified by their unique ID prior to finalization. Even so, it is often useful to know what the proposed block number is, even before we know if the block will committed with that number or not. The following structure -- called a "block tag" -- appears as a field in several execution event payload types, to communicate the block ID and the (proposed) block number together. ``` struct monad_exec_block_tag { monad_c_bytes32 id; ///< Monad consensus unique ID for block uint64_t block_number; ///< Proposal is to become this block }; ``` ## The four consensus events The four [block commit states](/monad-arch/consensus/block-states) correspond to four execution event types. Events of these types are published to announce that a particular block is moving to a new commit state. ### First consensus event: **`BLOCK_START`** (*proposed* state) ```c theme={null} /// Event recorded at the start of block execution struct monad_exec_block_start { struct monad_exec_block_tag block_tag; ///< Execution is for this block uint64_t block_round; ///< Round when block was proposed uint64_t epoch; ///< Epoch when block was proposed monad_c_bytes32 parent_eth_hash; ///< Hash of Ethereum parent block monad_c_uint256_ne chain_id; ///< Block chain we're associated with struct monad_c_eth_block_exec_input exec_input; ///< Ethereum execution inputs }; ``` The first event recorded by the EVM is a `BLOCK_START` event, whose event payload contains a `block_tag` field that introduces the unique ID for the block and the block number it will eventually have, if it gets finalized. Almost all execution events (transaction logs, call frames, receipts, etc.) occur between the `BLOCK_START` and `BLOCK_END` events. In the current implementation, block execution is never pipelined, so all events between `BLOCK_START` and `BLOCK_END` pertain to a single block, and there will not be another `BLOCK_START` until the current block is ended. Unlike the other events in this list, `BLOCK_START` is both a "consensus event" (it means the associated block is in proposed state) and an "EVM event," because execution information about the block is being made available to you. The other events in this list are not like that. They are "pure" consensus events: they tell you what happened to a proposed block in the consensus algorithm, after you've already seen all of its EVM events. To understand the implications of this state, see [here](/monad-arch/realtime-data/spec-realtime#first-commit-state-proposed). There's no reason why a block *has* to start in the proposed state. If execution is lagging behind consensus, it's possible that a block might have advanced to a later state in the consensus algorithm. For example, suppose consensus has been working on a block for a while, and by the time execution finally sees it, perhaps consensus knows that it has progressed to voted. In the current implementation, however, execution will not know this. It implicitly considers everything it executes to only be proposed. This is only literally true if execution is not lagging behind. ### Second consensus event: **`BLOCK_QC`** (*voted* state) ```c theme={null} /// Event recorded when a proposed block obtains a quorum certificate struct monad_exec_block_qc { struct monad_exec_block_tag block_tag; ///< QC for proposal with this block uint64_t round; ///< Round of proposal vote uint64_t epoch; ///< Epoch of proposal vote }; ``` When a block with the given tag is voted, an event of this type is published to announce it. To understand all the implications of seeing this event, see [here](/monad-arch/realtime-data/spec-realtime#second-commit-state-voted). ### Third consensus event: **`BLOCK_FINALIZED`** ```c theme={null} /// Event recorded when consensus finalizes a block typedef struct monad_exec_block_tag monad_exec_block_finalized; ``` The finalized event payload does not have any information that isn't already part of the block tag, so the payload is just the tag of the block that gets finalized. To understand all the implications of seeing this event, see [here](/monad-arch/realtime-data/spec-realtime#third-commit-state-finalized). ### Fourth consensus event: **`BLOCK_VERIFIED`** ```c theme={null} /// Event recorded when consensus verifies the state root of a finalized block struct monad_exec_block_verified { uint64_t block_number; ///< Number of verified block }; ``` The consensus algorithm produces one last event for a block, called `BLOCK_VERIFIED`. This time, it is sufficient to identify the block only by its block number. Because verified blocks are already finalized, they are part of the canonical blockchain and cannot be reverted without a hard fork. Thus, we no longer need the block tag. To understand all the implications of seeing this event, see [here](/monad-arch/realtime-data/spec-realtime#fourth-commit-state-verified). # Event rings in detail Source: https://docs.monad.xyz/execution-events/event-ring ## Event ring files and content types Event rings are made up of four shared memory segments. Two of these -- the event descriptor array and payload buffer -- are described in the [overview documentation](/execution-events/overview). The third shared memory segment contains a header that describes metadata about the event ring. The fourth (the "context area") is a special feature that is not needed for execution events. The shared memory segments are mapped into a process' address space using [mmap(2)](https://man7.org/linux/man-pages/man2/mmap.2.html). This means that the event ring's data structures live in a file somewhere, and that shared access to it is obtained by creating shared memory mappings of that file. Most of the time an event ring is a regular file, created on a special in-memory file system called [hugetlbfs](https://lwn.net/Articles/375096/). hugetlbfs is similar to the [tmpfs](https://man7.org/linux/man-pages/man5/tmpfs.5.html) in-memory filesystem, but supports the creation of files backed by large page sizes. The use of large pages is just an [optimization](https://lwn.net/Articles/374424/): event ring files may be created on any file system. If the execution daemon is told to create an event ring file on a filesystem without hugetlb mmap support, it will log a performance warning but will still create the file. To learn more about hugetlbfs and how it is used, read [this page](/execution-events/advanced#location-of-event-ring-files). ### Event ring configuration To use execution events, the execution daemon must be started with the command line parameter: ``` --exec-event-ring [] ``` Without this command line parameter, execution will not publish any events. This command line parameter (and mounting a hugetlbfs filesystem, for that matter) are not part of the default configuration instructions for the execution daemon. A [separate guide](https://validator-docs.vercel.app/docs/full_node/events-and-websockets) covers mounting a hugetlbfs filesystem and modifying the command line in the systemd unit configuration files. Note that the configuration string is optional; if you pass `--exec-event-ring` without an argument (which is the recommended thing to do), this is equivalent to passing `--exec-event-ring monad-exec-events`, where `monad-exec-events` is the default execution event ring file name. The event ring configuration string has the form: ``` [::] ``` In other words, the configuration string consists of three `:`-separated fields; the first field is required but the second two are optional. Here is an example of the command line parameter, with just the first field: ``` --exec-event-ring /var/lib/hugetlbfs/user/monad/pagesize-2MB/event-rings/monad-exec-events ``` and another example, with all three: ``` --exec-event-ring monad-exec-events:21:29 ``` The first field is the name of the event ring file. The execution daemon interprets this field in two different ways: * If it is purely a file name -- i.e., if the path does not contain any `/` characters -- then it is interpreted as a file living in the default event ring file directory; this is the directory returned by the API function `monad_event_open_ring_dir_fd`; it uses [libhugetlbfs](https://github.com/libhugetlbfs/libhugetlbfs) to locate the most suitable mount point for a hugetlbfs file system, and automatically creates subdirectory called `event-rings` underneath that, if it does not already exist (see [here](/execution-events/advanced#location-of-event-ring-files) for more information); the event ring file will be created in that `event-rings` subdirectory * If the path has multiple path components -- i.e., if it contains at least one `/` character -- then this path will be used as written *even if* it is not resident on a hugetlbfs file system; the rationale for why someone might want this is explained below The "shift" parameters are power-of-two exponents that determine the event ring's size.[^1] A `` of 21 means there will be 2^21 descriptors in the ring's event descriptor array. This means approximately 2 million events can be written before the descriptor ring buffer wraps around and overwrites older event descriptors. A `` of 29 means there will be 2^29 bytes in the payload buffer array. This means 512 MiB worth of event payloads can be recorded before the payload buffer wraps around and overwrites an older event's payload. If the event ring size parameters are not specified, then default values are used. Why might you increase these values from their defaults? If your reader program crashes, but execution does not, then you will likely miss some events during the time when your program is not running. Your application might not care about lost events for old blocks, but if it does, you'll need to retrieve them somehow. If not much time has gone by, chances are good that the events you are missing are still sitting there in the event ring memory, i.e., are not overwritten yet. The default sizes are large enough to hold several minutes worth of blocks at 10k TPS. Increasing these values allows you to rewind further back in time. Be aware that there is a fixed-size pool of huge pages. The pool size can be changed by modifying the system's configuration, see the discussion of `/proc/sys/vm/nr_hugepages` [here](https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt). If an event ring is created on a hugetlbfs mount, and its size exceeds the number of available huge pages, then the execution daemon will exit with an error message that reports a "no space left on device" error. For example, here we tried to allocate an event ring with a one terabyte payload buffer: ``` LOG_ERROR event library error -- monad_event_ring_init_simple@event_ring_util.c:78: posix_fallocate failed for event ring file `/dev/hugepages/monad-exec-events`, size 1099647942656: No space left on device (28) ``` If you happen to have multiple terabytes of main memory available, you could pass a file path containing a `/` character to a file on a tmpfs mount and it would work, e.g., `--exec-event-ring /my-giant-tmpfs/monad-exec-events`. If you need to rewind back especially far -- or if you are missing events because execution itself has crashed -- then you will need to use alternative recovery methods described elsewhere.[^2] [^1]: They are called "shifts" because `1UL << x` is equal to `2^x` [^2]: The alternative recovery methods are still in development and will be available in the next SDK release ### Event ring file format The event ring file format is simple: all four sections are laid out sequentially and aligned to a large page boundary, and the header describes the size of each section. ``` ╔═Event ring file══╗ ║ ┌──────────────┐ ║ ║ │ │ ║ ║ │ Header │ ║ ║ │ │ ║ ║ ├──────────────┤ ║ ║ │ │ ║ ║ │ Event │ ║ ║ │ Descriptor │ ║ ║ │ Array │ ║ ║ │ │ ║ ║ ├──────────────┤ ║ ║ │ │ ║ ║ │ │ ║ ║ │ │ ║ ║ │ │ ║ ║ │ Payload │ ║ ║ │ Buffer │ ║ ║ │ │ ║ ║ │ │ ║ ║ │ │ ║ ║ │ │ ║ ║ │ │ ║ ║ ├──────────────┤ ║ ║ │ │ ║ ║ │ Context │ ║ ║ │ Area │ ║ ║ │ │ ║ ║ └──────────────┘ ║ ╚══════════════════╝ ``` The descriptor array is a just an array of `struct monad_event_descriptor` objects, and the payload buffer is a flat byte array (i.e., it has type `uint8_t[]`). The header structure is defined this way: ```c theme={null} /// Event ring shared memory files start with this header structure struct monad_event_ring_header { char magic[6]; ///< 'RINGvv', vv = version number enum monad_event_content_type content_type; ///< Kind of events in this ring uint8_t schema_hash[32]; ///< Ensure event definitions match struct monad_event_ring_size size; ///< Size of following structures struct monad_event_ring_control control; ///< Tracks ring's state/status }; ``` ### Event content types The `content_type` header field is needed because the event ring library -- both the reader and writer APIs -- performs unstructured I/O: the functions read and write raw `uint8_t[]` event payloads, and the event descriptors contain plain `uint16_t` numerical event codes. Much like the UNIX `read(2)` and `write(2)` file I/O system calls, the event ring API functions do not inherently know the format of data they are working with. This is the reason why the `event_type` field in the event descriptor is the generic integer type `uint16_t` instead of `enum monad_exec_event_type`. The assumption here is that the reader and writer know the binary format of the data they're both working with, and they treat the raw data as if it has this format by type-casting it when needed, e.g., ```c theme={null} const struct monad_exec_block_start *block_start = nullptr; // We assume that the event ring file we opened contains execution events, // and thus further assume that it makes sense to compare `event->event_type` // to a value of type `enum monad_exec_event_type` if (event->event_type == MONAD_EXEC_BLOCK_START) { // Since this is MONAD_EXEC_BLOCK_START, we can cast the `const void *` // payload to a `const struct monad_exec_block_start *` payload. // Note: implicit type-casting from `void *` is allowed in C, but not C++ block_start = monad_event_ring_payload_peek(event_ring, event); } ``` We need some kind of error-detection mechanism to ensure this is safe to do. The event ring file header contains a "content type" enumeration constant explaining what kind of event data it contains: ```c theme={null} enum monad_event_content_type : uint16_t { MONAD_EVENT_CONTENT_TYPE_NONE, ///< An invalid value MONAD_EVENT_CONTENT_TYPE_TEST, ///< Used in simple automated tests MONAD_EVENT_CONTENT_TYPE_EXEC, ///< Core execution events MONAD_EVENT_CONTENT_TYPE_PERF, ///< Performance tracer events MONAD_EVENT_CONTENT_TYPE_COUNT ///< Total number of known event rings }; ``` The execution events are always recorded to a ring with `content_type` equal to `MONAD_EVENT_CONTENT_TYPE_EXEC`. ### Binary schema versioning: the `schema_hash` field If `content_type` is equal to `MONAD_EVENT_CONTENT_TYPE_EXEC`, then we know a ring is supposed to execution events, but what if the event payload definitions change? Or what if the enumeration constants in `enum monad_exec_event_type` change? Suppose that a user compiles their application with a particular version of `exec_event_ctypes.h`, the file which defines the execution event payloads and the event type enumeration. Now imagine that some time later, the user deploys a new version of the execution node, which was compiled with a different version of `exec_event_ctypes.h`, causing the memory representation of the event payloads to be different. If the reader does not remember to recompile their application with the new header, it could misinterpret the bytes in the event payloads, assuming they have the old layout from their old (compile-time) version of `exec_event_ctypes.h`. To prevent these kinds of errors, the binary layout of all event payloads is summarized by a hash value which changes any time a change is made to any event payload for that content type. In addition to payload changes, any change to `enum monad_exec_event_type` will also generate a new hash. This mechanism is called the "schema hash", and the hash value is present as a global, read-only byte array inside the library code (defined in `exec_event_ctypes_metadata.c`). If the hash value in this array does not match the hash value in the event ring file header, then the binary formats are incompatible. A helper function called `monad_event_ring_check_content_type` is used to check that an event ring file has both the expected content type, and the expected schema hash for that content type. Here is an example of it being called in the `eventwatch.c` sample program: ```c theme={null} struct monad_event_ring exec_ring; /* initialization of `exec_ring` not shown */ if (monad_event_ring_check_content_type( &exec_ring, MONAD_EVENT_CONTENT_TYPE_EXEC, g_monad_exec_event_schema_hash) != 0) { errx(EX_SOFTWARE, "event library error -- %s", monad_event_ring_get_last_error()); } ``` If the type of event ring is not `MONAD_EVENT_CONTENT_TYPE_EXEC` or if the `schema_hash` in the file header does not match the value contained in the global array `uint8_t g_monad_exec_event_schema_hash[32]`, this function will return the `errno(3)` domain code `EPROTO`. ## Event descriptors in detail ### Binary format The event descriptor is defined this way: ```c theme={null} struct monad_event_descriptor { alignas(64) uint64_t seqno; ///< Sequence number, for gap/liveness check uint16_t event_type; ///< What kind of event this is uint16_t : 16; ///< Unused tail padding uint32_t payload_size; ///< Size of event payload uint64_t record_epoch_nanos; ///< Time event was recorded uint64_t payload_buf_offset; ///< Unwrapped offset of payload in p. buf uint64_t content_ext[4]; ///< Extensions for particular content types }; ``` ### Flow tags: the `content_ext` fields in execution event rings For each content type, we may want to publish additional data directly in the event descriptor, e.g., if that data is common to every payload type or if it would help the reader quickly filter out events they are not interested in, without needing to examine the event payload. This additional data is stored in the `content_ext` ("content extensions") array, and its meaning is defined by the `content_type`. For execution event rings, the first three values of the `content_ext` array are sometimes filled in. The value at each index in the array has the semantic meaning described by the following enumeration type, which is defined in `exec_event_ctypes.h`: ```c theme={null} /// Stored in event descriptor's `content_ext` array to tag the /// block & transaction context of event enum monad_exec_flow_type : uint8_t { MONAD_FLOW_BLOCK_SEQNO = 0, MONAD_FLOW_TXN_ID = 1, MONAD_FLOW_ACCOUNT_INDEX = 2, }; ``` For example, if we have an event descriptor ```c theme={null} struct monad_event_descriptor event; ``` And its contents are initialized by a call to `monad_event_iterator_try_next`, then `event.content_ext[MONAD_FLOW_TXN_ID]` will contains the "transaction ID" for that event. The transaction ID is equal to the transaction index plus one, and it is zero if the event has no associated transaction (e.g., the start of a new block). The idea behind the "flow" tags is that they tag events with the context they belong to. For example, when a transaction accesses a particular account storage key, a `STORAGE_ACCESS` event is emitted. By looking at the `content_ext` array for the `STORAGE_ACCESS` event descriptor, the reader can tell it is a storage access made (1) by the transaction with index `event.content_ext[MONAD_FLOW_TXN_ID] - 1` and (2) to the account with index `event.content_ext[MONAD_FLOW_ACCOUNT_INDEX]` (this index is related to an earlier `ACCOUNT_ACCESS` event series that will have already been seen). Flow tags are used for two reasons: 1. **Fast filtering** - if we are processing 10,000 transactions per second, and there are at least a dozen events per transaction, then we only have about 10 microseconds to process each event or we'll eventually fall behind and gap. At timescales like these, even touching the memory containing the event payload is expensive, on a relative basis. The event payload lives on a different cache line -- one that is not yet warm in the reader's CPU -- and the cache line ownership must first be changed in the cache coherence protocol (because it was recently exclusively owned by the writer, and now must be shared with the reading CPU, causing cross-core bus traffic). For most applications, the user can identify transactions IDs they are interested in at the time of the `TXN_HEADER_START` event, and then any event without an interesting ID can be ignored. Because the IDs are a dense set of integers, a simple array of type `bool[TXN_COUNT + 1]` can be used to efficiently look up whether subsequent events associated with that transaction are interesting (this can be made even more efficient using a single bit instead of a full `bool` per transaction) 2. **Compression** - the account of a `STORAGE_ACCESS` is referred to by an index (which refers to an earlier `ACCOUNT_ACCESS` event) because an account address is 20 bytes: large enough that it cannot fit in the two remaining `content_ext` array slots The compression technique is also used for storing the block associated with an event, in `event.content_ext[MONAD_FLOW_BLOCK_SEQNO]`. The flow tag in this case is the *sequence number* of the `BLOCK_START` event that started the associated block. A few things to note about this flow tag: * Sometimes it is zero (an invalid sequence number), which means the event is not associated with any block; although most events are scoped to a block, the consensus state change events (`BLOCK_QC`, `BLOCK_FINALIZED`, and `BLOCK_VERIFIED`) do not occur inside a block * Note that the block flow tag is *not* the block number. This is because at the time events are seen, the blocks are in the "proposed" state, and the consensus algorithm has not finished voting on whether or not the block will be included in the canonical blockchain (this is discussed extensively in the next section). Until a block becomes finalized, the only unambiguous way to refer to it is by its unique ID, which is 32-byte hash value (which can be read from the `BLOCK_START` payload); thus the block flow tag is also a form of compression * Having the sequence number allows us to rewind the iterator to the start of the block, if we start observing the event sequence in the middle of a block (e.g., if the reader starts up after the execution daemon). An example of this (and a detailed explanation of it) can be found in the `eventwatch.c` example program, in the `find_initial_iteration_point` function # Building the C example program Source: https://docs.monad.xyz/execution-events/getting-started/c The C and C++ languages do not have a standard package manager, so using a third-party library requires the programmer to come up with their own dependency management scheme. Before we perform the first step of the guide, we'll discuss the options the user has for integrating the SDK library dependency into their project. Then we'll pick one of these options and build the example program using that method. A second method will be briefly shown at the end. If you are not familiar with CMake, you may want to read CMake's ["Using Dependencies Guide"](https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html) first ### Where is the SDK source code? The execution event C SDK lives in the same source code repository as the execution daemon ([here](https://github.com/category-labs/monad)), in the subdirectory [`category/event`](https://github.com/category-labs/monad/tree/main/category/event). It has a separate `CMakeLists.txt` file that can act as a top-level project file, so that users do not need to build the full execution project in order to compile it. The SDK's build system produces a library called `libmonad_event.a`, or `libmonad_event.so` if you prefer shared libraries. You will also need the public header files. ### How can my code use `libmonad_event.a`? Here are three different options: 1. **Precompiled library** - you could build the library yourself and store the library file (and its headers) somewhere, then import it into your build system manually. The SDK build system also creates a CMake "config" file for use with [`find_package`](https://cmake.org/cmake/help/latest/command/find_package.html) to help import it, if you are also using CMake The other two options assume your project is also using CMake: 2. **CMake subproject integration** - your CMake project can include the SDK as a subproject. In this case, you download the source code of the execution repository as part of your own project, and call the CMake function: ``` add_subdirectory(/category/event) ``` This will add the SDK's library target (called `monad_event`) into your parent CMake project. One way to add the SDK code to your build is to use a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules). Another way is to use CMake's [`FetchContent`](https://cmake.org/cmake/help/latest/module/FetchContent.html) module. The three main differences between these approaches are: 1. By default, `FetchContent` will clone a git repository into your CMake build tree at build configuration time, whereas a git submodule integrates into your source tree at the repository level 2. With `FetchContent`, the version you check out is specified by the `GIT_TAG` you specify in a `CMakeLists.txt` file; for `git submodule`, it is managed via git commands 3. If the content you are fetching has its own CMake buildsystem (as the C SDK does), `FetchContent` will automatically call `add_subdirectory` to add it to the current project; in the git submodule approach, you need to do this manually 3. **CMake `ExternalProject` integration** - CMake's [`ExternalProject`](https://cmake.org/cmake/help/latest/module/ExternalProject.html) module is similar to `FetchContent`, but is more isolated; this will build and install the SDK into a "staging" directory somewhere in your CMake build tree. This uses a completely separate CMake invocation, so it will not add the SDK's CMake project into your own. This means, for example, that you will not automatically have a `monad_event` library target in your own CMake project -- you would need to create one as an imported target. `ExternalProject` helps isolate your build system from the SDK's build system, ensuring that CMake configuration and variables from the SDK can't "leak" into your parent project In this guide, we'll use the `FetchContent` approach. This encapsulates the entire process as a simple, all-in-one `CMakeLists.txt` file, and we comment that file to explain everything you need to know. This is the clear best choice for our tiny "Getting started" example program, but it might not be best for your real project. At the end of this guide, an alternative method using `find_package` is briefly shown. ## Building the example program with `FetchContent` ### Step 1: install prerequisite development packages In addition to CMake (at least version 3.23) and git, we will need a recent C compiler and two third-party libraries. We will also use [curl](https://curl.se/docs/manpage.html) to download some files. #### Required C compiler The C SDK uses some recent features from C23, and requires either gcc-13 or clang-19. If the default compiler found by CMake is too old, you will need to specify an alternative C compiler by setting the `CC` environment variable or using a CMake [toolchain file](https://cmake.org/cmake/help/latest/variable/CMAKE_TOOLCHAIN_FILE.html). The default compiler chosen by CMake is usually the one reported by the output of the command `cc -v`. If you need to use a different one, you can use the bash syntax `VAR=VALUE ` for setting an environment variable in the scope of the following command, e.g.: ```shell theme={null} $ CC=gcc-15 cmake ``` #### Required C++ compiler C++ is not used in this example, but there are some optional C++ components in the CMake project. Consequently, you must have a C++ compiler installed or the CMake configuration step will fail. The SDK is written in pure C, except for some C++ header files that can be used to "pretty-print" event types using the `` library. These are not part of the example program, and they require full C++23 support for formatting ranges (the `__cpp_lib_format_ranges` feature test macro) which were added to libstdc++ in gcc version 15.2. This is a recent release: the first time gcc 15.2 appeared in the Ubuntu package repositories was in Ubuntu 25.10. You can also use clang with libc++, the LLVM implementation of the C++ standard library, which has had range formatting support since version 19. An example of building a C++ program with clang-19 is shown as the optional last step of the "Getting started" guide, when we build the `monad-event-cli` utility. #### Required third-party libraries | Requirement | Ubuntu package name | What is it used for? | | ------------ | ------------------- | ----------------------------------------------------------------------------------------------------------- | | zstd library | libzstd-dev | Snapshot event ring files are compressed with zstd; `libzstd` is needed to decompress them | | libhugetlbfs | libhugetlbfs-dev | `libhugetlbfs` is used to locate the optimal hugetlbfs mount point to create event ring shared memory files | `libzstd` is a hard requirement; `libhugetlbfs` is optional but is expected by default on Linux. It can be turned off manually by setting the CMake option `MONAD_EVENT_USE_LIBHUGETLBFS` to `OFF`. #### macOS compatiblity Real-time data requires a Linux host (because the execution daemon itself does), but you can compile and run the example program on historical data using macOS. This allows you to "try before you buy" and explore the SDK before going through the trouble of setting up a Monad node on Linux. In this case, you do not need `libhugetlbfs` (which is a Linux-only library), but you will need a recent XCode toolchain, the `libzstd` compression library, and CMake. The last two are not included in the default XCode development tools, so you'll probably want to use [Homebrew](https://brew.sh/) or [MacPorts](https://www.macports.org/) to install these onto your system. As for XCode itself, any version since 16.3 should work, but it has only been tested with version 23.2 (the real requirement is for Apple Clang 17).[^1] [^1]: "Apple Clang" is based on, but different than, vanilla LLVM [Clang](https://clang.llvm.org/). Apple Clang 17 is based on LLVM/Clang 19, thus the discrepancy in the requirements table. ### Step 2: download the example program First, create a new directory and download the example program source file into it. We'll use the example directory `~/src/event-sdk-example-c` ```shell theme={null} $ mkdir -p ~/src/event-sdk-example-c $ cd ~/src/event-sdk-example-c $ curl -O https://raw.githubusercontent.com/category-labs/monad/refs/tags/release/exec-events-sdk-v1.1/category/event/example/eventwatch.c ``` You should now have a file called `eventwatch.c` in your new directory. ### Step 3: add a `CMakeLists.txt` build file Create a `CMakeLists.txt` file in the directory alongside `eventwatch.c` and copy these contents into it: ```CMakeLists.txt theme={null} cmake_minimum_required(VERSION 3.23) project(eventwatch LANGUAGES C) # # SDK setup # include(FetchContent) FetchContent_Declare(exec_events_c_sdk # The execution events C SDK is kept in the same git repository as the # execution daemon itself GIT_REPOSITORY https://github.com/category-labs/monad.git # The latest version of the SDK is available on a special release branch # of the execution repository GIT_TAG release/exec-events-sdk-v1.1 # This will only download the SDK branch GIT_SHALLOW TRUE # This will disable the checkout of all git submodules; they are needed # for the full execution daemon to build, but not the SDK GIT_SUBMODULES "" # The top-level CMakeLists.txt builds the entire execution daemon; we don't # want that, so we specify SOURCE_SUBDIR to choose a CMakeLists.txt file # in a sudirectory to treat as the "top-level" file for the external # project; this only creates the monad_event library SOURCE_SUBDIR category/event) # The SDK's build system also builds the same example we're building now, using # the same target name ('eventwatch'). This is done as a CI check to ensure # that the upstream project doesn't break the example program. We have to # disable it, because it will conflict with the eventwatch target we're going # to add below (CMake does not allow two targets with the same name) set(MONAD_EVENT_BUILD_EXAMPLE OFF CACHE INTERNAL "") # This will download the source code and call add_subdirectory, which will add # the `monad_event` library target; this is the SDK target we need to link FetchContent_MakeAvailable(exec_events_c_sdk) # # eventwatch example program target # add_executable(eventwatch eventwatch.c) target_compile_options(eventwatch PRIVATE -Wall -Wextra -Wconversion -Werror) target_link_libraries(eventwatch PRIVATE monad_event) ``` ### Step 4: run CMake and build #### CMake with `make` and the default compiler: ```shell theme={null} $ cmake -S ~/src/event-sdk-example-c -B ~/src/event-sdk-example-c/build $ cd ~/src/event-sdk-example-c/build $ make ``` #### CMake with `ninja` and an alternate compiler: Here is another possible invocation, which sets an alternative C compiler using the `CC` environment variable and uses the [Ninja](https://ninja-build.org/) build tool: ```shell theme={null} $ CC=clang-19 cmake -S ~/src/event-sdk-example-c -B ~/src/event-sdk-example-c/build -G Ninja $ cd ~/src/event-sdk-example-c/build $ ninja ``` The compilation should produce an executable file called `eventwatch`. Try running it with the `-h` flag to print the help. ```shell theme={null} $ ./eventwatch -h usage: eventwatch [-h] [] execution event observer example program Options: -h | --help print this message Positional arguments: path of execution event ring shared memory file [default: monad-exec-events] ``` If all was successful, continue on to the [next step in the guide](/execution-events/getting-started/snapshot) or if you are also interested in Rust, build the [Rust example program](/execution-events/getting-started/rust). The Rust example program prints more interesting output than the C version, thanks to Rust's [`#[derive(Debug)]`](https://doc.rust-lang.org/rust-by-example/hello/print/print_debug.html) attribute, so the "Getting started" experience is better in Rust. You can do an equivalent thing in C++ by using the aformentioned `std::formatter` specializations, but they're not in the tutorial. You can also continue on to the next section on this page, which shows a different way of integrating with the C library. ## Alternative: install locally, find with `find_package` Now that you have seen the "all-in-one" tutorial, which explains the source code organization, where the SDK's `CMakeLists.txt` file is, etc., it is easy to show an alternative kind of build system without as much commentary. In this section we will: * Install the SDK to the temporary directory `/tmp/sdk-install-demo`, which will have the traditional `include` and `lib` directory structure, but also a `lib/cmake/category-labs` directory containing the config files for CMake's `find_package` * Compile `eventwatch.c` again, this time using `find_package` which will be instructed to look in `/tmp/sdk-install-demo` ### Step 1: build and install `libmonad_event.a` ```shell theme={null} $ git clone -b release/exec-events-sdk-v1.1 https://github.com/category-labs/monad.git \ ~/src/monad-exec-events-sdk $ cmake -S ~/src/monad-exec-events-sdk/category/event \ -B ~/build/monad-exec-events-sdk-v1-release \ -DCMAKE_INSTALL_PREFIX=/tmp/sdk-install-demo -DCMAKE_BUILD_TYPE=RelWithDebInfo $ cmake --build ~/build/monad-exec-events-sdk-v1-release $ cmake --install ~/build/monad-exec-events-sdk-v1-release ``` If all is successful, you should have a populated `/tmp/sdk-install-demo` directory. ### Step 2: create a new directory and download `eventwatch.c` ```shell theme={null} $ mkdir -p ~/src/event-sdk-example-c-find-package $ cd ~/src/event-sdk-example-c-find-package $ curl -O https://raw.githubusercontent.com/category-labs/monad/refs/tags/release/exec-events-sdk-v1.1/category/event/example/eventwatch.c ``` ### Step 3: create `CMakeLists.txt` Add a `CMakeLists.txt` file with this content: ```CMakeLists.txt theme={null} cmake_minimum_required(VERSION 3.23) project(eventwatch LANGUAGES C) find_package(monad_exec_events_sdk REQUIRED PATHS /tmp/sdk-install-demo/lib/cmake/category-labs) add_executable(eventwatch eventwatch.c) target_compile_options(eventwatch PRIVATE -Wall -Wextra -Wconversion -Werror) target_link_libraries(eventwatch PRIVATE monad_event) ``` ### Step 4: build and run ```shell theme={null} $ cmake -S . -B build $ cmake --build build $ build/eventwatch -h ``` # Running the example program on live data, and next steps Source: https://docs.monad.xyz/execution-events/getting-started/final If you're following this guide in order, you should have already built one of the example programs (in [C](/execution-events/getting-started/c) or [Rust](/execution-events/getting-started/rust)), ran it with a [snapshot file](/execution-events/getting-started/snapshot), and installed your own local [Monad node](/execution-events/getting-started/setup-node). Now we'll run the example program again, but this time it will print the real-time events published by our local Monad node. ## Running with live data ### Step 1: preparing the Monad node Before running, make sure the execution daemon is running and that [execution events are enabled](/node-ops/events-and-websockets). in particular, make sure you have passed the command line argument `--exec-event-ring` to the execution daemon ### Step 2: run the example program In the snapshot example, we passed the name of the snapshot file to the program as a command line argument. In both the C and Rust example programs, if we do not pass any filename at all, it will use default filename used by the execution daemon, connecting us to the live event stream. * For C, run `eventwatch` with no arguments * For Rust, run the command `cargo run -- -d` You will see similar data to the snapshot case, but as it is being published by execution. If you stop the execution daemon, the example program will detect that the source of data is gone, and exit. ## Next steps This completes the getting started guide! If you're interested in developing your own real-time data processing software with the SDK, where should you go from here? Here is a recommended list of resources, in roughly the order that will be most helpful in developing real applications: 1. If you haven't already, read the [overview](/execution-events/overview) and the source code for the example program you just ran 2. Once you understand the basic ideas in the example, [the rest of the SDK documentation](/execution-events#execution-events-documentation) should be easy to follow 3. Before using the SDK, make sure you understand the [consensus events](/execution-events/consensus-events) and what they mean 4. Try out a more sophisticated program and look at the source code for it * For Rust, try the "Block Explorer" TUI example in the upstream [`monad`](https://github.com/category-labs/monad/tree/release/exec-events-sdk-v1.1/rust/crates/monad-exec-events/examples/explorer.rs) repository. You can run it with `cargo run -p monad-exec-events --example explorer` and then browse the source code in `explorer.rs` * For C, look at the code for the `monad-event-cli` program in the upstream [`monad`](https://github.com/category-labs/monad/tree/release/exec-events-sdk-v1.1/category/event/tools/event-cli) repository; this program is the "tcpdump" of the execution event system, and shows several different uses of the API. You may also want to read the next section about compiling this program ## Optional: build the `monad-event-cli` tool `monad-event-cli` is a useful utility for working with the event system. Like the Rust `eventwatch` example, it can decode execution event payloads into human-readable form. It does several other tasks which are useful in the developer workflow, e.g., recording captures of events and creating snapshot event ring files for test cases. `monad-event-cli` requires gcc 15.2 or higher, and will not build with gcc 15.1. Currently, the only Ubuntu release that ships with gcc 15.2 in its package repositories is Ubuntu 25.10, which was recently released at the time this guide was written. If your Linux distribution does not provide gcc 15.2 and you do not want to install it manually, you can instead use clang-19 (or more recent) but *using libc++ instead of libstdc++*. The default on Linux is for clang to use the gcc C++ standard library (libstdc++). If you specify `-stdlib=libc++` it will use the LLVM standard library instead, which has the needed `` support. You may have to install it, since in some distributions it is not part of the clang package. In Ubuntu, the clang-19 libc++ runtime and development packages will be added when you install `libc++-19-dev`. When using libc++-19, you must also specify the `-fexperimental-library` compiler flag to enable C++20 time zone support in ``; the event CLI tool uses this for printing the event timestamp in local time. In some future version of libc++ this will no longer be needed. To build `monad-event-cli`, you will also need the [CLI11](https://github.com/CLIUtils/CLI11) C++ command-line parser library and the OpenSSL development files. Although it is optional, you should also install the development files for [GNU multiple-precision library](https://gmplib.org/) so that `uint256` values print in decimal form. The instructions also use the [ninja](https://ninja-build.org/) build tool. You can install everything on Ubuntu with: ``` $ sudo apt install libcli11-dev libssl-dev libgmp-dev ninja-build ``` Now clone the [execution repository](https://github.com/category-labs/monad.git) and check out the branch `release/exec-events-sdk-v1.1`, then build the CMake project rooted at `category/event`. Using clang-19 with libc++ and the above options: ```shell theme={null} $ git clone -b release/exec-events-sdk-v1.1 https://github.com/category-labs/monad.git \ ~/src/monad-event-cli $ CC=clang-19 CFLAGS="-march=x86-64-v4" \ CXX=clang++-19 CXXFLAGS="-stdlib=libc++ -fexperimental-library -march=x86-64-v4" cmake \ -S ~/src/monad-event-cli/category/event -B ~/build/monad-event-cli-v1.1-release -G Ninja \ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMONAD_EVENT_BUILD_CLI=ON $ cmake --build ~/build/monad-event-cli-v1.1-release ``` You should now be able to run: ```shell theme={null} $ ~/build/monad-event-cli-v1.1-release/monad-event-cli --help ``` The `-march=x86-64-v4` is needed to enable certain atomic operations at the CPU instruction level, to avoid needing to link with libatomic.a; without this, a performance warning is emitted, which becomes a compilation error due to `-Werror` To simplify running cmake with all these settings, you might want to create a CMake toolchain file instead of using environment variables. To do this, create a file called `clang19-libcxx.cmake` with these contents: ```CMake theme={null} set(CMAKE_C_COMPILER clang-19) set(CMAKE_CXX_COMPILER clang++-19) set(CMAKE_ASM_FLAGS_INIT -march=x86-64-v4) set(CMAKE_C_FLAGS_INIT -march=x86-64-v4) set(CMAKE_CXX_FLAGS_INIT "-march=x86-64-v4 -stdlib=libc++ -fexperimental-library") ``` Now can you run this slightly cleaner command: ```shell theme={null} $ cmake --toolchain -S ~/src/monad-event-cli/category/event \ -B ~/build/monad-event-cli-v1.1-release -G Ninja \ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMONAD_EVENT_BUILD_CLI=ON ``` # Getting started Source: https://docs.monad.xyz/execution-events/getting-started/index In this guide, we will: 1. Compile an example program, which will involve building code with the execution event SDK as a dependency. The SDK is offered for both the C and Rust programming languages. Each language has its own guide, so follow the instructions for your language of choice * [C guide](/execution-events/getting-started/c) * [Rust guide](/execution-events/getting-started/rust) 2. [Run the example program on some historical data](/execution-events/getting-started/snapshot), which prints ASCII representations of execution events to `stdout` 3. [Set up and run our own Monad node](/execution-events/getting-started/setup-node), so that we have a local execution process publishing real-time data 4. [Run the example program again, this time using our Monad node](/execution-events/getting-started/final); this will again print execution events to `stdout`, but this time the source will be real-time data from our local node This guide has been tested on a clean Ubuntu 24.04 LTS install, but should work on any recent Linux distribution, although the names of the required packages might be different. The distribution will need to provide a recent enough C compiler, either `gcc-13` or `clang-19`. The first two steps of the guide, which involve looking at historical data instead of real-time data, will also work on a macOS installation that is configured for software development. This may make it easier for some developers to try out the SDK on a development workstation or laptop, without the need to set up a Linux host first. Unlike the SDK, the Monad node itself only runs on Linux so the later steps of the guide -- which actually consume real-time data -- require a full Linux host running your own Monad node. # Building the Rust example program Source: https://docs.monad.xyz/execution-events/getting-started/rust The execution event SDK is made up of two packages, called `monad-event-ring` and `monad-exec-events`. These are described in more detail in the [Rust API guide](/execution-events/rust-api), but for now it's enough to just know their names. In the future, Category Labs may publish these packages to [crates.io](https://crates.io), but that is not how the SDK is currently distributed. Instead, the user's `Cargo.toml` file declares the upstream source of these dependencies to be a particular release tag of the git repository where the SDK source code is located. Dependencies which are sourced from git rather than crates.io are explained in [this section](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories) of the Cargo Book. ## Where is the Rust SDK source code? The execution event Rust SDK lives in same source code repository as the execution daemon ([here](https://github.com/category-labs/monad)). Most of the code in this repository in written in either C or C++, the languages used by the execution daemon itself. The repository also contains a few Rust wrapper APIs, one of which is the Rust execution events SDK. The top-level `Cargo.toml` file for all the Rust components is located in the `rust/` subdirectory. Many of the functions in the execution events C SDK are reused by Rust, via an FFI interface. The main way this affects you as a Rust user is that you must ensure that a recent enough C compiler is selected when Cargo calls CMake and bindgen from the SDK's [`build.rs` build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html). The C SDK uses some recent C23 language features, and requires either gcc-13 or clang-19. If you run `cc -v` and it reports an older compiler, you will need to set the `CC` environment variable to tell CMake to select a newer compiler. ## Building the example program ### Step 1: install prerequisite development packages You might already have some of these installed, but make sure you have at least the minimum required version (newer versions will probably work, but are not explicitly tested). In addition to a Rust toolchain, you will need: | Requirement | Ubuntu package name | Minimum version | What is it used for? | | ------------ | ------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | C compiler | gcc-13 or clang-19 | see package names | The core event library (`libmonad_event.a`) is written in C, and is used by the Rust library | | C++ compiler | g++-13 or clang-19 | see package names | The optional C++ components are not used by Rust, but the CMake configure step will report an error if it cannot find a C++ compiler | | CMake | cmake | CMake 3.23 | `libmonad_event.a` is built with CMake, via `build.rs` integration with cargo | | zstd library | libzstd-dev | any | Snapshot event ring files are compressed with zstd; this library is needed to decompress them | | libhugetlbfs | libhugetlbfs-dev | any | `libhugetlbfs` is used to locate the default hugetlbfs mount point that holds event ring shared memory files | | libclang | clang-19 | clang-19 | Rust's bindgen requires the a recent version of the libclang library | We will also need git and curl. #### macOS compatiblity Real-time data requires a Linux host (because the execution daemon itself does), but you can compile and run the example program on historical data using macOS. This allows you to "try before you buy" and explore the SDK before going through the trouble of setting up a Monad node on Linux. In this case, you do not need `libhugetlbfs` (which is a Linux-only library), but you will need a recent XCode toolchain, the `libzstd` compression library, and CMake. The last two are not included in the default XCode development tools, so you'll probably want to use [Homebrew](https://brew.sh/) or [MacPorts](https://www.macports.org/) to install these onto your system. As for XCode itself, any version since 16.3 should work, but it has only been tested with version 23.2 (the real requirement is for Apple Clang 17).[^1] [^1]: "Apple Clang" is based on, but different than, vanilla LLVM [Clang](https://clang.llvm.org/). Apple Clang 17 is based on LLVM/Clang 19, thus the discrepancy in the requirements table. #### Dependencies quickstart To install all of the dependenices in one shot on Ubuntu 24.04 or higher, run this command (feel free to use more recent verions, e.g., `clang-20`): ```shell theme={null} $ sudo apt install git curl gcc g++ cmake clang-19 libzstd-dev libhugetlbfs-dev ``` Even if you compile `libmonad_event.a` with gcc, the Rust [bindgen](https://rust-lang.github.io/rust-bindgen/) utility still uses the [libclang](https://clang.llvm.org/docs/Tooling.html#libclang) tool to programmatically generate Rust bindings to C code. Technically you should not need the full clang compiler, just the libclang package, but some users have reported trouble without installing it. The reason you need version 19 (or greater) is that clang-19 was the first version to support enough features from the C23 language standard to be able to compile the SDK. If you see errors that imply that bindgen cannot understand the `constexpr` keyword, then bindgen has automatically selected a libclang version that is too old. If you have multiple libclang versions on the system (the default clang is version 18 on Ubuntu 24.04), installing a newer version may not help, if the underlying problem is that bindgen is selecting the wrong one by default. Later on in this guide (during the `cargo build` step) we'll explain more about how to fix some common problems with libclang version selection. ### Step 2: create a new package and copy the example code into it First, create a new cargo package for the example program: ```shell theme={null} $ cargo new --bin event-sdk-example-rust $ cd event-sdk-example-rust ``` Next, we'll overwrite the default "Hello world" `main.rs` source file with the example program code, downloaded from github: ``` $ curl https://raw.githubusercontent.com/category-labs/monad/refs/tags/release/exec-events-sdk-v1.1/rust/crates/monad-exec-events/examples/eventwatch.rs > src/main.rs ``` ### Step 3: integrating with the SDK packages Create the following `Cargo.toml` file: ```Cargo.toml theme={null} [package] name = "event-sdk-example-rust" version = "0.1.0" edition = "2021" [dependencies] chrono = "0.4.34" clap = { version = "4.2", features = ["derive"] } lazy_static = "1.5.0" [dependencies.monad-event-ring] git = "https://github.com/category-labs/monad" tag = "release/exec-events-sdk-v1.1" [dependencies.monad-exec-events] git = "https://github.com/category-labs/monad" tag = "release/exec-events-sdk-v1.1" ``` ### Step 4: build the project ```shell theme={null} cargo build ``` The first time you build will be *very* slow, because it will fetch the monad repository and all transitive git submodules. None of them are needed for the SDK, but cargo checks them out and there is currently no way to override this (see [this issue](https://github.com/rust-lang/cargo/issues/4247)). Thankfully, the git cache is usually shared by all Cargo projects (in `$HOME/.cargo/git`) so this long download should only happen a single time, not once per project. You may need to tell CMake to use a more recent C compiler than the default one it detects, which you can do using the `CC` environment variable. The example below uses the bash terse syntax for setting environment variables in the scope of the next command to be run: ```shell theme={null} CC=clang-19 cargo build ``` If you encounter any build errors, check out the "trouble-shooting" section after this one. Otherwise, the build should produce a working executable file. Try running it with the `-h` flag to print the help: ```shell theme={null} cargo run -- -h ``` If all was successful, continue on to the [next step in the guide](/execution-events/getting-started/snapshot). ### Trouble-shooting common build errors #### libclang errors The most common source of errors when building is when bindgen selects an outdated libclang version, as explained earlier. This typically appears as either: * An error explicitly mentioning `libclang` OR * A message that includes the text "Unable to generate bindings" Setting the environment variable `CC=clang-19` only influences the compiler that CMake uses to build the C SDK library, `libmonad_event.a`. Namely, it does not the affect the libclang version that is used by bindgen to generate the bindings. `CC` specifies a particular binary program, whereas libclang is a shared library that is dynamically loaded by a `build.rs` build script. They are (for the most part) *not* affected by the same environment variables. There are a number of environment variables that control the behavior of how libclang is located and configured, and they're documented in the "Environment Variables" section of the [Rust libclang bindings library](https://crates.io/crates/clang-sys). Some advice we have found works well: * Setting `LLVM_CONFIG_PATH` to point to the full path to the `llvm-config` binary is the best option; this command bakes in a lot of details about how LLVM was built and installed on the system, to make it easier for users of LLVM (such as bindgen) to find the configuration they need * In some unusual cases, you may select the right libclang but it may be configured strangely, so that it cannot find the basic libc header files anymore (typical symptoms are claims that `stddef.h`, `assert.h`, or `string.h` are missing); you can can figure out the location of these files on the system and pass them as system include directories (the `-isystem` clang option) to libclang through bindgen using the `BINDGEN_EXTRA_CLANG_ARGS` environment variable; on macOS this looks like: ``` BINDGEN_EXTRA_CLANG_ARGS="-isystem $(xcrun --show-sdk-path)/usr/include" ``` and in a typical Linux setup it looks like this: ``` BINDGEN_EXTRA_CLANG_ARGS="-isystem /usr/include" ``` #### zstd library errors A common error with MacPorts on macOS is the inability of the C compiler to find the `zstd.h` header file, or the inability of the linker to find the `libzstd` library. This is because the system C compiler will not search in the `/opt/local` directory hierarchy by default. To fix this, export the `CFLAGS` and `RUSTFLAGS` environment variables as follows: ```shell theme={null} export CFLAGS=-I/opt/local/include export RUSTFLAGS=-L/opt/local/lib ``` Alternatively, you can set `CC` to a MacPorts-provided C compiler, which will know to search there. # Setting up your Monad node Source: https://docs.monad.xyz/execution-events/getting-started/setup-node The execution events SDK relies on a shared memory communication system, in which the node's EVM execution daemon acts a publisher. Thus, in order to use it, you need to (i) run your own Monad node and (ii) run your data processing application on the same host as the node, so it can read the data from shared memory. Follow the guides on the [node operations](/node-ops) page, which cover how to install and configure a full node, and how to reset it when problems occur. You will also need to follow some [extra configuration steps](/node-ops/events-and-websockets) to prepare the node for use with execution events. # Running the example program on snapshot data Source: https://docs.monad.xyz/execution-events/getting-started/snapshot The easiest way to get acquainted with the execution event system is to try out the example program and read the code, although you may want to read the [quick overview](/execution-events/overview) explaining the basic concepts first. If you're following this guide in order you should have already built one of the example programs. If you have not built one yet, choose the appropriate guide for your language or choice (either [C](/execution-events/getting-started/c) or [Rust](/execution-events/getting-started/rust)), and then return to this page. ## Live event rings vs. snapshot event rings The event ring's shared memory data structures typically live inside of a regular file. Any process that wants shared access to an event ring, first locates it via the filesystem, then maps a shared view of it into the process' virtual memory map using the [mmap(2)](https://man7.org/linux/man-pages/man2/mmap.2.html) system call. Event ring files come in two flavors: 1. **"Live" event ring files** -- these are the "normal" event ring files that are the source of real-time data. The whole point of the SDK is to read real-time events from these files, but they're not very convenient for most day-to-day software development tasks. Suppose, for example, you wanted to write a test for your data processing program. The SDK is mostly designed around *reading* events, so to test it with a live event ring, you'd need to write some dummy event publishing code just to have events to read. For execution events, the live event ring file is populated by the execution daemon, which we have not even installed at this point in the tutorial! A lot of development headaches are solved by the second kind of event ring file. 2. **"Snapshot" event ring files** -- these are compressed snapshots taken of a live event ring file as it existed at a particular moment in time. Typically they are "rewound" to the oldest event in the circular event queue, and are used to replay a fixed set of historical execution events. Snapshot files are useful for testing and development workflows, because you do not need to be running an active publisher to use them. Because they're so useful for development, snapshots are the first data source we'll use, before trying the example program on a live node. ## Running the example program on a snapshot file ### Step 1: download a snapshot file Run this command to download a snapshot: ```shell theme={null} $ curl https://raw.githubusercontent.com/category-labs/monad/refs/tags/release/exec-events-sdk-v1.1/rust/crates/monad-exec-events/test/data/exec-events-emn-30b-15m/snapshot.zst > /tmp/exec-events-emn-30b-15m.zst ``` The `emn-30b-15m` part of the filename means "Ethereum mainnet replay for 30 blocks starting after block 15 million". In other words, this contains the execution events emitted during a historical replay of the Ethereum blockchain (chain ID 1), from block 15,000,001 to block 15,000,031. The Category Labs execution daemon is able to execute blocks from Monad blockchains (the EVM chain ID 143 or any of its test networks), but also from other EVM-compatible networks. Historical replay of the Ethereum mainnet is used as an execution "conformance test", to make sure the node software remains as Ethereum compatible as possible. We use an Ethereum chain snapshot in the tutorial under the assumption that many developers are already familiar with the Ethereum ecosystem, but might be new to Monad. You can check that all of the data captured in the snapshot file matches the data published by your favorite Ethereum data provider. For example, you'll be able to check that the data shown here matches what is reported by websites like [Etherscan](https://etherscan.io/). Our example `curl` command placed the snapshot file in `/tmp` for a reason. Although the file can be placed anywhere, we encourage users not to place it in the same directory they are already in, to ensure they won't encounter a confusing error the first time they run the program. If the file is placed in the current working directory, and you specified it as `exec-events-emn-30b-15m.zst`, an error would occur. That error would go away if you instead referred to the file as `./exec-events-emn-30b-15m.zst`. The leading `./` "fixes" the problem in a way you've seen before: when you want to run a command in your UNIX shell which is not on the `$PATH`, you often add a `./` to suppress the default automatic path search. Any `/` character marks the input as an actual file path and not a "command name" to be searched for. A similar thing happens with event ring files, where file inputs without `/` are translated in an automatic way. The file would not be "searched for" in the current directory unless the name contains a `./` to communicate that the input is a path. "Pure" filenames are only searched for in a special directory called the "default event ring directory." The rationale is explained fully in the ["Location of event ring files"](/execution-events/advanced#location-of-event-ring-files) section of the SDK. ### Step 2: run the SDK example program you built previously The command is slightly different for each programming language. For C, run: ```shell theme={null} $ eventwatch /tmp/exec-events-emn-30b-15m.zst ``` For Rust, run: ```shell theme={null} cargo run -- --event-ring-path /tmp/exec-events-emn-30b-15m.zst -d ``` The Rust example program output is more informative than the C output. Both programs "pretty-print" the event descriptor information, but the C example program can only hexdump the event payloads, whereas the Rust program is able to debug-print them, thanks to Rust's `#[derive(Debug)]` feature. The `-d` parameter in the Rust command line tells the program to print this "debug" form. Full pretty-printers *do* exist in the SDK for the C language family, but they are only available for C++ and are based on the standard C++ `` library ### Step 3: analyze the data (Rust only) If you're running the Rust example program -- and this step of the guide assumes you are -- you will see a text dump of all event data. We'll look at a few specific events to give you a sense of what kind of data the SDK produces, and what you can do with it. The first two lines printed by the Rust example program look like this: ``` 16:26:14.354056730 BLOCK_START [2 0x2] SEQ: 1 BLK: 15000001 Payload: BlockStart(monad_exec_block_start { }) ``` Let's break down the first line: * `16:26:14.354056730` -- this is the nanosecond-resolution timestamp when the original event was recorded; since we're looking at a snapshot and not live data, this will always be the same number, and it's from a long time ago; the actual "date" portion of the timestamp is omitted when we print it, since the typical use-case for the SDK is for real-time data (where the date is usually "today") * `BLOCK_START` - this is the type of the event that occurred inside of the EVM; a `BLOCK_START` event is recorded when a new block is first seen by the execution daemon, and its payload describes all the execution inputs that are known at the *start* of execution processing; this mostly corresponds to the fields in the Ethereum block header which are known prior to execution * `[2 0x2]` - this is the numerical code that corresponds to the `BLOCK_START` event type, in both decimal and hexidecimal * `SEQ: 1` - the sequence number (a monotonic counter of the number of events published so far) is 1; in a live event ring, these are used for gap / overwrite detection * `BLK: 15000001` - this event is part of block number 15,000,001 The second line is produced by this Rust statement: ```rust theme={null} println!("Payload: {exec_event:x?}"); ``` Because it's a very long line (there is no line-wrapping in Rust's `#[derive(Debug)]` output) it was abbreviated in our example output text. We'll look at parts of it in a moment, but we'll pause here to explain some things about this `println!("Payload: {exec_event:x?}")` statement. `exec_event` is a value of Rust enum type `ExecEvent`. Here is how that enum is defined: ```rust theme={null} pub enum ExecEvent { RecordError(monad_event_record_error), BlockStart(monad_exec_block_start), BlockReject(monad_exec_block_reject), BlockPerfEvmEnter, BlockPerfEvmExit, BlockEnd(monad_exec_block_end), BlockQC(monad_exec_block_qc), BlockFinalized(monad_exec_block_finalized), BlockVerified(monad_exec_block_verified), TxnHeaderStart { txn_index: usize, txn_header_start: monad_exec_txn_header_start, data_bytes: Box<[u8]>, blob_bytes: Box<[u8]>, }, // ... more enum variants follow, full definition not shown } ``` * The debug output starts with `BlockStart(...)`, so `exec_event` has the `ExecEvent::BlockStart` enum variant * It seems like we already knew that from the earlier `BLOCK_START [2 0x2]` print-out, but there's a subtle difference. The first line prints information found in the *event descriptor*, which is like a header containing the the common fields of an event. At the point in the program where the descriptor line is printed, it has not yet decoded the event payload to construct the `exec_event` variant. Suppose we were only interested in block 15,000,002. In that case, we could look at just the descriptor, notice it relates to block 15,000,001, and skip over this event (and all other events for that block), i.e., we would not bother decoding it * The value associated with an `ExecEvent::BlockStart` variant if of type `struct monad_exec_block_start`; notice that this type does *not* follow the normal Rust code-formatting style: it uses `lower_case_snake_case` instead of `UpperCamelCase` and has a seemingly-unnecessary prefix (all the variant value types start with `monad_exec_`). This is because the payload types are defined as C language structures, and their Rust equivalents are generated using bindgen. The C-style spelling helps indicate that. The definition of `monad_exec_block_start` comes from the C header file `exec_event_ctypes.h`, where it is defined like this: ```c theme={null} /// Event recorded at the start of EVM execution struct monad_exec_block_start { struct monad_exec_block_tag block_tag; ///< Proposal is for this block uint64_t round; ///< Round when block was proposed uint64_t epoch; ///< Epoch when block was proposed __uint128_t proposal_epoch_nanos; ///< UNIX epoch nanosecond timestamp monad_c_uint256_ne chain_id; ///< Blockchain we're associated with struct monad_c_secp256k1_pubkey author; ///< Public key of block author monad_c_bytes32 parent_eth_hash; ///< Hash of Ethereum parent block struct monad_c_eth_block_input eth_block_input; ///< Ethereum execution inputs struct monad_c_native_block_input monad_block_input; ///< Monad execution inputs }; ``` The Ethereum execution inputs field `eth_block_input` is the field that corresponds to the parts of the Ethereum block header which are known at the start of execution. Some of this output is difficult to read, since Rust's `#[derive(Debug)]` is meant for ease of debugging and doesn't always "pretty-print" data in the best way for readability. Other fields are clear though, for example, the `gas_limit` of the block is shown as a hexidecimal value: ``` monad_c_eth_block_input { gas_limit: 1c9c380 <...not shown> } ``` `0x1c9c380` corresponds to the decimal number `30,000,000`, a number we expect to see for a mainnet Ethereum gas limit. Real pretty-printing of events is done with a developer tool called `monad-event-cli`, which is part of the SDK. This example is meant to be as simple and short as possible, to help with learning the API. When debugging real event programs, you will probably prefer developer tools like the event CLI tool. The build instructions for it are in the final step of the "Getting start" guide [(here)](/execution-events/getting-started/final#optional-build-the-monad-event-cli-tool). Now let's look for something a little more interesting, to get a sense of a what a real SDK consumer might do with this data. If you search the output for the string `TXN_EVM_OUTPUT`, the first match will be this event (with some formatting differences): ``` 16:26:14.376725676 TXN_EVM_OUTPUT [17 0x11] SEQ: 236 BLK: 15000001 TXN: 0 Payload: TxnEvmOutput { txn_index: 0, output: monad_exec_txn_evm_output { receipt: monad_c_eth_txn_receipt { status: false, log_count: 0, gas_used: 765c }, call_frame_count: 1 } } ``` This is the first event that describes the output of transaction zero in block 15,000,001 -- note the `TXN: 0` in the descriptor and `txn_index: 0` in the payload. We say "first event" because the output for any particular transaction usually spans *several* events: each log, call frame, state change, and state access is recorded as a separate event. The first event is always of type `TXN_EVM_OUTPUT`. It contains a basic summary of what happened, and an indication of how many more output-related events will follow. You can see that this particular transaction emitted zero logs, and one call frame trace. The call frame information is recorded in the next event, on the line below this one. As it turns out, the very first transaction is also somewhat interesting: it failed to execute after using 30,300 gas (0x765c). The transaction's failure is recorded by `status` field. As you can see, it is set to `false`. Why did it fail? To figure it out, we'll use the information in the `TXN_CALL_FRAME` event that follows this one. The `evmc_status_code` field in that event has the value `2`, which is the numeric value of the [`EVMC_REVERT`](https://github.com/ipsilon/evmc/blob/496ce0f81058378b72d0b592d1c49b935bce3302/include/evmc/evmc.h#L298) status code. This tells us that the revert was requested by the contract code itself, i.e., it executed a [`REVERT`](https://www.evm.codes/?fork=osaka#fd) instruction. In other words, this was not a VM-initiated exceptional halt such as "out of gas" or "illegal instruction, but something the contract itself decided to do. Because this is a Solidity contract, we can decode richer error information from the call frame. The `REVERT` instruction can pass arbitrary-length return data back to the caller. This return data is recorded in the call frame, in the `return_bytes` array. Observe that the first 4 bytes of `return_bytes` are `0x8c379a0`. This is how Solidity represents a revert that carries a string explanation. The details of how this string is encoded is [here](https://docs.soliditylang.org/en/v0.8.21/control-structures.html#revert), but the upshot is that we can decode the last 32 bytes of this `return_bytes` array as an ASCII string. If you try this yourself, you'll discover that it says: ``` Ownable: caller is not the owner ``` This error string ultimately comes from [here](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.0/contracts/access/Ownable.sol#L43), in OpenZeppelin's abstract "Ownable" contract. This was used as a third-party library in the implementation of this smart contract, to provide some simple access controls. In an earlier event (called `TXN_HEADER_START`) we can find the transaction's Keccak hash, which is `0xaedb8ef26125d8ad6e0c5f19fc9cbdd7f4a42eb82de88686b39090b8abcfeb8f`. If we look up information about this transaction on [Etherscan](https://etherscan.io/tx/0xaedb8ef26125d8ad6e0c5f19fc9cbdd7f4a42eb82de88686b39090b8abcfeb8f), using the hash, we can see that Etherscan agrees. The `Status:` field reads: ``` Fail with error 'Ownable: caller is not the owner' ``` Feel free to double-check this result using your favorite tool for exploring Ethereum mainnet data! ### Step 4: Learn how it works The source code for the example program you just ran has a lot of comments, and it is designed to teach you how to use the API. The best way to learn about the SDK is to read through it, but if you haven't read the [overview](/execution-events/overview) yet, you may want to do that first. You can either do that now, or continue on to the next step, where we'll install our own local [Monad node](/execution-events/getting-started/setup-node). Once we have our own node, we can run this same example program but make it consume real-time Monad blockchain data instead of snapshot data. # Execution Events Source: https://docs.monad.xyz/execution-events/index The **Execution Events** system allows developers to build high-performance applications that receive lowest-latency event data from a Monad node via a shared memory queue. In addition to low-latency applications, this system is also needed by programs that process very large *volumes* of blockchain data. To consume this real-time data, you write some data processing software in C, C++, or Rust using the software development kit described on this page, and run it on a host running the [Monad node software](/node-ops) built by Category Labs. This would be overkill for simple data processing use cases; see the [alternatives section](#alternatives-to-execution-events) for more convenient ways to consume Monad blockchain data. For comparisons to other systems such as Reth's ExEx or Solana Geyser, see the [comparisons](#comparisons-with-other-data-systems) section. ## Do I need execution events? If you're coming to Monad from other EVM blockchains, you might find your data processing is "not able to keep up" with the rate of data generated on Monad. This is because Monad is a fast blockchain, in two different dimensions: * New blocks are proposed every 400 milliseconds, or equivalently about 150 blocks every minute. * A single block can contain many more transactions compared to other blockchains; at peak processing rates, Monad can execute blocks containing thousands of transactions, and it was designed to sustain execution rates approaching 10,000 transactions per second (TPS). The popular [JSON-RPC data access APIs](/reference) were designed for the original Ethereum blockchain, which rarely exceeds 50 TPS. If you need a lot of data, or you need the most recent data very quickly, JSON-RPC simply won't scale. Data streaming over [WebSockets](/reference/websockets) can sometimes help, but not all data is published over WebSockets. If you're not able to keep up, you have two options: 1. *Build more sophisticated processing yourself* - you can use the execution events SDK to build the lowest-latency, highest-throughput data consumer possible. 2. *Use a third-party data service* - if you are not comfortable with systems programming (low-level backend programming in the C, C++, or Rust programming langauges) or if you simply don't have the time or interest, there are many third-party providers which provide data services for Monad. Typically these services will use the execution events SDK themselves, and then somehow filter, digest, or enrich the data so that you can get what you need more efficiently than the traditional JSON-RPC. Check out the [Tooling and Infrastructure](/tooling-and-infra) page for links to popular providers, particularly the [indexers](/tooling-and-infra/indexers) section although other services like [block explorers](/tooling-and-infra/block-explorers) sometimes provide data access APIs that might offer you what you need. ## What are "execution events"? The Category Labs [execution daemon](https://github.com/category-labs/monad/blob/main/docs/overview.md) contains a shared-memory communication system that publishes data about most actions taken by the EVM during transaction execution. The raw binary records of these EVM actions are called "execution events". Third-party applications that need the highest performance can run on the same host as the node software, and directly consume the execution event records from shared memory. To read this data, your third-party application calls functions in the execution event SDK, our real-time data library. The Solidity programming language also has a feature called [events](https://docs.soliditylang.org/en/latest/contracts.html#events). These are not the same thing as execution events. Solidity events are a programming-language-level abstraction of the Ethereum Virtual Machine's [low-level logging opcodes](https://www.evm.codes/?fork=prague#a0). The execution events system *does* record all log events, but it also records information about other things that happen during transaction execution, such as calls to other contracts, a list of accounts that were accessed by a transaction, and more. ## Execution events documentation * [Release notes](/execution-events/release-notes) - see what's new in the latest release of the SDK * [Getting started](/execution-events/getting-started) - describes how to build and run a simple example program * [Events overview](/execution-events/overview) - explains the core concepts in the execution events system * [Event rings in detail](/execution-events/event-ring) - documents event ring files and protocol versioning * API documentation - overview of our programming libraries, which are provided for several programming languages * [C API](/execution-events/c-api) * [Rust API](/execution-events/rust-api) * [Consensus events](/execution-events/consensus-events) - execution publishes some information from consensus that is essential for understanding real-time data * [Advanced topics](/execution-events/advanced) - documentation for advanced users and for software developers who contribute to the execution source code ## Alternatives to execution events Category Labs' node software includes an RPC server component. The RPC server supports two easier ways to read blockchain data: 1. The typical [JSON RPC](/reference) endpoints supported by most EVM-compatible blockchain nodes (e.g., Geth) 2. The Geth [real-time events](https://geth.ethereum.org/docs/interacting-with-geth/rpc/pubsub) WebSocket protocol (i.e., `eth_subscribe`) is also supported, along with some Monad-specific extensions for better performance; see the [WebSocket guide](/reference/websockets) for more information Both of these access methods are standardized across EVM-compatible blockchains and are simpler to use than execution events. The execution events system is designed for specialized applications, such as running an indexer platform or applications that need the lowest latency possible (e.g., market making). It is also where the RPC server itself gets its real-time data. ## Comparisons with other data systems A brief comparison with low latency systems in other blockchain software: * **Geth Live Tracing** [(link)](https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing) - "hook" based API: your code is loaded into the Geth node as a plugin, and is run synchronously (via callbacks) during execution * **Reth ExEx** [(link)](https://www.paradigm.xyz/2024/05/reth-exex) and [(link)](https://reth.rs/exex/overview) - async function based API: your code is loaded into a Reth node; execution sees events after the fact rather than synchronously * **Solana Geyser** [(link)](https://www.helius.dev/blog/solana-geyser-plugins-streaming-data-at-the-speed-of-light) - "hook" based API, a plugin that runs inside a Solana validator and invokes callbacks during execution All three of these are different from the Execution Events approach. In our approach: * You are seeing events "as-they-happen", as in the Geth Live Tracer and Solana Geyser. Unlike these approaches, your code is not running as a plugin inside the execution engine, but in parallel (about one microsecond later) in a separate process * Like the Geth Live Tracer (but unlike Reth's ExEx) you see each "piece" of the transaction -- each log, each balance change, etc. -- as a separate event * Unlike the Geth Live Tracer or Geyser, you do not install "hooks" and receive callbacks; instead you continuously poll for new event records, iterating through any new events that are returned to you (and ignoring events that you are not interested in) * Because the system is based on shared memory ring buffers, you can lose data if your consumer is too slow -- you must keep up! # Execution events overview Source: https://docs.monad.xyz/execution-events/overview The execution daemon includes a system for recording events that occur during transaction processing. An "execution event" is a notification that the EVM has performed some action, such as "an account balance has been updated" or "a new block has started executing." These EVM events can be observed by external third-party applications, using a high-performance inter-process communication (IPC) channel. The execution daemon publishes event data to shared memory, and external applications read from this same shared memory region to observe the events. Your application can read events using the C library `libmonad_event` or the Rust package `monad-exec-events`. This page provides an overview of the basic concepts used in both the C and Rust APIs. ## Event rings vs. execution events Although the real-time data system and its SDK are often called "execution events," there are two different parts of the SDK: 1. **Event ring API** - "event ring" is the name of a shared memory data structure and the API for reading and writing to it. Event rings are a general purpose, IPC broadcast utility for publishing events to any number of reading processes. The event ring API works with *unstructured* I/O: like the UNIX [`read(2)`](https://pubs.opengroup.org/onlinepubs/009604599/functions/read.html) and [`write(2)`](https://pubs.opengroup.org/onlinepubs/009695099/functions/write.html) file I/O system calls, the event ring API sees all data as raw byte arrays 2. **Execution event definitions** - the actual "execution events" are the standardized binary formats that the execution daemons writes to represent particular EVM actions. It can be thought of as a protocol, a schema, or a serialization format. Continuing the analogy, if the event ring API is like the UNIX `read(2)` and `write(2)` file APIs, then "execution events" are like a "file format" that defines what a particular file contains In the Rust SDK, these two parts are in different packages: `monad-event-ring` and `monad-exec-events`. The C SDK is a single library, but the header files for the two different parts live in different directories: the event ring headers live in the `category/core/event` subdirectory, and the execution event files live in `category/execution/ethereum`. ## Event ring basics ### What is an event? Events are made up of two components: 1. The *event descriptor* is a fixed-size (currently 64 byte) object describing the common fields of an event that has happened. It contains the event's type, a sequence number, a timestamp, and some internal book-keeping information 2. The *event payload* is a variably-sized piece of extra data about the event, which is specific to the event type. For example, a "transaction log" event describes a single EVM log record emitted by a transaction. While the descriptor tells us the event's type (i.e., that it is "log event"), the payload tells us all the details: the contract address, the log topics, and the log data. Some of the fields in the event descriptor not already mentioned are used to communicate where in shared memory the payload bytes are located, and the payload's length Remember that at the event ring API level, an event payload is just an unstructured byte buffer; the reader must know the format of what they are reading, and interpret it accordingly ### Where do events live? When an event occurs, an event descriptor is written into a ring buffer that lives in a shared memory segment. This ring buffer is the "event descriptor array" in the diagram below. Event payloads are stored in a different array (in a separate shared memory segment) called the "payload buffer." ``` ╔═Event descriptor array══════════════...═════════════════════════════════════╗ ║ ║ ║ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ║ ║ │ Event │ │ Event │ │ Event │ │░░░░░░░░░░░░░░░│ ║ ║ │ descriptor │ │ descriptor │ │ descriptor │ │░░░░ empty ░░░░│ ║ ║ │ 1 │ │ 2 │ │ N │ │░░░░░░░░░░░░░░░│ ║ ║ └┬──────────────┘ └┬──────────────┘ └┬──────────────┘ └───────────────┘ ║ ╚══╬═════════════════╬════════════════...══╬══════════════════════════════════╝ │ │ │ │ │ │ │ ┌───────┘ └─┐ │ │ │ │ │ │ ╔═╬═════════╬═══════════════════════════...═╬═══════════════════════════════╗ ║ │ │ │ ║ ║ ▼───────┐ ▼─────────────────────────┐ ▼─────────────┐ ┌─────────────┐ ║ ║ │Event 1│ │ Event 2 │ │ Event N │ │░░░░free░░░░░│ ║ ║ │payload│ │ payload │ │ payload │ │░░░░space░░░░│ ║ ║ └───────┘ └─────────────────────────┘ └─────────────┘ └─────────────┘ ║ ╚═Payload buffer════════════════════════...═════════════════════════════════╝ ``` Keep in mind that real event payloads are typically much larger (in terms of number of bytes) than the event descriptors, even though they don't appear that way in this simple diagram. The diagram is primarily trying to show that: * Event descriptors are *fixed-size* and event payloads are *variably-sized* * An event descriptor refers / "points to" the location of its payload * Event descriptors and payloads live in different contiguous arrays of shared memory Although there are two different ring buffers in this system -- the descriptor array and payload byte buffer -- we call the entire combined data structure an "event ring." A few properties about the style of communication chosen: * It supports *broadcast* semantics: multiple readers may read from the event ring simultaneously, and each reader maintains its own iterator position within the ring * As in typical broadcast protocols, the writer is not aware of the readers -- events are written regardless of whether anyone is reading them or not. Because the writer does not even know what the readers are doing, it cannot wait for a reader if it is slow. Readers must iterate through events quickly, or events will be lost: descriptor and payload memory can be overwritten by later events. Conceptually the event sequence is a *queue* (it has FIFO semantics) but is it called a *ring* to emphasize its overwrite-upon-overflow semantics * A sequence number is included in the event descriptor to detect gaps (missing events due to slow readers), and a similar strategy is used to detect when payload buffer contents are overwritten ## Execution event basics As mentioned, the event ring API works with unstructured I/O. This API does not understand what the data means: it knows about bytes, but does not know anything about blocks, transactions, etc. When working with a particular event ring, the reader interprets the bytes assuming they have some known format. The primary format used with the SDK are the *execution* events: the binary format that records what is happening in the EVM in real time, during the execution of proposed blocks on the Monad blockchain. There are a few other kinds of formats (called "content types"), but they are only used internally by Category Labs, mostly for performance profiling. For the remainder of the overview, we'll look at an example execution event. ### Example: the "transaction start" event One particularly important kind of event is the "start of transaction header" event, which is recorded shortly after a new transaction is decoded by the EVM. It contains most of the transaction information (encoded as a C structure) as its event payload. The payload structure is defined in `exec_event_ctypes.h` as: ```c theme={null} /// First event recorded when transaction processing starts struct monad_exec_txn_header_start { monad_c_bytes32 txn_hash; ///< Keccak hash of transaction RLP monad_c_address sender; ///< Recovered sender address struct monad_c_eth_txn_header txn_header; ///< Transaction header }; ``` The nested `monad_c_eth_txn_header` structure contains most of the interesting information -- it is defined in `eth_ctypes.h` as follows: ```c theme={null} /// Fields of an Ethereum transaction that are recognized by the monad EVM /// implementation. /// /// This type contains the fixed-size fields present in any supported /// transaction type. If a transaction type does not support a particular field, /// it will be zero-initialized. struct monad_c_eth_txn_header { enum monad_c_transaction_type txn_type; ///< EIP-2718 transaction type monad_c_uint256_ne chain_id; ///< T_c: EIP-155 blockchain identifier uint64_t nonce; ///< T_n: num txns sent by this sender uint64_t gas_limit; ///< T_g: max usable gas (upfront xfer) monad_c_uint256_ne max_fee_per_gas; ///< T_m in EIP-1559 txns or T_p (gasPrice) monad_c_uint256_ne max_priority_fee_per_gas; ///< T_f in EIP-1559 txns, 0 otherwise monad_c_uint256_ne value; ///< T_v: wei xfered or contract endowment monad_c_address to; ///< T_t: recipient bool is_contract_creation; ///< True -> interpret T_t == 0 as null monad_c_uint256_ne r; ///< T_r: r value of ECDSA signature monad_c_uint256_ne s; ///< T_s: s value of ECDSA signature bool y_parity; ///< Signature Y parity (see YP App. F) monad_c_uint256_ne max_fee_per_blob_gas; ///< EIP-4844 contribution to max fee uint32_t data_length; ///< Length of trailing `data` array uint32_t blob_versioned_hash_length; ///< Length of trailing `blob_versioned_hashes` array uint32_t access_list_count; ///< # of EIP-2930 AccessList entries uint32_t auth_list_count; ///< # of EIP-7702 AuthorizationList entries }; ``` The formal nomenclature in the comments (e.g., `T_n` and `T_c`) are references to variable names in the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). The type `monad_c_uint256_ne` ("native endian") is a 256-bit integer that is stored as a `uint64_t[4]` in the [limb format](https://gmplib.org/manual/Integer-Internals) used by most "big integer" libraries that have good performance. If you are using the Rust SDK, `struct` types with the same names (and the same binary layouts, courtesy of a `#[repr(C)]` attribute) are generated by [bindgen](https://docs.rs/bindgen/latest/bindgen/) when the `monad-exec-events` package is built. The defining characteristic of the execution event payloads is that they rely on the "natural" interoperability of simple C data structures across programming languages. Most popular programming languages have a defined foreign function interface for working with C code, and this usually also entails some way to "naturally" work with C structure types. Although C's data representation is not portable, these objects live in shared memory, therefore both the reader and writer must be on the same host, and must follow the same C ABI. ### Variable-length trailing arrays and subsequent events For a particular "start of transaction" event, *most* of the event payload will be the low-level byte representation of a `struct monad_exec_txn_header_start` value. Almost always, there will be some extra data in the payload byte array *following* this structure: * The transaction's variably-sized `data` byte array, whose length is specified by the `data_length` field, is also part of the event payload and immediately follows the `struct monad_exec_txn_header_start` object * If this is an EIP-4844 transaction, a `blob_versioned_hashes` array will immediately follow the `data` array Both of these are examples of "variable-length trailing" (VLT) array payload data; "trailing" means a simple variable-length array is recorded after the fixed-size payload structure which (among other things) must contain a field that describes length of that array; if there is more than VLT array, they are recorded in the same order that their corresponding `_length` fields are listed in the fixed-size structure. The EIP-2930 and EIP-7702 lists are also variable-length items in a transaction, but they are *not* recorded in the payload of the "start of transaction header" event. Instead of being recording in trailing arrays, a unique event will be recorded for each EIP-2930 access list entry and each EIP-7702 authorization tuple. The number of these events *is* published in the "start of transaction header" event payload (see the `access_list_count` and `auth_list_count` fields), so that the reader will know how many more events to expect. ### Execution event properties in the descriptor So far we've talked about the payload for a "start of transaction" event, but the common properties of the event are recorded directly in the event descriptor. Most importantly, these include the numeric code that identifies the type of event, so we know we're supposed to interpret the unstructured payload bytes as a `struct monad_exec_txn_header_start` in the first place. An event descriptor is defined this way: ```c theme={null} struct monad_event_descriptor { alignas(64) uint64_t seqno; ///< Sequence number, for gap/liveness check uint16_t event_type; ///< What kind of event this is uint16_t : 16; ///< Unused tail padding uint32_t payload_size; ///< Size of event payload uint64_t record_epoch_nanos; ///< Time event was recorded uint64_t payload_buf_offset; ///< Unwrapped offset of payload in p. buf uint64_t content_ext[4]; ///< Extensions for particular content types }; ``` For a "start of transaction header" event, the `event_type` field will be set to the value of the C enumeration constant `MONAD_EXEC_TXN_HEADER_START`, a value of type `enum monad_exec_event_type`. This tells the user that it is appropriate to cast the `const uint8_t *` pointing to the start of the event payload to a `const struct monad_event_txn_header_start *` (or to perform the corresponding `unsafe` cast in Rust). All the C enumeration constants start with a `MONAD_EXEC_` prefix, but typically the documentation refers to event types without the prefix, e.g., `TXN_HEADER_START`. Note that the transaction number is not included in the payload structure. Because of their importance in the blockchain protocol, transaction numbers are encoded directly in the event descriptor. At a low level (i.e., in the C API) this information is encoded in the `context_ext[1]` field. In the Rust API -- which is a bit more user friendly -- it is decoded and presented as the structure field `ExecEventRingFlowInfo::txn_idx`.[^1] [^1]: This encoding and the rationale for storing it in the descriptor is described elsewhere in the documentation, in the section describing [flow tags](/execution-events/event-ring#flow-tags-the-content_ext-fields-in-execution-event-rings)). The potential presence of subsequent events with EIP-2930 and EIP-7702 information is why `TXN_HEADER_START` is called the *start* of the transaction header. A corresponding event called `TXN_HEADER_END` is emitted after all the transaction header information has been seen. `TXN_HEADER_END` has no payload, and only serves to announce that all events related to the transaction input have been recorded. Such an event is called a "marker event" in the documentation. Finally, the reason it is called a "header" in the first place, is that there are many more events related to transactions. The various "transaction header" events only describe all the inputs that were in the block. Most of the events describe transaction *outputs*: the logs, the call frames, the state changes, and the receipt. ### Example in-memory layout The following diagram illustrates everything explained above about a transaction header's variable-length trailing arrays, related subsequent events, and its terminating marker event. This example transaction has two accounts in its EIP-2930 access list, and no EIP-7702 entries. Each address in an EIP-2930 list records a separate `TXN_ACCESS_LIST_ENTRY` event, with a variable-length trailing array of potentially-accessed storage keys. ``` ╔═Payload buffer══════════════════════════════╗ ║ ║ ║ ┏━━━━━━━TXN_HEADER_START payload━━━━━━━━┓ ║ ║ ┃░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░┃ ║ ┌───╬──╋─▶─monad_exec_txn_header_start───────┐░┃ ║ │ ║ ┃░│ │░┃ ║ │ ║ ┃░│ monad_c_bytes32 txn_hash; │░┃ ║ │ ║ ┃░│ monad_c_address sender; │░┃ ║ │ ║ ┃░│ struct monad_c_eth_txn_header │░┃ ║ ╔═Event descriptor array════╗ │ ║ ┃░│ txn_header; │░┃ ║ ║ ║ │ ║ ┃░├───────────────────────────────────┤░┃ ║ ║ ┌───────────────────────┐ ║ │ ║ ┃░│ │░┃ ║ ║ │ seqno: 1 □─╬───┘ ║ ┃░│ Transaction data variable │░┃ ║ ║ │ TXN_HEADER_START │ ║ ║ ┃░│ length trailing array │░┃ ║ ║ └───────────────────────┘ ║ ║ ┃░│ │░┃ ║ ║ ║ ║ ┃░│ │░┃ ║ ║ ┌───────────────────────┐ ║ ║ ┃░└───────────────────────────────────┘░┃ ║ ║ │ seqno: 2 □─╬────┐ ║ ┃░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░┃ ║ ║ │ TXN_ACCESS_LIST_ENTRY │ ║ │ ║ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║ ║ └───────────────────────┘ ║ │ ║ ║ ║ ║ │ ║ ┏━━━━━TXN_ACCESS_LIST_ENTRY payload━━━━━┓ ║ ║ ┌───────────────────────┐ ║ │ ║ ┃░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░┃ ║ ║ │ seqno: 3 │ ║ └──╬──╋─▶─monad_exec_txn_access_list_entry──┐░┃ ║ ║ │ TXN_ACCESS_LIST_ENTRY □─╬────┐ ║ ┃░│ │░┃ ║ ║ └───────────────────────┘ ║ │ ║ ┃░│ uint32_t index; │░┃ ║ ║ ║ │ ║ ┃░│ struct monad_c_access_list_entry │░┃ ║ ║ ┌───────────────────────┐ ║ │ ║ ┃░│ entry; │░┃ ║ ║ │ seqno: 4 │ ║ │ ║ ┃░├───────────────────────────────────┤░┃ ║ ║ │ TXN_HEADER_END │ ║ │ ║ ┃░│ Storage key variable │░┃ ║ ║ └───────────────────────┘ ║ │ ║ ┃░│ length trailing array │░┃ ║ ║ ║ │ ║ ┃░└───────────────────────────────────┘░┃ ║ ║ ║ │ ║ ┃░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░┃ ║ ║ ║ │ ║ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║ ║ ║ │ ║ ║ ║ ║ │ ║ ┏━━━━━TXN_ACCESS_LIST_ENTRY payload━━━━━┓ ║ ║ ║ │ ║ ┃░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░┃ ║ ║ ║ └──╬──╋─▶─monad_exec_txn_access_list_entry──┐░┃ ║ ║ ║ ║ ┃░│ │░┃ ║ ║ ║ ║ ┃░│ uint32_t index; │░┃ ║ ╚═══════════════════════════╝ ║ ┃░│ struct monad_c_access_list_entry │░┃ ║ ║ ┃░│ entry; │░┃ ║ ║ ┃░├───────────────────────────────────┤░┃ ║ ║ ┃░│ │░┃ ║ ║ ┃░│ Storage key variable │░┃ ║ ║ ┃░│ length trailing array │░┃ ║ ║ ┃░│ (this has more storage │░┃ ║ ║ ┃░│ keys and is larger) │░┃ ║ ║ ┃░│ │░┃ ║ ║ ┃░└───────────────────────────────────┘░┃ ║ ║ ┃░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░┃ ║ ║ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║ ║ ║ . . . . . . ║ ║ ╚═════════════════════════════════════════════╝ ``` ### Patterns in execution event serialization Why are EIP-2930 entries recorded as separate `TXN_ACCESS_LIST_ENTRY` events instead of as variable-length trailing arrays in `TXN_HEADER_START`? Because there are two levels of variable-length information involved. There are a variable number of EIP-2930 accounts, and then for each account, a variable-length number of associated storage keys. The event serialization protocol tries to be very simple: the only time a variable length trailing array will be recorded is when the array *element* type is fixed size. In particular, data that is shaped like a ["jagged array"](https://en.wikipedia.org/wiki/Jagged_array) is not permitted in an event payload. Whenever there are multiple dimensions to the variability, they are "factored out" by using more distinct events. The trade-off is between fewer events with a more complex encoding, vs. more events which "unfold" the data into a "flatter" shape. The latter choice fits better with the "zero-copy C ABI" data model.[^2] [^2]: The natural C encoding of jagged arrays requires an array of pointers. This can't work in a shared memory structure unless we explicitly control the address in virtual address space where the event ring file is mapped, e.g., with `MAP_FIXED`. Technically, the EIP-7702 authorization list *could* be represented as a variable-length trailing array, since the authorization tuples are fixed-size. However, as a design decision, variable-length trailing arrays are only allowed to have simple element types like `u8` or `uint256`, and there cannot be too many of them. The decoding logic of VLT arrays tends to be error prone; it looks confusing because it is harder to "see" in the code exactly what the serialization rules are. "Unfolding" the data into more events is more self-documenting: distinct typed objects are created rather than relying on implicit parsing rules for reinterpreting unstructured trailing data. Consequently, VLT arrays are only used when their use seems "obvious", e.g., the storage key arrays in each `EIP-2930` access list entry. # Release notes Source: https://docs.monad.xyz/execution-events/release-notes ### v1.1 * The execution events Rust SDK was moved from the [consensus repository](https://github.com/category-labs/monad-bft) to the [execution repository](https://github.com/category-labs/monad) * The `eventcap` program was renamed to `monad-event-cli` * General improvements to the documentation ### v1.0 Initial release of the execution event SDK # Rust API Source: https://docs.monad.xyz/execution-events/rust-api ## Modules The Rust execution events API is split across two library packages: 1. **`monad-event-ring`** - this package provides the core event ring functionality. Recall that event rings are a generic broadcast utility based on shared memory communication, and are agnostic about what kind of event data they contain. Consequently, this package does *not* include the definitions of the execution event types (nor any other event types) 2. **`monad-exec-events`** - the execution event data types are defined in this library, along with some helpful utilities for writing real-time data applications These libraries fit together in a more structured way than they do in C. In the C API, the event ring API works with unstructured data, e.g., event numerical codes are `uint16_t` values and event payloads are raw byte arrays. The reader performs unchecked type casts to reinterpret the meaning of those bytes. There are some [safety mechanisms](/execution-events/event-ring#binary-schema-versioning-the-schema_hash-field) to check if an event ring file appears to contain the right kind of data, but the content types are not strongly represented in the type system. In the Rust API, the event ring is not just "generic" in the general sense of the word; it is a literal generic type: ```rust theme={null} struct EventRing ``` Event rings are explicitly parameterized by a "decoder". The decoder knows how to interpret the raw bytes for a particular event content type, e.g., execution events. ## Core concepts ### Event enumeration type Consider how decoding works in the C API: it's typically a "giant switch statement" pattern, where we examine the numerical code of an event and reinterpret the raw bytes as the appropriate payload type via an unchecked type cast: ```c theme={null} const void *payload = monad_event_ring_payload_peek(&exec_ring, &event); switch (event.event_type) { case BLOCK_START: handle_block_start((const struct monad_exec_block_start *)payload); break; case BLOCK_END: handle_block_end((const struct monad_exec_block_end *)payload); break; // ... more event types handled here } ``` The Rust way of expressing this is to use an `enum` type: the different kinds of event payloads become the enum's variants, and the `switch` logic is replaced by a more-powerful `match`. In Rust, decoding produces a value of enumeration type `ExecEvent`, which is defined like this: ```rust theme={null} #[derive(Clone, Debug)] pub enum ExecEvent { BlockStart(monad_exec_block_start), BlockReject(monad_exec_block_reject), BlockEnd(monad_exec_block_end), // more variants follow ``` Notice that each variant of `ExecEvent` holds a value whose type name resembles the C event payload structures. For example, `struct monad_exec_block_start` is the event payload structure definition in the C API. It's recorded when a new block starts, and is defined in the file `exec_event_ctypes.h`. The use of these exact same C structure names -- including the `monad_exec` prefix and lower-case, snake-case spelling -- is designed to alert you to the fact that the payload types have *exactly* the same in-memory representation as their C API counterparts. They are generated by [bindgen](https://rust-lang.github.io/rust-bindgen/) and are layout-compatible (via a `#[repr(C)]` attribute) with the C types of the same names. ### Event rings and the `'ring` reference lifetime `EventRing` is an RAII-handle type: when you create an `EventRing` instance, new shared memory mapping are added to your process for that event ring file. Likewise, when `EventRing::drop` is called, those shared memory mappings are removed. Any pointers or references pointing into shared memory would need to be invalidated at that point. We rely on Rust's builtin reference lifetime analysis framework to express this. References to data that lives in event ring shared memory always carries a reference lifetime called `'ring`. This lifetime corresponds to the lifetime of the `EventRing` object itself. Since an `EventRing` pins the shared memory mappings in place by being alive, the true meaning of `'ring` can usually be thought of as the "shared memory lifetime", which is the same. ### Zero-copy APIs and the "event reference" enumeration type In a previous section, we discussed the decoded execution event type, `enum ExecEvent`. There is a second type with a similar design called `enum ExecEventRef<'ring>`; it is used for the zero copy API. To compare the two, here is the `ExecEvent` type: ```rust theme={null} #[derive(Clone, Debug)] pub enum ExecEvent { BlockStart(monad_exec_block_start), BlockReject(monad_exec_block_reject), BlockEnd(monad_exec_block_end), // more variants follow ``` And here is the `ExecEventRef<'ring>` type: ```rust theme={null} #[derive(Clone, Debug)] pub enum ExecEventRef<'ring> { BlockStart(&'ring monad_exec_block_start), BlockReject(&'ring monad_exec_block_reject), BlockEnd(&'ring monad_exec_block_end), // more variants follow ``` The former contains *copies* of event payloads, whereas the latter directly references the bytes living in the shared memory payload buffer. By working with `ExecEventRef<'ring>`, you avoid avoid copying a potentially large amount of data, e.g., especially large EVM logs or call frames. This is valuable if you are filtering out most events anyway. The "event reference" enum type offers better performance, but it comes with two drawbacks: 1. Because it has a reference lifetime as a generic parameter, it can be more difficult to work with (i.e., more running afoul of the borrow checker) 2. Data that lives directly in the payload buffer can be overwritten at any time, so you shouldn't rely on it still being there long after you first look at it ### Copying vs. zero-copy payload APIs The copy vs. zero-copy decision only applies to event payloads; event descriptors are small, and are always copied. There are two ways to read an event's payload once you have its descriptor: 1. *Copying style* `EventDescriptor::try_read` - this will return an `EventPayloadResult` enum type, which either contains the "success" variant (`EventPayloadResult::Ready`) or the "failure" variant (`EventPayloadResult::Expired`); the former contains a `ExecEvent` payload value, and the latter indicates that the payload was lost 2. *Zero-copy style* `EventDescriptor::try_filter_map` - you pass a non-capturing closure to this method, and it is called back with an `ExecEventRef<'ring>` reference pointing to the event payload in shared memory; since your closure can't capture anything, the only way for you to react to the event payload is to return some value `v` of type `T`; `EventDescriptor::try_filter_map` itself returns an `Option`, which is used in the following way: * If the payload has expired prior to calling your closure, then your closure is never called, and the `try_filter_map` returns `Option::None` * Otherwise your closure is run and its return value `v: T` is moved into the `try_filter_map` function * If the payload is still valid after your closure has run, then the value is transferred to the caller by returning `Option::Some(v)`, otherwise `Option::None` is returned #### Why non-capturing closures? The pattern of zero-copy APIs generally works like this: * Create a reference to the data in the event ring payload buffer (`e: &'ring E`) and check for expiration; if not expired ... * ... compute something based on the event payload value, i.e., compute `let v = f(&e)` * Once `f` finishes, check again if the payload expired; if it is expired *now*, then it *may have* become expired sometime during the computation of `v = f(&e)`; the only safe thing we can do is discard the computed value `v`, since we have no way of knowing exactly when the expiration happened If you were permitted to capture variables in the zero-copy closure, you could "smuggle out" computations out-of-band from the library's payload expiration detection checks. That is, if the library later detects that the payload was overwritten sometime during when your closure was running, it would have no guaranteed way to "poison" your smuggled out value. It could only *advise* you not to trust it, but that is error prone. Idiomatic Rust tends to follow a "correct by default" style, and guards against these kinds of unsafe patterns. In the zero-copy API, you can communicate only through return values since you cannot capture anything. This way, the library can decide not to propagate the return value back to you at all, if it later discovers that the payload it gave you as input has expired. ## Important types in the Rust API There are six core types in the API: 1. **Event ring** `EventRing` - given a path to an event ring file, you create one of these to gain access to the shared memory segments of the event ring in that file; you typically use the type alias `ExecEventRing`, which is syntactic sugar for `EventRing` 2. **Event reader** `EventReader<'ring, D: EventDecoder>` - this is the iterator-like type that is used to read events; it's called a "reader" rather than an "iterator" because [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html) already has a specific meaning in Rust; the event reader has a more complex return type than a Rust iterator because it has a "polling" style: its equivalent of `next()` -- called `next_descriptor()` -- can return an event descriptor, report a gap, or indicate that no new event is ready yet 3. **Event descriptor** `EventDescriptor<'ring, D: EventDecoder>` - the event reader produces one of these if the next event is read successfully; recall that the event descriptor contains the common fields of the event, and stores the necessary data to read the event payload and check if it's expired; in the Rust API, reading payloads is done using methods defined on the event descriptor 4. **Event decoder** `trait EventDecoder` - you don't use this directly, but a type that implements this trait -- `ExecEventDecoder` in the case of an execution event ring -- contains all the logic for how to decode event payloads 5. **Event enumeration types** (associated types `EventDecoder::Event` and `EventDecoder::EventRef`) - these give the "copy" and "zero-copy" decoded forms of events; in the case of the `ExecEventDecoder`, `ExecEvent` is the "copy" type and `ExecEventRef<'ring>` is the zero-copy (shared-memory reference) type 6. **Execution event payload types** (`monad_exec_block_start`, and others) - these are bindgen-generated, `#[repr(C)]` event payload types that match their C API counterparts ## Block-level utilities ### `ExecutedBlockBuilder` Execution events are granular: most actions taken by the EVM will publish a single event describing that one action, e.g., every EVM log emitted is published as as a separate `ExecEvent::TxnLog` event. The events are streamed to consumers almost as soon as they are available, so the real-time data of a block comes in "a piece at a time." A utility called the `ExecutedBlockBuilder` will aggregate these events back into a single, block-oriented update, if the user prefers working with complete blocks. The data types in the block representation are also [alloy\_primitives types](https://docs.rs/alloy-primitives/latest/alloy_primitives/) which are more ergonomic to work with in Rust. ### `CommitStateBlockBuilder` As explained in the section on [speculative real-time data](/monad-arch/realtime-data/spec-realtime), the EVM publishes execution events as soon as it is able to, which means it is usually publishing data about blocks that are speculatively executed. We do not know if these blocks will be appended to the blockchain or not, since the consensus decision is occurring in parallel with (and will finish later than) the block's execution. `CommitStateBlockBuilder` builds on the `ExecutedBlockBuilder` by also tracking the commit state of the block as it moves through the consensus life cycle. The block update itself is passed around via an `Arc`, so that it is cheap to copy references to it. As the block commit state changes, you receive updates describing the new state, along with another reference to the `Arc` itself. The speculative real-time data guide often points out that block abandonment is not explicitly communicated by the event system (e.g., [here](/monad-arch/realtime-data/spec-realtime#third-commit-state-finalized) and [here](/reference/websockets#monadnewheads-and-monadlogs)). The `CommitStateBlockBuilder` however, *does* report explicit abandonment of failed proposals, because it is a higher level, user-friendly utility. # Frequently Asked Questions Source: https://docs.monad.xyz/faq ## Execution A few opcodes and precompiles have been repriced to more correclty account for their relative cost. See details [here](/developer-essentials/opcode-pricing). In Monad, like in Ethereum, transactions are ordered linearly within a block. The guarantee that Monad provides is that the result at the end of each block will be as if the transactions were executed serially, even though under the hood there was work done in parallel. Monad handles interdependent transactions gracefully by separating the concerns of **computation** (which can be done in parallel) from **commitment** (which is still done serially). Transactions are computed in parallel optimistically (i.e. assuming that any storage slots read in during execution are correct), generating a **pending result** for each transaction. A pending result consists of the set of input storage slots (and their values) and output storage slots (and their values). Pending results are committed **serially** in the original order of the transactions, checking each input for correctness at the time of commitment. (An input will be incorrect if it was mutated by one of the previously-committed pending results.) If a pending result has any incorrect inputs, it will be re-executed; no other pending results can be committed until that completes. Committing pending results serially ensures that correctness is always preserved. An example illustrates this best. Suppose that at the start of a block, Alice, Bob, and Charlie each have a balance of 100 USDC. These are the first two transactions: | Transaction # | What happens | | ------------- | ------------------------- | | 0 | Alice sends Bob 5 USDC | | 1 | Bob sends Charlie 10 USDC | When transactions 0 and 1 are executed in parallel, they produce the following pending results: | Pending Result # | Inputs | Outputs | | ---------------- | --------------------------- | -------------------------- | | 0 | Alice: 100
Bob: 100 | Alice: 95
Bob: 105 | | 1 | Bob: 100
Charlie: 100 | Bob: 90
Charlie: 110 | Now we commit the pending results serially. Pending result 0 gets committed. When we try to commit pending result 1, we notice that one of the inputs is wrong - Bob's balance was expected to be 100, but it is actually 105. This re-triggers execution for transaction 1. No other transactions can be committed until transaction 1 is re-executed.
*(Note: This question is asking about re-execution as discussed [here](/monad-arch/execution/parallel-execution#optimistic-execution).)* While it's true that having to re-execute a pending result is slower than immediately committing it, re-execution is also typically much faster than the original execution because inputs are stored in cache (RAM). Also note that every transaction will be executed at most twice: once initially, and once on re-execution. More generally, you can think of optimistic parallel execution as a two-pass strategy. The first pass begins executing many transactions in parallel, thus surfacing many storage slot dependencies in parallel and pulling them all into cache. The second pass iterates over the transactions serially, either committing the pending result immediately or re-executing it (but from a position where most storage slots are cached). This strategy, combined with efficient SSD lookups from [MonadDb](/monad-arch/execution/monaddb), delivers a workload that uses the full SSD throughput more efficiently. No, no need! Transactions interacting with your smart contract behave as if every transaction is being executed serially. Parallel execution is strictly an implementation detail. Also, it is important to note that all state contention is evaluated on a slot-by-slot basis. So for example, suppose that transaction 1 involves Alice sending USDC to Bob, and transaction 2 involves Charlie sending USDC to David. It doesn't matter that both transactions involve the same smart contract (the USDC ERC-20 contract); the two affected storage slots in transaction 1 are completely independent from the two affected storage slots in transaction 2. MonadDb stores Merkle Patricia Trie data natively, rather than embedding the trie inside a generic database (like LevelDB or RocksDB) which have their own logic for mapping database entries to locations on disk. This eliminates a level of indirection and substantially reduces the number of IOPS and page reads to look up one value. Trie operations such as recomputing the merkle root at the end of each block are much more efficient. MonadDb further reduces latency and increases throughput by implementing asynchronous I/O using `io_uring` and by bypassing the filesystem. `io_uring` is a new linux kernel technology that allows execution threads to issue I/O requests without stalling or tieing up threads. This allows many I/O requests to be issued in parallel, sequenced by the kernel, and serviced by the first available thread on return. Finally, in MonadDb, each node in the trie is versioned, allowing for intuitive maintenance of the merkle trie and efficient state synchronization algorithms. Only the necessary trie components are sent during statesync, making bootstrapping and recovery faster. 1. Usage of access lists generally increases the size of transactions; long-term we think that bandwidth is the biggest bottleneck 2. In Ethereum, the workflow for a user to submit using access lists is: simulate the transaction, note which storage slots are accessed, then submit the transaction with these slots mentioned in the access list. However, the state of the world may change between simulation and the real execution; we feel that it's the job of the system to handle this gracefully under the hood. 3. It would break integrations with existing wallets which don't support EIP-2930. 4. Note that EIP-2930 access lists are actually underspecified, at least from the perspective of anticipating state contention. If two transactions both read from the same storage slot (but neither writes to it) then, with respect to that storage slot, there is no state contention - neither transaction can invalidate the other's computation. Contention only occurs when an earlier transaction writes to a storage slot that a later transaction will read. EIP-2930 access lists mention which storage slots are accessed, but don't make note of whether the transaction will read from or write to that storage slot. ## Consensus The leader schedule is constructed by a deterministic, stake-weighted process that is computed once per epoch: * An epoch occurs roughly every 5.5 hours (50000 blocks). Validator stake weights are locked in one epoch ahead (i.e. any changes for epoch N+1 must be registered prior to the start of epoch N). * At the start of each epoch, each validator computes the leader schedule based on running a deterministic pseudorandom function on the stake weights. Since the function is deterministic, everyone arrives at the same leader schedule. The client codebase has a parameter called [`ACTIVE_VALSET_SIZE`](/monad-arch/consensus/staking#constants) which is currently set to 200. Thus, the top 200 validators (ordered by stake weight) can participate directly in consensus. This parameter is likely to change over time. Participation is permissionless; one simply needs to be in the top `ACTIVE_VALSET_SIZE` validators. ### Raptorcast Check [this](https://www.category.xyz/blogs/raptorcast-designing-a-messaging-layer) blog post! See the discussion in [this](https://www.category.xyz/blogs/raptorcast-designing-a-messaging-layer) blog post. UDP was selected, accepting lossyness but alleviating it by adding additional data integrity (Raptor codes) and message authentication (signatures over merkle roots), because the combination of those strategies over UDP is substantially more efficient than using TCP. Ethereum is using [libp2p](https://blog.libp2p.io/libp2p-and-ethereum/). It is gossip based (each node propagates its message to a set of peers) which is a lot less efficient (more duplicate messages and a more meandering process for getting the word out). As a result, Ethereum budgets several seconds for the block to propagate throughout the network. Solana uses [Turbine](https://www.helius.dev/blog/turbine-block-propagation-on-solana). Turbine and RaptorCast are similar in the sense that they both use erasure coding, cut packets into MTU-sized chunks, and send transactions through a broadcast tree for efficiency. Some of the differences include: * Monad uses Raptor codes while Turbine uses Reed-Solomon * Monad uses a 2-level broadcast tree with every other validator as a level-1 node while Solana uses a deeper, less structured broadcast tree with fewer level-1 nodes and more complex logic for determining the broadcast tree. There aren't BFT guarantees on block delivery the way that there are for Monad. In L2s there's only 1 sequencer so there isn't a notion of block propagation for consensus. The sequencer just pushes transaction batches occasionally to L1. ### Mempool For example, if my EOA currently has nonce 0, and I send a transaction with nonce 3, then I send transactions with nonce 0, 1, and 2. Will the transaction with nonce 3 be executed or dropped? Answer: Transaction 3 will be executed. ## Block States and Finality A node can start executing [speculatively](/monad-arch/consensus/asynchronous-execution#speculative-execution) as soon as a new block proposal is received. Executing a block just generates new states and a new merkle trie root - but the official pointer is still to the old one. This is like receiving a possible piece of homework from your teacher which will be finalized soon - you can start working on it on a new piece of paper, and just throw it away if it turns out that the homework isn't needed. As soon as a block enters the [Finalized](/monad-arch/consensus/block-states) state, the merkle root for the speculative execution of that block becomes the official local merkle root for that block. That merkle root won't be verified by consensus for another `D=3` blocks, but it is still known locally because state is deterministic given a fixed ordering of transactions. If you want to be certain that your local node did not make a computation error (e.g. due to cosmic rays), you may wait `D=3` blocks for the delayed merkle root, which makes the block in question enter the [Verified](/monad-arch/consensus/block-states) state. ## RPC Due to Monad's high throughput, full nodes do not provide access to arbitrarily old state, as this would require too much storage. See [Historical Data](/developer-essentials/historical-data) for a fuller discussion. When writing smart contracts, it is recommended to use events to log any state that will be needed later, or use a [smart contract indexer](/tooling-and-infra/indexers/indexing-frameworks) to compute it off-chain. ## Features Yes, EIP-7702 is supported. Note that when accounts are delegated under EIP-7702, their treatment under Monad's [Reserve Balance](/developer-essentials/reserve-balance) rules changes slightly. You can learn more about it [here](/developer-essentials/eip-7702). Yes, it is the precompile at `0x0100` following EIP-7951. This enables on-chain verification of WebAuthn/passkey signatures using the P256 curve. See [Precompiles](/developer-essentials/precompiles#p256-signature-verification) for usage details and a Solidity example. ## Miscellaneous Monad's north star is decentralization. If it's really expensive to run a node, only professional validation companies with a large amount of stake will be able to justify the cost. Making nodes economical is crucial to making it feasible for anyone to run a full node, as well as to supporting a large validator set - the Day-1 mainnet target of 100-200 nodes is just a starting point. Rust and C++ are both great high-performance languages. C++ was selected for the database and execution system to get finer control over the filesystem and to use libraries like `io_uring` and `boost::fibers`. Rust was selected for consensus to take advantage of stricter memory safety given that consensus concerns itself with slightly higher-level systems engineering problems. # Add Monad to Wallet Source: https://docs.monad.xyz/guides/add-monad-to-wallet/index Add Monad Mainnet Add Monad Testnet # Add Monad Mainnet to Wallet Source: https://docs.monad.xyz/guides/add-monad-to-wallet/mainnet Follow this quick guide to add Monad Mainnet to your wallet. Click the button below to automatically add Monad Mainnet to your wallet: To manually add Monad Mainnet to your wallet, navigate to your wallet's network settings and add a custom network with the following details:

Network Details:

  • RPC URL: `https://rpc.monad.xyz`
  • Chain ID: `143`
  • Currency Symbol: `MON`
  • Block Explorer: `https://monadvision.com`
# Add Monad Testnet to Wallet Source: https://docs.monad.xyz/guides/add-monad-to-wallet/testnet Follow this quick guide to add Monad Testnet to your wallet. Click the button below to automatically add Monad Testnet to your wallet: To manually add Monad Testnet to your wallet, navigate to your wallet's network settings and add a custom network with the following details:

Network Details:

  • RPC URL: `https://testnet-rpc.monad.xyz`
  • Chain ID: `10143`
  • Currency Symbol: `MON`
  • Block Explorer: `https://testnet.monadvision.com`
# How to build a donation blink Source: https://docs.monad.xyz/guides/blinks In this guide, you will learn how to build a [Blink](https://www.dialect.to/) that allows people to donate MON with a single click. ## Prerequisites * Code Editor of your choice ([Cursor](https://www.cursor.com/) or [Visual Studio Code](https://code.visualstudio.com/) recommended). * [Node](https://nodejs.org/en/download) 18.x.x or above. * Basic TypeScript knowledge. * Testnet MON ([Faucet](https://testnet.monad.xyz)). ## Initial setup ### Initialize the project ```bash theme={null} npx create-next-app@14 blink-starter-monad && cd blink-starter-monad ``` **When prompted, configure your project with these settings:** * ✓ Ok to proceed? → Yes * ✓ Would you like to use TypeScript? → Yes * ✓ Would you like to use ESLint? → Yes * ✓ Would you like to use Tailwind CSS? → Yes * ✓ Would you like your code inside a `src/` directory? → Yes * ✓ Would you like to use App Router? → Yes * ✓ Would you like to customize the import alias (`@/*` by default)? → No ### Install dependencies ```bash theme={null} npm install @solana/actions wagmi viem@2.x ``` ### Start development server The development server is used to start a local test environment that runs on your computer. It is perfect to test and develop your blink, before you ship it to production. ```bash theme={null} npm run dev ``` ## Building the Blink Now that we have our basic setup finished, it is time to start building the blink. ### Create an endpoint To write a blink provider, you have to create an endpoint. Thanks to NextJS, this all works pretty straightforward. All you have to do is to create the following folder structure: ``` src/ └── app/ └── api/ └── actions/ └── donate-mon/ └── route.ts ``` ### Create actions.json Create a route in `app` folder for the `actions.json` file which will be hosted in the root directory of our application. This file is needed to tell other applications which blink providers are available on your website. **Think of it as a sitemap for blinks.** You can read more about the [actions.json](https://docs.dialect.to/documentation/actions/specification/actions.json) in the official [Dialect documentation](https://docs.dialect.to/documentation/actions/specification/actions.json). ``` src/ └── app/ └── actions.json/ └── route.ts ``` ```js lines title="src/app/actions.json/route.ts" theme={null} import { ACTIONS_CORS_HEADERS, ActionsJson } from "@solana/actions"; export const GET = async () => { const payload: ActionsJson = { rules: [ // map all root level routes to an action { pathPattern: "/*", apiPath: "/api/actions/*", }, // idempotent rule as the fallback { pathPattern: "/api/actions/**", apiPath: "/api/actions/**", }, ], }; return Response.json(payload, { headers: ACTIONS_CORS_HEADERS, }); }; // DO NOT FORGET TO INCLUDE THE `OPTIONS` HTTP METHOD // THIS WILL ENSURE CORS WORKS FOR BLINKS export const OPTIONS = GET; ``` ### Add an image for the blink Every blink has an image that is rendered on top. If you have your image already hosted somewhere, you can skip this step but if you haven't you can just create a `public` folder in your `NextJS` project and paste an image there. In our example we will paste a file called `donate-mon.png` into this public folder. You can right-click and save the image below. donate-mon image ### OPTIONS endpoint and headers This enables CORS for cross-origin requests and standard headers for the API endpoints. This is standard configuration you do for every Blink. ```js lines title="src/app/api/actions/donate-mon/route.ts" theme={null} // CAIP-2 format for Monad const blockchain = `eip155:10143`; // Create headers with CAIP blockchain ID const headers = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, x-blockchain-ids, x-action-version", "Content-Type": "application/json", "x-blockchain-ids": blockchain, "x-action-version": "2.0", }; // OPTIONS endpoint is required for CORS preflight requests // Your Blink won't render if you don't add this export const OPTIONS = async () => { return new Response(null, { headers }); }; ``` ### GET endpoint `GET` returns the Blink metadata and UI configuration. It describes: * How the Action appears in Blink clients * What parameters users need to provide * How the Action should be executed ```js lines title="src/app/api/actions/donate-mon/route.ts" theme={null} import { ActionGetResponse, } from "@solana/actions"; // GET endpoint returns the Blink metadata (JSON) and UI configuration export const GET = async (req: Request) => { // This JSON is used to render the Blink UI const response: ActionGetResponse = { type: "action", icon: `${new URL("/donate-mon.png", req.url).toString()}`, label: "1 MON", title: "Donate MON", description: "This Blink demonstrates how to donate MON on the Monad blockchain. It is a part of the official Blink Starter Guides by Dialect Labs. \n\nLearn how to build this Blink: https://dialect.to/docs/guides/donate-mon", // Links is used if you have multiple actions or if you need more than one params links: { actions: [ { // Defines this as a blockchain transaction type: "transaction", label: "0.01 MON", // This is the endpoint for the POST request href: `/api/actions/donate-mon?amount=0.01`, }, { type: "transaction", label: "0.05 MON", href: `/api/actions/donate-mon?amount=0.05`, }, { type: "transaction", label: "0.1 MON", href: `/api/actions/donate-mon?amount=0.1`, }, { // Example for a custom input field type: "transaction", href: `/api/actions/donate-mon?amount={amount}`, label: "Donate", parameters: [ { name: "amount", label: "Enter a custom MON amount", type: "number", }, ], }, ], }, }; // Return the response with proper headers return new Response(JSON.stringify(response), { status: 200, headers, }); }; ``` ### Testing the Blink Visit [dial.to](https://dial.to) and type in the link to your blink to see if it works. If your server runs on localhost:3000 the url should be like this: `http://localhost:3000/api/actions/donate-mon` [dial.to](https://dial.to) currently supports only GET previews for EVM. To test your POST endpoint, we need to build a Blink Client. testing blink ### POST endpoint `POST` handles the actual MON transfer transaction. #### POST request to the endpoint Create the post request structure and add the necessary imports as well as the `donationWallet` on top of the file. ```js lines title="src/app/api/actions/donate-mon/route.ts" theme={null} // Update the imports import { ActionGetResponse, ActionPostResponse } from "@solana/actions"; import { serialize } from "wagmi"; import { parseEther } from "viem"; // Wallet address that will receive the donations const donationWallet = ``; // POST endpoint handles the actual transaction creation export const POST = async (req: Request) => { try { // Code that goes here is in the next step } catch (error) { // Log and return an error response console.error("Error processing request:", error); return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500, headers, }); } }; ``` #### Extract data from request The request contains the URL and the account (PublicKey) from the payer. ```js lines title="src/app/api/actions/donate-mon/route.ts" theme={null} // POST endpoint handles the actual transaction creation export const POST = async (req: Request) => { try { // Step 1 // Extract amount from URL const url = new URL(req.url); const amount = url.searchParams.get("amount"); if (!amount) { throw new Error("Amount is required"); } } catch (error) { // Error handling } } ``` #### Create the transaction Create a new transaction with all the necessary data and add it below in the `POST` request. ```js lines title="src/app/api/actions/donate-mon/route.ts" theme={null} // POST endpoint handles the actual transaction creation export const POST = async (req: Request) => { try { // ... previous code from step // Build the transaction const transaction = { to: donationWallet, value: parseEther(amount).toString(), chainId: 10143, }; const transactionJson = serialize(transaction); } catch (error) { // Error handling } } ``` #### Return the transaction in response. Create `ActionPostResponse` and return it to the client. ```ts lines title="src/app/api/actions/donate-mon/route.ts" theme={null} export const POST = async (req: Request) => { try { // ... previous code from step 1 and 2 // Build ActionPostResponse const response: ActionPostResponse = { type: "transaction", transaction: transactionJson, message: "Donate MON", }; // Return the response with proper headers return new Response(JSON.stringify(response), { status: 200, headers, }); } catch (error) { // Error handling } } ``` ### Full code in `route.ts` ```ts lines title="src/app/api/actions/donate-mon/route.ts" theme={null} import { ActionGetResponse, ActionPostResponse } from "@solana/actions"; import { serialize } from "wagmi"; import { parseEther } from "viem"; // CAIP-2 format for Monad const blockchain = `eip155:10143`; // Wallet address that will receive the donations const donationWallet = ``; // Create headers with CAIP blockchain ID const headers = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, x-blockchain-ids, x-action-version", "Content-Type": "application/json", "x-blockchain-ids": blockchain, "x-action-version": "2.0", }; // OPTIONS endpoint is required for CORS preflight requests // Your Blink won't render if you don't add this export const OPTIONS = async () => { return new Response(null, { headers }); }; // GET endpoint returns the Blink metadata (JSON) and UI configuration export const GET = async (req: Request) => { // This JSON is used to render the Blink UI const response: ActionGetResponse = { type: "action", icon: `${new URL("/donate-mon.png", req.url).toString()}`, label: "1 MON", title: "Donate MON", description: "This Blink demonstrates how to donate MON on the Monad blockchain. It is a part of the official Blink Starter Guides by Dialect Labs. \n\nLearn how to build this Blink: https://dialect.to/docs/guides/donate-mon", // Links is used if you have multiple actions or if you need more than one params links: { actions: [ { // Defines this as a blockchain transaction type: "transaction", label: "0.01 MON", // This is the endpoint for the POST request href: `/api/actions/donate-mon?amount=0.01`, }, { type: "transaction", label: "0.05 MON", href: `/api/actions/donate-mon?amount=0.05`, }, { type: "transaction", label: "0.1 MON", href: `/api/actions/donate-mon?amount=0.1`, }, { // Example for a custom input field type: "transaction", href: `/api/actions/donate-mon?amount={amount}`, label: "Donate", parameters: [ { name: "amount", label: "Enter a custom MON amount", type: "number", }, ], }, ], }, }; // Return the response with proper headers return new Response(JSON.stringify(response), { status: 200, headers, }); }; // POST endpoint handles the actual transaction creation export const POST = async (req: Request) => { try { // Extract amount from URL const url = new URL(req.url); const amount = url.searchParams.get("amount"); if (!amount) { throw new Error("Amount is required"); } // Build the transaction const transaction = { to: donationWallet, value: parseEther(amount).toString(), chainId: 10143, }; const transactionJson = serialize(transaction); // Build ActionPostResponse const response: ActionPostResponse = { type: "transaction", transaction: transactionJson, message: "Donate MON", }; // Return the response with proper headers return new Response(JSON.stringify(response), { status: 200, headers, }); } catch (error) { // Log and return an error response console.error("Error processing request:", error); return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500, headers, }); } }; ``` At this point the Blink is ready, but we need a Blink client since [dial.to](https://dial.to) does not support EVM wallets. ## Implementing the Blink client In this step you will learn to implement the blink client, which is the visual representation of a blink. ### Install dependencies ```bash theme={null} npm install connectkit @tanstack/react-query @dialectlabs/blinks ``` ### Implement the provider The provider is necessary to trigger wallet actions in the blink. ### Create config for `WagmiProvider` This file is used to set the proper configurations for the `WagmiProvider` in the next step. ```ts lines title="src/config.ts" theme={null} import { http, createConfig } from "wagmi"; import { monadTestnet } from "wagmi/chains"; export const config = createConfig({ chains: [monadTestnet], transports: { [monadTestnet.id]: http(), }, }); ``` ### Create the wallet connection context providers Create the provider that we can use to wrap around our app. Don't forget to use the `“use client”;` at the top of the file if you are in a NextJS project. In this project, we are using [ConnectKit](https://docs.family.co/connectkit) but you can use other alternatives as well (Eg: [RainbowKit](https://www.rainbowkit.com/)) ```tsx lines title="src/provider.tsx" theme={null} "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ConnectKitProvider } from "connectkit"; import { type PropsWithChildren } from "react"; import { WagmiProvider } from "wagmi"; import { config } from "@/config"; const queryClient = new QueryClient(); export const Providers = ({ children }: PropsWithChildren) => { return ( {children} ); }; ``` ### Wrap the app with context provider If you want your provider to be accessible throughout your app, it is recommended to wrap it around the `children` element in your `layout.tsx`. ```tsx lines title="src/app/layout.tsx" theme={null} // additional import import { Providers } from "@/provider"; // other code in the file ... export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ``` ### Using the `Blink` component Now that we have everything wrapped, we can start with the implementation of the blink renderer. To do so open the `page.tsx` file in your `/src/app` folder. ```tsx lines title="src/app/page.tsx" theme={null} "use client"; import { Blink, useBlink, useActionsRegistryInterval, } from "@dialectlabs/blinks"; import "@dialectlabs/blinks/index.css"; import { useEvmWagmiAdapter } from "@dialectlabs/blinks/hooks/evm"; import { ConnectKitButton, useModal } from "connectkit"; export default function Home() { // Actions registry interval useActionsRegistryInterval(); // ConnectKit modal const { setOpen } = useModal(); // Wagmi adapter, used to connect to the wallet const { adapter } = useEvmWagmiAdapter({ onConnectWalletRequest: async () => { setOpen(true); }, }); // Action we want to execute in the Blink const { blink, isLoading } = useBlink({ url: "evm-action:http://localhost:3000/api/actions/donate-mon", }); return (
{isLoading || !blink ? ( Loading ) : ( // Blink component, used to execute the action )}
); } ``` ### Make a transaction That's it. To test it, visit [localhost:3000](http://localhost:3000) and click on a button or enter a custom amount that you want to donate. blink client ## Conclusion In this tutorial, you learned how you can create a blink that sends MON to another wallet from scratch using a `NextJS` project. Besides the basic project setup there were two important things that we built. The first thing was the blink provider. This provider works as an API for the blink and handles how the blink is rendered in the fronend (`GET` request) and executes the blockchain transaction (`POST` request). The second implementation was the blink client. This client serves as the visual representation of the blink and is what the user sees and uses to interact with the blink provider. These are two separate parts, which means you can build a blink without worrying about the client implementation and you can implement clients for existing blinks without the need to build your own blink. # Custom Stablecoins on Monad with Brale Source: https://docs.monad.xyz/guides/brale Deploy regulated, fully-backed USD stablecoins on Monad and integrate them into your application with onramps, offramps, and custody. ## Overview Monad's high-performance EVM, with 10,000+ tx/s and sub-second time-to-finality, makes it well-suited for stablecoin-powered applications. Brale lets businesses create their own stablecoins and then access them programmatically through a single API for money movements. ## What You'll Build By the end of this guide, you'll have: * A custom stablecoin launched on Monad * API credentials for programmatic control * Working integrations for onramps, transfers, swaps, and offramps ## Why Custom Stablecoins? | Use Case | Benefit | | --------------------- | -------------------------------------------------------------------- | | Branded experience | Your token, your identity—build user trust | | Direct fiat rails | Connect to ACH, wire, and RTP through a single orchestration layer | | Cross-chain liquidity | Move value across supported chains through a unified transfers model | | Compliance built-in | Brale handles the regulated issuance and orchestration layer | *** ## Prerequisites Before starting, ensure you have: * A registered business entity for KYB verification * A [Brale account](https://app.brale.xyz/) * Monad network and wallet access: * [Mainnet network info](https://docs.monad.xyz/developer-essentials/network-information) * [Testnet network info](https://docs.monad.xyz/developer-essentials/testnets) * [Add Monad to your wallet](https://docs.monad.xyz/guides/add-monad-to-wallet) ## Choose Your Environment Use Brale testnet while developing and switch to mainnet for production. In testnet, use `monad_testnet` as the `transfer_type` and in mainnet, use `monad`. Brale's testnet supports testnet networks only and skips the real fiat leg on mint and redemption flows. *** ## Part 1: Launch Your Stablecoin in Brale Stablecoin issuance is a dashboard workflow. API integration begins after your stablecoin has been launched. Once issued, the stablecoin is available via the API. ### Step 1: Complete Business Verification 1. Log in to the Brale Dashboard 2. Complete KYB (Know Your Business) verification: * Business name and EIN * Beneficial ownership information ### Step 2: Launch Your Stablecoin In the Brale Dashboard, create your stablecoin and configure: * **Name:** Your stablecoin's display name (for example, Acme Dollar) * **Symbol:** Token ticker (for example, ACME) * **Chains:** Select Monad * **Branding:** Add your token logo Then fund it via Wire, ACH, or USDC. Once your stablecoin is live, continue with the API setup below. *** ## Part 2: Integrate with the Brale API Brale's API uses OAuth 2.0 client credentials, environment-specific applications, and `address_id` values as the source/destination primitive for transfers. ### Step 1: Create API Credentials 1. Sign in to your Brale account 2. Navigate to **Settings → API** 3. Click **Create Application** 4. Give the application a name and choose **Mainnet** or **Testnet** 5. Copy the generated `CLIENT_ID` and `CLIENT_SECRET` Brale API applications are environment-specific, and the same application credentials can be used for direct API access and the hosted Brale API MCP server. ### Step 2: Authenticate Exchange your credentials for a bearer token: ```bash theme={null} export CLIENT_ID="your_client_id" export CLIENT_SECRET="your_client_secret" CLIENT_CREDENTIALS=$( printf "%s:%s" "${CLIENT_ID}" "${CLIENT_SECRET}" | base64 | tr -d '\n' ) curl --request POST \ --url "https://auth.brale.xyz/oauth2/token" \ --header "Authorization: Basic ${CLIENT_CREDENTIALS}" \ --header "Content-Type: application/x-www-form-urlencoded" \ --data grant_type=client_credentials ``` **Response** ```json theme={null} { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 } ``` Use the returned token in subsequent requests: ```bash theme={null} export AUTH_TOKEN="your_access_token" ``` ### Step 3: Set Your Working Variables ```bash theme={null} export ACCOUNT_ID="your_account_id" export TOKEN_SYMBOL="ACME" # Use testnet while developing export MONAD_TRANSFER_TYPE="monad_testnet" # Switch to mainnet for production # export MONAD_TRANSFER_TYPE="monad" ``` ### Step 4: Retrieve Your `account_id` List your accounts and identify the account you want to operate on: ```bash theme={null} curl --request GET \ --url "https://api.brale.xyz/accounts" \ --header "Authorization: Bearer ${AUTH_TOKEN}" ``` ### Step 5: Get Your Internal Monad `address_id` Brale automatically generates internal custodial addresses on supported chains for onboarded accounts. Retrieve the internal Monad address you want to use for custodial flows: ```bash theme={null} curl --request GET \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/addresses?type=internal&transfer_type=${MONAD_TRANSFER_TYPE}" \ --header "Authorization: Bearer ${AUTH_TOKEN}" ``` Store the returned internal Monad `address_id`: ```bash theme={null} export MONAD_ADDRESS_ID="your_internal_monad_address_id" ``` ### Step 6: Register an External Monad Wallet (Optional) If you want to send to or receive from a self-custody Monad wallet, register it as an external address first. In Brale, addresses can be `type=internal` or `type=external`, and both are represented by `address_id` values used in transfers. ```bash theme={null} curl --request POST \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/addresses/external" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "name": "User Monad Wallet", "address": "0x1234567890abcdef1234567890abcdef12345678", "transfer_types": ["'"${MONAD_TRANSFER_TYPE}"'"] }' ``` **Response** ```json theme={null} { "id": "2VcUIIsgARwVbEGlIYbhg6fGG57" } ``` Store the returned `id` as the wallet's Brale `address_id`: ```bash theme={null} export EXTERNAL_MONAD_ADDRESS_ID="your_external_monad_address_id" ``` *** ## Alternative: Brale API MCP Server Brale offers a hosted API MCP server for live API access from MCP-compatible clients. The remote server URL is `https://mcp.brale.xyz/`. ### Setup for Claude Desktop 1. Open **Settings → Connectors** 2. Click **Add custom connector** 3. Enter the remote MCP server URL: `https://mcp.brale.xyz/` 4. Open **Advanced settings** and enter your OAuth Client ID and Client Secret 5. Save the connector Once added, enable it from the Search and Tools menu in chat. Custom connectors are available on Pro, Max, Team, and Enterprise plans. ### Setup for Cursor Add a server entry in **Settings → MCP**: ```json theme={null} { "name": "Brale API MCP", "url": "https://mcp.brale.xyz/", "env": { "CLIENT_ID": "your_client_id", "CLIENT_SECRET": "your_client_secret" } } ``` ### Setup for Windsurf Edit `~/.codeium/windsurf/mcp_config.json`: ```json theme={null} { "mcpServers": { "Brale API MCP": { "url": "https://mcp.brale.xyz/", "env": { "CLIENT_ID": "your_client_id", "CLIENT_SECRET": "your_client_secret" } } } } ``` Restart Windsurf after saving. *** ## Move Value with Transfers All stablecoin movement is handled through the Transfers API. Every transfer is scoped to an `account_id`, references `address_id` values where applicable, and uses `transfer_type` plus `value_type` to define the rail/chain and asset being moved. ### Base URL ``` https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers ``` ### Required Headers ``` Authorization: Bearer ${AUTH_TOKEN} Content-Type: application/json Idempotency-Key: $(uuidgen) ``` Always include a fresh `Idempotency-Key` on create requests. *** ## Onramps: Fiat to Stablecoin Convert USD to your stablecoin on Monad. ### Option A: Wire Onramp ```bash theme={null} curl --request POST \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "amount": { "value": "1000.00", "currency": "USD" }, "source": { "value_type": "usd", "transfer_type": "wire" }, "destination": { "address_id": "'"${MONAD_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "'"${MONAD_TRANSFER_TYPE}"'" } }' ``` ### Option B: ACH Onramp If you are funding from a linked bank account, use an ACH funding address and mint to your Monad destination address: ```bash theme={null} export FUNDING_ADDRESS_ID="your_ach_funding_address_id" curl --request POST \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "amount": { "value": "1000.00", "currency": "USD" }, "source": { "address_id": "'"${FUNDING_ADDRESS_ID}"'", "value_type": "usd", "transfer_type": "ach_debit" }, "destination": { "address_id": "'"${MONAD_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "'"${MONAD_TRANSFER_TYPE}"'" } }' ``` ### Option C: Automated Onramps For recurring flows, create an Automation that mints stablecoins automatically when USD arrives: 1. In the Dashboard, navigate to **Automations** 2. Create an automation that listens for inbound ACH or wire funding 3. Set the destination to your Monad address 4. Save the automation *** ## Transfers: Move Stablecoins Transfer your stablecoin between addresses on Monad or across chains. ### On-Chain Transfer (Monad) ```bash theme={null} export SOURCE_ADDRESS_ID="your_source_monad_address_id" export DESTINATION_ADDRESS_ID="your_destination_monad_address_id" curl --request POST \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "amount": { "value": "500.00", "currency": "USD" }, "source": { "address_id": "'"${SOURCE_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "'"${MONAD_TRANSFER_TYPE}"'" }, "destination": { "address_id": "'"${DESTINATION_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "'"${MONAD_TRANSFER_TYPE}"'" } }' ``` ### Cross-Chain Transfer Move your stablecoin from Monad to another supported chain: ```bash theme={null} export BASE_ADDRESS_ID="your_base_address_id" curl --request POST \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "amount": { "value": "500.00", "currency": "USD" }, "source": { "address_id": "'"${MONAD_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "'"${MONAD_TRANSFER_TYPE}"'" }, "destination": { "address_id": "'"${BASE_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "base" } }' ``` *** ## Swaps: Exchange Stablecoins Swap between supported stablecoins at 1:1 with no slippage. Confirm the specific `value_type` and `transfer_type` combination you plan to use before production. ### Example: Swap Your Token for USDC ```bash theme={null} export USDC_DESTINATION_ADDRESS_ID="your_usdc_destination_address_id" curl --request POST \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "amount": { "value": "250.00", "currency": "USD" }, "source": { "address_id": "'"${MONAD_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "'"${MONAD_TRANSFER_TYPE}"'" }, "destination": { "address_id": "'"${USDC_DESTINATION_ADDRESS_ID}"'", "value_type": "USDC", "transfer_type": "base" } }' ``` *** ## Offramps: Stablecoin to Fiat Convert your stablecoin back to USD via wire or ACH. ### Offramp to Wire ```bash theme={null} export USD_DESTINATION_ADDRESS_ID="your_wire_destination_address_id" curl --request POST \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "amount": { "value": "1000.00", "currency": "USD" }, "source": { "address_id": "'"${MONAD_ADDRESS_ID}"'", "value_type": "'"${TOKEN_SYMBOL}"'", "transfer_type": "'"${MONAD_TRANSFER_TYPE}"'" }, "destination": { "address_id": "'"${USD_DESTINATION_ADDRESS_ID}"'", "value_type": "usd", "transfer_type": "wire" } }' ``` ### Offramp to ACH For ACH payouts, use `ach_credit` or `same_day_ach_credit` on the destination address instead of `wire`. *** ## Monitoring Transfers Check transfer status by querying the transfer ID: ```bash theme={null} export TRANSFER_ID="your_transfer_id" curl --request GET \ --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers/${TRANSFER_ID}" \ --header "Authorization: Bearer ${AUTH_TOKEN}" ``` ### Transfer States | Status | Description | | ------------ | ------------------------------------------------------------ | | `pending` | Transfer submitted but not yet in progress | | `processing` | Transfer is in progress | | `complete` | Transfer finalized and funds have arrived at the destination | | `canceled` | Transfer has been canceled | | `failed` | Transfer could not be completed | *** ## Best Practices ### Security * Store API credentials in environment variables or a secrets manager * Use a fresh idempotency key for each new transfer request * Refresh bearer tokens when they expire ### Testing * Use testnet credentials during development * Use `monad_testnet` while testing and `monad` in production * Do not mix testnet credentials/resources with mainnet flows ### Addressing * Use internal addresses for Brale-custodied wallets * Register self-custody Monad wallets as external addresses before using them in transfers * Store returned `address_id` values and reuse them across workflows *** ## Next Steps * Explore the [Brale docs](https://docs.brale.xyz/) * Review the [Brale API MCP guide](https://docs.brale.xyz/mcp/api) * Confirm [supported transfer types and value types](https://docs.brale.xyz/coverage/transfer-types) before shipping production flows # How to build custom deep links in an Expo-based mobile app Source: https://docs.monad.xyz/guides/deeplinks-using-expo Deep links are URLs that take users directly to specific content within a mobile app or website, rather than just launching the app's home screen. They work like shortcuts, enabling smoother navigation and improving user experience. In this guide, you will learn the basics of adding deep links into your [Expo](https://docs.expo.dev/)-based mobile app. ## What are deep links? Deep link is constructed by three parts: * **Scheme**: The URL scheme that identifies the app that should open the URL (example: myapp\://). It can also be https or http for non-standard deep links. * **Host**: The domain name of the app that should open the URL (example: web-app.com). * **Path**: The path to the screen that should be opened (example: /product). If the path isn't specified, the user is taken to the home screen of the app. Deep links can also have params just like web links! Example: ``` rnwalletapp://swap?from={token}&to={token}&amount={amount} ``` ## Building a deep link If you'd like to try a deep link demo you can do by cloning [this](https://github.com/monad-developers/expo-swap-template/tree/branch/deeplink?tab=readme-ov-file) repo and switching to the `branch/deeplink` branch: ```bash theme={null} git clone https://github.com/monad-developers/expo-swap-template.git ``` ```bash theme={null} git checkout branch/deeplink ``` ### Defining the scheme The first step is to define a scheme; you can do so by editing the `app.json` file in the Expo project. ```json title="app.json" theme={null} { "expo": { "scheme": "myapp" // or your preferred scheme } } ``` Custom schemes like `myapp://` are not unique across Android or iOS. If two apps register the same scheme, the system won’t know which one to launch, or it might launch the wrong one. Use something app-specific and hard to accidentally duplicate. ### Listening for deep link events In your app entrypoint (e.g., `_layout.tsx` or a provider), add logic to: * Handle initial deep link * Listen for deep link changes A good practice is to create a `DeepLinkHandler` and wrap the entire app with it. Example (in an Expo project using File-based routing): ```tsx lines title="app/_layout.tsx" theme={null} ... // Function to parse the deep link and get the hostname and queryParams function parseSwapDeeplink(url: string): SwapDeeplinkParams | null { try { const { hostname, queryParams } = Linking.parse(url); if (hostname !== 'swap' || !queryParams) { return null; } return { from: queryParams.from as string | undefined, to: queryParams.to as string | undefined, amount: queryParams.amount as string | undefined, }; } catch (error) { console.error('Error parsing deeplink:', error); return null; } } function DeeplinkHandler({ children }: { children: React.ReactNode }) { const router = useRouter(); useEffect(() => { const handleDeeplink = (url: string) => { // Parse the deep link and get the params (host, path, params etc...) const params = parseSwapDeeplink(url); if (params) { // The example here makes the params globally accessible in the app, however you can use React Context or similar to make the params accessible from anywhere in the app. (global as any).swapDeeplinkParams = params; // Based on the path or host you can redirect the user to the respective screen in the app router.replace('/'); } }; // Handle initial URL Linking.getInitialURL().then(url => url && handleDeeplink(url)); // Create an event listener and handle URL changes while app is open const subscription = Linking.addEventListener('url', event => handleDeeplink(event.url)); // Removes the event listener when the component is destroyed (avoids memory leaks) return () => subscription.remove(); }, [router]); return <>{children}; } export default function Layout() { ... return ( ); } ``` That's it, your app is ready to handle deep links based on the `hostname` and `queryParams` you can redirect the user to the respective screens. Additionally if you make the `queryParams` accessible globally (via context or some other way) you can prefill input values too! **Example: Prefilling token swap amounts!** ## Testing the deep link Here's a demo of how deep links work in a mobile app: