| Implements schema and functions related to Ethereum blocks. |
|---|
- 🦄 All block features till Osaka
- 🌴 Tree-shakeable API
- 👷🏼 Controlled dependency set (4 external +
@noblecrypto) - 🔮
EIP-4844Shard Blob Txs - 🔮
EIP-7594PeerDAS Blob Transactions - 💸
EIP-4895Beacon Chain Withdrawals - 📨
EIP-7685Consensus Layer Requests - 📋
EIP-7928Block Level Access List Hash (Amsterdam, experimental) - 🕐
EIP-7843Slot Number header field (Amsterdam, experimental) - 🛵 324KB bundle size (81KB gzipped)
- 🏄🏾♂️ WASM-free default + Fully browser ready
Runnable examples live in examples/.
- Installation
- Getting Started
- Creating Blocks
- EIP-1559 Base Fee
- EIP-4895 Withdrawals
- EIP-4844 Blob Blocks
- EIP-7685 CL Requests
- EIP-7928 Block Access List Hash
- EIP-7843 Slot Number
- Consensus Types
- Browser
- API
- Testing
- EthereumJS
- License
npm install @ethereumjs/blockNote: For EIP-4844 blob blocks you need a KZG setup on Common — see @ethereumjs/tx KZG setup.
Block and BlockHeader objects are created via standalone factory functions (not new Block()). Properties are frozen by default (freeze: false to opt out).
// ./examples/createBlockHeader.ts
import { createBlockHeader } from '@ethereumjs/block'
import { bytesToHex } from '@ethereumjs/util'
import type { HeaderData } from '@ethereumjs/block'
const headerData: HeaderData = {
number: 15,
parentHash: '0x6bfee7294bf44572b7266358e627f3c35105e1c3851f3de09e6d646f955725a7',
gasLimit: 8000000,
timestamp: 1562422144,
}
const header = createBlockHeader(headerData)
console.log(`Created block header with hash=${bytesToHex(header.hash())}`)Main constructors:
| Factory | Purpose |
|---|---|
createBlock() |
Full block from BlockData |
createEmptyBlock() |
Block with header only |
createBlockFromRLP() |
Decode serialized block bytes |
createBlockFromRPC() / createBlockFromJSONRPCProvider() |
From JSON-RPC block object |
createBlockFromExecutionPayload() / createBlockFromBeaconPayloadJSON() |
From CL execution payload |
createBlockHeader() |
Header-only variants of the above |
Trie helpers: genTransactionsTrieRoot(), genWithdrawalsTrieRoot(), genRequestsRoot().
// ./examples/createBlockWithTxs.ts
import { createBlock, genTransactionsTrieRoot } from '@ethereumjs/block'
import { Common, Mainnet } from '@ethereumjs/common'
import { createFeeMarket1559Tx } from '@ethereumjs/tx'
import { bytesToHex, createAddressFromString, hexToBytes } from '@ethereumjs/util'
const main = async () => {
const common = new Common({ chain: Mainnet })
const privateKey = hexToBytes('0xe331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109')
const tx = createFeeMarket1559Tx(
{
type: 2,
nonce: 0n,
gasLimit: 21_000n,
maxFeePerGas: 100n,
maxPriorityFeePerGas: 1n,
to: createAddressFromString('0xcccccccccccccccccccccccccccccccccccccccc'),
value: 1n,
},
{ common },
).sign(privateKey)
const block = createBlock({ transactions: [tx] }, { common, skipConsensusFormatValidation: true })
const transactionsRoot = await genTransactionsTrieRoot(block.transactions)
console.log(`Block with ${block.transactions.length} transaction(s)`)
console.log(`Transactions root: ${bytesToHex(transactionsRoot)}`)
}
void main()// ./examples/blockFromRLP.ts
import { createBlock, createBlockFromRLP } from '@ethereumjs/block'
import { Common, Mainnet } from '@ethereumjs/common'
import { bytesToHex } from '@ethereumjs/util'
const common = new Common({ chain: Mainnet })
const block = createBlock(
{
header: {
number: 1n,
gasLimit: 30_000_000n,
},
},
{ common, skipConsensusFormatValidation: true },
)
const serialized = block.serialize()
const decoded = createBlockFromRLP(serialized, { common })
console.log(`Serialized ${serialized.length} bytes`)
console.log(`Round-trip hash match: ${bytesToHex(decoded.hash()) === bytesToHex(block.hash())}`)Call await block.validateData() to run consensus and transaction checks. Use skipConsensusFormatValidation: true in examples and tests when header fields are intentionally incomplete.
Blocks created with default Mainnet Common are EIP-1559 compatible. BlockHeader.calcNextBaseFee() computes the base fee for the child block.
// ./examples/1559.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Mainnet } from '@ethereumjs/common'
import { createTx } from '@ethereumjs/tx'
const common = new Common({ chain: Mainnet })
const block = createBlock(
{
header: {
baseFeePerGas: BigInt(10),
gasLimit: BigInt(100),
gasUsed: BigInt(60),
},
},
{ common },
)
// Base fee will increase for next block since the
// gas used is greater than half the gas limit
console.log(Number(block.header.calcNextBaseFee())) // 11
// So for creating a block with a matching base fee in a certain
// chain context you can do:
const blockWithMatchingBaseFee = createBlock(
{
header: {
baseFeePerGas: block.header.calcNextBaseFee(),
gasLimit: BigInt(100),
gasUsed: BigInt(60),
},
},
{ common },
)
console.log(Number(blockWithMatchingBaseFee.header.baseFeePerGas)) // 11
// successful validation does not throw error
await blockWithMatchingBaseFee.validateData()
// failed validation throws error
const tx = createTx(
{ type: 2, maxFeePerGas: BigInt(20) },
{ common: new Common({ chain: Mainnet }) },
)
blockWithMatchingBaseFee.transactions.push(tx)
console.log(blockWithMatchingBaseFee.getTransactionsValidationErrors()) // invalid transaction added to block
try {
await blockWithMatchingBaseFee.validateData()
} catch (err) {
console.log(`Expected validation failure: ${(err as Error).message.split('\n')[0]}`)
}Shanghai+ blocks carry beacon-chain withdrawals. Provide withdrawals and a matching withdrawalsRoot:
// ./examples/withdrawals.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Mainnet } from '@ethereumjs/common'
import { Address, hexToBytes } from '@ethereumjs/util'
import type { WithdrawalData } from '@ethereumjs/util'
const common = new Common({ chain: Mainnet })
const withdrawal: WithdrawalData = {
index: BigInt(0),
validatorIndex: BigInt(0),
address: new Address(hexToBytes(`0x${'20'.repeat(20)}`)),
amount: BigInt(1000),
}
const block = createBlock(
{
header: {
withdrawalsRoot: hexToBytes(
'0x69f28913c562b0d38f8dc81e72eb0d99052444d301bf8158dc1f3f94a4526357',
),
},
withdrawals: [withdrawal],
},
{
common,
},
)
console.log(`Block with ${block.withdrawals!.length} withdrawal(s) created`)Validate the withdrawals trie with await block.withdrawalsTrieIsValid().
Cancun+ blocks include excessBlobGas and blobGasUsed. Blob txs require KZG on Common:
// ./examples/eip4844Block.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createBlob4844Tx } from '@ethereumjs/tx'
import { createAddressFromPrivateKey } from '@ethereumjs/util'
import { randomBytes } from '@noble/hashes/utils.js'
import { trustedSetup } from '@paulmillr/trusted-setups/fast-peerdas.js'
import { KZG as microEthKZG } from 'micro-eth-signer/kzg.js'
const main = async () => {
const kzg = new microEthKZG(trustedSetup)
const common = new Common({
chain: Mainnet,
customCrypto: {
kzg,
},
hardfork: Hardfork.Cancun,
})
const blobTx = createBlob4844Tx(
{ blobsData: ['myFirstBlob'], to: createAddressFromPrivateKey(randomBytes(32)) },
{ common },
)
const block = createBlock(
{
header: {
excessBlobGas: 0n,
},
transactions: [blobTx],
},
{
common,
skipConsensusFormatValidation: true,
},
)
console.log(
`4844 block header with excessBlobGas=${block.header.excessBlobGas} created and ${
block.transactions.filter((tx) => tx.type === 3).length
} blob transactions`,
)
}
void main()Blob gas pricing from the header:
// ./examples/blobGasPrice.ts
import { createBlockHeader } from '@ethereumjs/block'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Cancun })
const header = createBlockHeader({ excessBlobGas: 1_000_000n }, { common })
console.log(`Blob gas price at excessBlobGas=1_000_000: ${header.getBlobGasPrice()}`)Prague+ blocks carry a requestsHash over sorted consensus-layer requests. Use genRequestsRoot() and @ethereumjs/util request types:
// ./examples/clrequests.ts
import { createBlock, genRequestsRoot } from '@ethereumjs/block'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { bytesToHex, createCLRequest, hexToBytes } from '@ethereumjs/util'
import { sha256 } from '@noble/hashes/sha2.js'
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague })
const depositData = hexToBytes(
'0x00ac842878bb70009552a4cfcad801d6e659c50bd50d7d03306790cb455ce7363c5b6972f0159d170f625a99b2064dbefc010000000000000000000000818ccb1c4eda80270b04d6df822b1e72dd83c3030040597307000000a747f75c72d0cf0d2b52504c7385b516f0523e2f0842416399f42b4aee5c6384a5674f6426b1cc3d0827886fa9b909e616f5c9f61f986013ed2b9bf37071cbae951136265b549f44e3c8e26233c0433e9124b7fd0dc86e82f9fedfc0a179d7690000000000000000',
)
const withdrawalData = hexToBytes(
'0x01000000000000000000000000000000000000000001000000000000000000000de0b6b3a7640000',
)
const consolidationData = hexToBytes('0x020000000100000000000000000000000000000000000001')
// Requests must be sorted by type (Deposit=0, Withdrawal=1, Consolidation=2)
const requests = [
createCLRequest(depositData),
createCLRequest(withdrawalData),
createCLRequest(consolidationData),
]
const requestsHash = genRequestsRoot(requests, sha256)
const block = createBlock({ header: { requestsHash } }, { common })
console.log(`Created ${requests.length} CL requests`)
console.log(`requestsHash: ${bytesToHex(requestsHash)}`)
console.log(`Block hash: ${bytesToHex(block.hash())}`)See the canonical Amsterdam overview in @ethereumjs/vm for release ↔ spec tracking.
When EIP-7928 is active (Hardfork.Amsterdam, experimental), blocks carry a blockAccessListHash header field. Compute it with @ethereumjs/util or obtain it from runBlock({ generate: true }) in the VM.
// ./examples/blockAccessListHash.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { bytesToHex, createBlockLevelAccessListFromJSON } from '@ethereumjs/util'
const main = () => {
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Amsterdam })
const balJson = [
{
address: '0x0000000000000000000000000000000000000001',
storageChanges: [],
storageReads: [],
balanceChanges: [{ blockAccessIndex: '0x01', postBalance: '0x03e8' }],
nonceChanges: [],
codeChanges: [],
},
]
const bal = createBlockLevelAccessListFromJSON(balJson)
const block = createBlock(
{
header: {
blockAccessListHash: bal.hash(),
},
},
{ common, skipConsensusFormatValidation: true },
)
console.log(`blockAccessListHash: ${bytesToHex(block.header.blockAccessListHash!)}`)
console.log(`matches BAL hash: ${bytesToHex(bal.hash())}`)
console.log(`hash length: ${block.header.blockAccessListHash!.length} bytes`)
}
void main()When EIP-7843 is active (Hardfork.Amsterdam, experimental), blocks carry a slotNumber header field. Set it explicitly when constructing blocks — runBlock({ generate: true }) does not populate it automatically.
// ./examples/blockSlotNumber.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
const main = () => {
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Amsterdam })
const block = createBlock(
{
header: {
slotNumber: 42n,
},
},
{ common, skipConsensusFormatValidation: true },
)
console.log(`slotNumber: ${block.header.slotNumber}`)
}
void main()Post-merge blocks (Hardfork.Paris and higher) use PoS header rules — difficulty 0, no ommers, and related EIP-3675 changes:
// ./examples/pos.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Mainnet } from '@ethereumjs/common'
const common = new Common({ chain: Mainnet })
const block = createBlock(
{
// Provide your block data here or use default values
},
{ common },
)
console.log(`Proof-of-Stake (default) block created with hardfork=${block.common.hardfork()}`)Pre-merge or dedicated PoW chains:
// ./examples/pow.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Chainstart })
console.log(common.consensusType()) // 'pow'
console.log(common.consensusAlgorithm()) // 'ethash'
createBlock({}, { common })
console.log(`Old Proof-of-Work block created`)Pass calcDifficultyFromHeader with the parent header to auto-compute difficulty.
For historical PoA testnet blocks (e.g. Goerli). Use createSealedCliqueBlock() with a cliqueSigner key to seal on instantiation:
// ./examples/clique.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Hardfork } from '@ethereumjs/common'
import { goerliChainConfig } from '@ethereumjs/testdata'
const common = new Common({ chain: goerliChainConfig, hardfork: Hardfork.Chainstart })
console.log(common.consensusType()) // 'poa'
console.log(common.consensusAlgorithm()) // 'clique'
createBlock({ header: { extraData: new Uint8Array(97) } }, { common })
console.log(`Old Clique Proof-of-Authority block created`)Clique utility functions (cliqueSigner, cliqueVerifySignature, …) throw outside a PoA context.
We provide hybrid ESM/CJS builds for all our libraries. With the v10 breaking release round from Spring 2025, all libraries are "pure-JS" by default and we have eliminated all hard-wired WASM code. Additionally we have substantially lowered the bundle sizes, reduced the number of dependencies, and cut out all usages of Node.js-specific primitives (like the Node.js event emitter).
It is easily possible to run a browser build of one of the EthereumJS libraries within a modern browser using the provided ESM build. For a setup example see ./examples/browser.html.
Generated TypeDoc documentation.
Every Block / BlockHeader is built with a Common instance that determines the active hardfork, EIP set, and validation rules. See @ethereumjs/common for chain configuration.
WASM crypto backends can be plugged in via Common.customCrypto — see the common README.
Tests in the tests directory are partly outdated; primary coverage comes from BlockchainTests in @ethereumjs/vm.
To wrap ethereum/tests JSON fixtures locally, use wrap-ethereum-test.sh.
The EthereumJS GitHub organization and its repositories are managed by members of the former Ethereum Foundation JavaScript team and the broader Ethereum community. If you want to join for work or carry out improvements on the libraries see the developer docs for an overview of current standards and tools and review our code of conduct.