npm.io
10.1.3 • Published 1 week ago

@ethereumjs/tx

Licence
MPL-2.0
Version
10.1.3
Deps
5
Size
1.0 MB
Vulns
0
Weekly
0
Stars
2.8K

@ethereumjs/tx v10

NPM Package GitHub Issues Actions Status Code Coverage Discord

Implements schema and functions for the different Ethereum transaction types
  • All tx types up to Osaka
  • Tree-shakeable API
  • Controlled dependency set (1 external + @Noble crypto)
  • Unified tx type API
  • New type for EIP-7702 account abstraction
  • EIP-7594 PeerDAS Blob Transactions
  • 190KB bundle size (all tx types) (47KB gzipped)
  • WASM-free default + Fully browser ready

Runnable examples live in examples/.

Table of Contents

Installation

npm install @ethereumjs/tx

Getting Started

Prefer factory functions (createLegacyTx, createTx, …) over calling transaction class constructors directly. Every transaction needs a Common for chain ID, hardfork, and field validation.

import { Common, Mainnet } from '@ethereumjs/common'
import { createFeeMarket1559Tx } from '@ethereumjs/tx'
Hardfork Tx types introduced
chainstart Legacy (type 0)
spuriousDragon EIP-155 replay protection
berlin EIP-2930 access list (type 1)
london EIP-1559 fee market (type 2)
cancun EIP-4844 blob (type 3)
prague EIP-7702 set-code (type 4)
amsterdam EIP-2780 intrinsic split; EIP-7976 / EIP-7981 floor (experimental)

Properties are frozen by default (freeze: false to opt out).

Transaction Types

Gas Fee Market (EIP-1559, type 2)

Recommended tx type from london onward.

// ./examples/eip1559Tx.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import type { FeeMarketEIP1559TxData } from '@ethereumjs/tx'
import { createFeeMarket1559Tx } from '@ethereumjs/tx'
import { bytesToHex } from '@ethereumjs/util'

const common = new Common({ chain: Mainnet, hardfork: Hardfork.Cancun })

const txData: FeeMarketEIP1559TxData = {
  data: '0x1a8451e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  gasLimit: '0x02625a00',
  maxPriorityFeePerGas: '0x01',
  maxFeePerGas: '0xff',
  nonce: '0x00',
  to: '0xcccccccccccccccccccccccccccccccccccccccc',
  value: '0x0186a0',
  v: '0x01',
  r: '0xafb6e247b1c490e284053c87ab5f6b59e219d51f743f7a4d83e400782bc7e4b9',
  s: '0x479a268e0e0acd4de3f1e28e4fac2a6b32a4195e8dfa9d19147abe8807aa6f64',
  chainId: '0x01',
  accessList: [],
  type: '0x02',
}

const tx = createFeeMarket1559Tx(txData, { common })
console.log(bytesToHex(tx.hash())) // 0x6f9ef69ccb1de1aea64e511efd6542541008ced321887937c95b03779358ec8a
Access List (EIP-2930, type 1)
// ./examples/accessListTx.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import type { AccessList2930TxData } from '@ethereumjs/tx'
import { createAccessList2930Tx } from '@ethereumjs/tx'
import { bytesToHex } from '@ethereumjs/util'

const common = new Common({ chain: Mainnet, hardfork: Hardfork.Berlin })

const txData: AccessList2930TxData = {
  data: '0x1a8451e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  gasLimit: '0x02625a00',
  gasPrice: '0x01',
  nonce: '0x00',
  to: '0xcccccccccccccccccccccccccccccccccccccccc',
  value: '0x0186a0',
  v: '0x01',
  r: '0xafb6e247b1c490e284053c87ab5f6b59e219d51f743f7a4d83e400782bc7e4b9',
  s: '0x479a268e0e0acd4de3f1e28e4fac2a6b32a4195e8dfa9d19147abe8807aa6f64',
  chainId: '0x01',
  accessList: [
    {
      address: '0x0000000000000000000000000000000000000101',
      storageKeys: [
        '0x0000000000000000000000000000000000000000000000000000000000000000',
        '0x00000000000000000000000000000000000000000000000000000000000060a7',
      ],
    },
  ],
  type: '0x01',
}

const tx = createAccessList2930Tx(txData, { common })
console.log(bytesToHex(tx.hash())) // 0x9150cdebad74e88b038e6c6b964d99af705f9c0883d7f0bbc0f3e072358f5b1d

For generating access lists from network state, use reportAccessList on VM.runTx() in @ethereumjs/vm.

Blob (EIP-4844 / EIP-7594, type 3)

Requires KZG on Common — see KZG Setup. Use serializeNetworkWrapper() for JSON-RPC submission (includes blobs); serialize() for block-body format.

// ./examples/blobTx.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import type { BlobEIP4844TxData } from '@ethereumjs/tx'
import { createBlob4844Tx } from '@ethereumjs/tx'
import { bytesToHex, getBlobs, randomBytes } from '@ethereumjs/util'
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)
  // EIP-4844 only
  const common4844 = new Common({
    chain: Mainnet,
    hardfork: Hardfork.Cancun,
    customCrypto: { kzg },
  })

  // EIP-4844 and EIP-7594
  const common4844and7594 = new Common({
    chain: Mainnet,
    hardfork: Hardfork.Osaka,
    customCrypto: { kzg },
  })
  const setups = [
    {
      title: 'Blob transaction (EIP-4844 only)',
      common: common4844,
      proofAmountComment: 'one proof per blob',
    },
    {
      title: 'Blob transaction (EIP-4844 + EIP-7594)',
      common: common4844and7594,
      proofAmountComment: '128 cells per blob + one proof per cell -> NUM_BLOBS * 128 proofs',
    },
  ]

  for (const setup of setups) {
    console.log(`\n${setup.title}:`)
    console.log('---------------------------------------')

    const blobsData = ['blob 1', 'blob 2', 'blob 3']
    console.log(`Blobs (Data) : "${blobsData.join('", "')}"`)
    // Final format, filled with a lot of 0s, added marker
    const blobs = getBlobs(blobsData)

    console.log('Generating tx...')

    const txData: BlobEIP4844TxData = {
      data: '0x1a8451e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
      gasLimit: 16_000_000n,
      maxPriorityFeePerGas: '0x01',
      maxFeePerGas: '0xff',
      maxFeePerBlobGas: '0xfff',
      nonce: '0x00',
      to: '0xcccccccccccccccccccccccccccccccccccccccc',
      value: '0x0186a0',
      v: '0x01',
      r: '0xafb6e247b1c490e284053c87ab5f6b59e219d51f743f7a4d83e400782bc7e4b9',
      s: '0x479a268e0e0acd4de3f1e28e4fac2a6b32a4195e8dfa9d19147abe8807aa6f64',
      chainId: '0x01',
      accessList: [],
      type: '0x05',
      blobs,
    }

    const tx = createBlob4844Tx(txData, { common: setup.common })

    console.log(`Tx hash               : ${bytesToHex(tx.hash())}`)
    console.log(`Num blobs             : ${tx.numBlobs()}`)
    console.log(`Blob versioned hashes : ${tx.blobVersionedHashes.join(', ')}`)
    console.log(`KZG commitments       : ${tx.kzgCommitments!.join(', ')}`)
    console.log(`First KZG (cell) proof: ${tx.kzgProofs![0]}`)
    console.log(`Num KZG (cell) proofs : ${tx.kzgProofs!.length} (${setup.proofAmountComment})`)
  }

  // To send a transaction via RPC, you can something like this:
  // const rawTx = tx.sign(privateKeyBytes).serializeNetworkWrapper()
  // myRPCClient.request('eth_sendRawTransaction', [rawTx]) // submits a transaction via RPC
  //
  // Also see ./sendRawSepoliaTx.ts example
}

void main()
EOA Code (EIP-7702, type 4)

Prague+ txs with an authorization list. See EIP-7702 Set-Code Transactions for a full sign-and-delegate workflow.

// ./examples/eoaCode7702Tx.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createEOACode7702Tx } from '@ethereumjs/tx'
import { type PrefixedHexString, createAddressFromPrivateKey, randomBytes } from '@ethereumjs/util'

const ones32 = `0x${'01'.repeat(32)}` as PrefixedHexString

const common = new Common({ chain: Mainnet, hardfork: Hardfork.Cancun, eips: [7702] })
const tx = createEOACode7702Tx(
  {
    authorizationList: [
      {
        chainId: '0x2',
        address: `0x${'20'.repeat(20)}`,
        nonce: '0x1',
        yParity: '0x1',
        r: ones32,
        s: ones32,
      },
    ],
    to: createAddressFromPrivateKey(randomBytes(32)),
  },
  { common },
)

console.log(
  `EIP-7702 EOA code tx created with ${tx.authorizationList.length} authorization list item(s).`,
)
Legacy (type 0)
// ./examples/legacyTx.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import type { LegacyTxData } from '@ethereumjs/tx'
import { createLegacyTx } from '@ethereumjs/tx'
import { bytesToHex, hexToBytes } from '@ethereumjs/util'

const txData: LegacyTxData = {
  nonce: '0x0',
  gasPrice: '0x09184e72a000',
  gasLimit: '0x2710',
  to: '0x0000000000000000000000000000000000000000',
  value: '0x00',
  data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057',
}

const common = new Common({ chain: Mainnet, hardfork: Hardfork.Istanbul })
const tx = createLegacyTx(txData, { common })

const privateKey = hexToBytes('0xe331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109')

const signedTx = tx.sign(privateKey)

const _serializedTx = signedTx.serialize()
console.log(bytesToHex(signedTx.hash())) // 0x894b72d87f8333fccd29d1b3aca39af69d97a6bc281e7e7a3a60640690a3cd2b

Transaction Factory

When the tx type is only known at runtime, use createTx() — it dispatches on the type field or RLP prefix:

// ./examples/txFactory.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { Capability, createTx } from '@ethereumjs/tx'

import type { EIP1559CompatibleTx } from '@ethereumjs/tx'

const common = new Common({ chain: Mainnet, hardfork: Hardfork.London })

const txData = { type: 2, maxFeePerGas: BigInt(20) } // Creates an EIP-1559 compatible transaction
const tx = createTx(txData, { common })

if (tx.supports(Capability.EIP1559FeeMarket)) {
  console.log(
    `The max fee per gas for this transaction is ${(tx as EIP1559CompatibleTx).maxFeePerGas}`,
  )
}

Factory methods: createTx, createTxFromRLP, createTxFromBlockBodyData, createTxFromRPC, createTxFromJSONRPCProvider.

Signing and Deserialization

Sign, serialize, and verify
// ./examples/transactions.ts

import { createLegacyTx, createLegacyTxFromBytesArray } from '@ethereumjs/tx'
import { bytesToHex, hexToBytes, randomBytes } from '@ethereumjs/util'

// Create and sign a contract-creation tx
const tx = createLegacyTx({
  nonce: 0,
  gasPrice: 100,
  gasLimit: 1_000_000_000,
  value: 0,
  data: '0x7f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d6552656700000000000000000000000000000000000000000000000000573360455760415160566000396000f20036602259604556330e0f600f5933ff33560f601e5960003356576000335700604158600035560f602b590033560f60365960003356573360003557600035335700',
})

const signedTx = tx.sign(randomBytes(32))
console.log(`Upfront cost: ${signedTx.getUpfrontCost()} wei`)
console.log(`Serialized: ${bytesToHex(signedTx.serialize())}`)

// Parse a signed legacy tx from its values array
const rawTx = [
  '0x',
  '0x09184e72a000',
  '0x2710',
  '0x0000000000000000000000000000000000000000',
  '0x',
  '0x7f7465737432000000000000000000000000000000000000000000000000000000600057',
  '0x1c',
  '0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab',
  '0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13',
].map(hexToBytes)

const parsed = createLegacyTxFromBytesArray(rawTx)
console.log(`Sender: ${parsed.getSenderAddress().toString()}`)
console.log(`Signature valid: ${parsed.verifySignature()}`)
Deserialize from RLP
// ./examples/txFromRLP.ts

import { Common, Mainnet } from '@ethereumjs/common'
import { createFeeMarket1559Tx, createTxFromRLP } from '@ethereumjs/tx'
import { bytesToHex, hexToBytes } from '@ethereumjs/util'

const common = new Common({ chain: Mainnet })
const privateKey = hexToBytes('0xe331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109')

const tx = createFeeMarket1559Tx(
  {
    type: 2,
    nonce: 0n,
    gasLimit: 21_000n,
    maxFeePerGas: 20n,
    maxPriorityFeePerGas: 1n,
    to: '0xcccccccccccccccccccccccccccccccccccccccc',
  },
  { common },
).sign(privateKey)

const roundTrip = createTxFromRLP(tx.serialize(), { common })

console.log(`Type preserved: ${roundTrip.type === tx.type}`)
console.log(`Hash match: ${bytesToHex(roundTrip.hash()) === bytesToHex(tx.hash())}`)
Deserialize with custom chain ID

When parsing txs from a non-mainnet chain, pass a matching Common:

// ./examples/customChainIdTx.ts

import { Hardfork, Mainnet, createCustomCommon } from '@ethereumjs/common'
import { createLegacyTxFromRLP } from '@ethereumjs/tx'
import { hexToBytes, toBytes } from '@ethereumjs/util'

const txData = hexToBytes(
  '0xf9010b82930284d09dc30083419ce0942d18de92e0f9aee1a29770c3b15c6cf8ac5498e580b8a42f43f4fb0000000000000000000000000000000000000000000000000000016b78998da900000000000000000000000000000000000000000000000000000000000cb1b70000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000001363e4f00000000000000000000000000000000000000000000000000000000000186a029a0fac36e66d329af0e831b2e61179b3ec8d7c7a8a2179e303cfed3364aff2bc3e4a07cb73d56e561ccbd838818dd3dea5fa0b5158577ffc61c0e6ec1f0ed55716891',
)

const common = createCustomCommon({ chainId: 3 }, Mainnet)
common.setHardfork(Hardfork.Prague)
const tx = createLegacyTxFromRLP(txData, { common })

if (
  tx.isValid() &&
  tx.getSenderAddress().toString() === '0x9dfd2d2b2ed960923f7bf2e8883d73f213f3b24b'
) {
  console.log('Correctly created the tx')
} else {
  console.error('Invalid tx')
}

Custom Chains

Use createCustomCommon() from @ethereumjs/common to override chainId (and optionally name) while keeping mainnet parameters:

// ./examples/customChainTx.ts

import { Hardfork, Mainnet, createCustomCommon } from '@ethereumjs/common'
import { createLegacyTx } from '@ethereumjs/tx'
import { createAddressFromPrivateKey, hexToBytes } from '@ethereumjs/util'

// In this example we create a transaction for a custom network.

// This custom network has the same params as mainnet,
// except for name, chainId, so we use the `Common.custom` method.
const customCommon = createCustomCommon(
  {
    name: 'my-network',
    chainId: 2134,
  },
  Mainnet,
  {
    hardfork: Hardfork.Prague,
  },
)

// We pass our custom Common object whenever we create a transaction
const opts = { common: customCommon }
const tx = createLegacyTx(
  {
    nonce: 0,
    gasPrice: 100,
    gasLimit: 1000000000,
    value: 100000,
  },
  opts,
)

// Once we created the transaction using the custom Common object, we can use it as a normal tx.

// Here we sign it and validate its signature
const privateKey = hexToBytes('0xe331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109')

const signedTx = tx.sign(privateKey)
const address = createAddressFromPrivateKey(privateKey)

if (signedTx.isValid() && signedTx.getSenderAddress().equals(address)) {
  console.log('Valid signature')
} else {
  console.log('Invalid signature')
}

console.log("The transaction's chain id is: ", signedTx.common.chainId().toString())

L2 example (xDai / Gnosis chain ID 100):

// ./examples/l2Tx.ts

import { Mainnet, createCustomCommon } from '@ethereumjs/common'
import { createLegacyTx } from '@ethereumjs/tx'
import { bytesToHex, createAddressFromString, hexToBytes } from '@ethereumjs/util'

const pk = hexToBytes('0x076247989df60a82f6e86e58104368676096f84e60972282ee00d4673a2bc9b9')
// xDai chain ID
const common = createCustomCommon({ chainId: 100 }, Mainnet)
const to = createAddressFromString('0x256e8f0ba532ad83a0debde7501669511a41a1f3')

const txData = {
  nonce: 0,
  gasPrice: 1000000000,
  gasLimit: 21000,
  to,
  value: 1,
}

const tx = createLegacyTx(txData, { common })
const signedTx = tx.sign(pk)
console.log(bytesToHex(signedTx.hash())) // 0xbf98f6f8700812ed6f2314275070256e11945fa48afd80fb301265f6a41a2dc2

EIP-7702 Set-Code Transactions

Full workflow: sign authorization items with @ethereumjs/util, attach to a type-4 tx, sign and serialize. Do not use real keys with funds.

// ./examples/setEOATx.ts

/**
 * This example shows how to initialize an EIP-7702 (Set EOA account code) transaction
 * WARNING: do NOT try this or sign this with any keys which have any value on any network
 * This is for educational purposes only.
 */

// This example will show how to self-delegate a fresh EOA account to the address `0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
// In the same transaction, it will also delegate another account to address `0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`
// It will self-delegate. Note that in a 7702-tx, you are free to include any authorization item.
// If the authorization item is valid (it has the correct nonce, and matches the chainId (or the chainId is 0))
// then it will delegate the code of the account **who signed that authorization item** to the address in that authority item
import { createEOACode7702Tx } from '@ethereumjs/tx'
import type { EOACode7702AuthorizationListItemUnsigned } from '@ethereumjs/util'
import {
  Address,
  eoaCode7702AuthorizationListBytesItemToJSON,
  eoaCode7702SignAuthorization,
  privateToAddress,
} from '@ethereumjs/util'

const privateKey = new Uint8Array(32).fill(0x20)
const privateKeyOther = new Uint8Array(32).fill(0x99)

const myAddress = new Address(privateToAddress(privateKey))

const unsignedAuthorizationListItemSelf: EOACode7702AuthorizationListItemUnsigned = {
  chainId: '0x1337', // This delegation will only work on the chain with chainId 0x1337
  address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  nonce: '0x01', // Since we are self-delegating we need account for the nonce being bumped of the account
}
const signedSelf = eoaCode7702SignAuthorization(unsignedAuthorizationListItemSelf, privateKey)
// To convert the bytes array to a human-readable form, use `eoaCode7702AuthorizationListBytesItemToJSON`
console.log(eoaCode7702AuthorizationListBytesItemToJSON(signedSelf))

const unsignedAuthorizationListItemOther: EOACode7702AuthorizationListItemUnsigned = {
  chainId: '0x', // The chainId 0 is special: this authorization will work on any chain which supports EIP-7702
  address: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
  nonce: '0x',
}
const signedOther = eoaCode7702SignAuthorization(
  unsignedAuthorizationListItemOther,
  privateKeyOther,
)

const authorizationList = [signedSelf, signedOther]

const unsignedTx = createEOACode7702Tx({
  authorizationList,
  to: myAddress, // Call into self, so call into own address into `0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
})

const signed = unsignedTx.sign(privateKey)

console.log(signed.toJSON())

KZG Setup

Blob transactions and KZG precompiles need a KZG implementation on Common.customCrypto.kzg:

// ./examples/initKZG.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
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)
  // Instantiate `common`
  const common = new Common({
    chain: Mainnet,
    hardfork: Hardfork.Cancun,
    customCrypto: { kzg },
  })

  console.log(`KZG configured: ${Boolean(common.customCrypto.kzg)}`)
}

void main()

Install micro-eth-signer and @paulmillr/trusted-setups as dependencies. The trusted setup is large and intentionally not bundled.

Sending a Transaction

See sendRawSepoliaTx.ts for submitting a signed blob tx via JSON-RPC. For blob txs use serializeNetworkWrapper(), not serialize().

Amsterdam Validation

See the canonical Amsterdam overview in @ethereumjs/vm.

Intrinsic gas and the calldata floor are two meters. Wallets take max of both (getMinimumGasLimit()). They share EIP-2780's recipient/value extras as a common base; they are not two names for the same formula.

Intrinsic (EIP-2780) splits the old flat 21_000. A value transfer to another account is still 21_000:

Part Gas
TX_BASE (txGas) 12_000
Recipient access 3_000
Value (+ transfer log) 6_000

Self-transfers skip the extras (12_000). getIntrinsicGas() still adds calldata execution cost, create, access-list, and 7702 auth on top.

Calldata floor (EIP-7623, raised by EIP-7976) is floor_base + totalCostFloorPerToken × tokens. Prague introduced the floor so data-heavy txs cannot underpay. Amsterdam's EIP-7976 counts 4 tokens per calldata byte (zero and non-zero alike) and sets totalCostFloorPerToken to 16 → 64 gas per byte. EIP-7981 adds access-list bytes to the same token count. EIP-2780 extras are the floor base (TX_BASE + recipient/value), not a second floor.

Minimum gasLimit: getMinimumGasLimit() = max(intrinsic, floor). Empty transfers still pass 21_000 (intrinsic ≥ floor). Calldata often cannot. First-touch state gas (EIP-8037) is not in this bound — use estimateTxGasDimensions() on @ethereumjs/vm.

// ./examples/calldataFloorGas.ts

import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import {
  countCalldataFloorTokens,
  createLegacyTx,
  getCalldataFloorGas,
  getEip2780FloorBaseGas,
  getEip2780RecipientRegularGas,
} from '@ethereumjs/tx'
import { createAddressFromPrivateKey, createZeroAddress, hexToBytes } from '@ethereumjs/util'

import type { LegacyTxInterface } from '@ethereumjs/tx'

const main = () => {
  const common = new Common({ chain: Mainnet, hardfork: Hardfork.Amsterdam })
  const senderKey = hexToBytes(`0x${'20'.repeat(32)}`)
  const sender = createAddressFromPrivateKey(senderKey)
  const other = createZeroAddress()

  const report = (label: string, tx: LegacyTxInterface) => {
    console.log(label)
    console.log(`  TX_BASE:      ${tx.common.param('txGas')}`)
    console.log(`  2780 extras:  ${getEip2780RecipientRegularGas(tx)}`)
    console.log(`  floor base:   ${getEip2780FloorBaseGas(tx)}`)
    console.log(`  floor tokens: ${countCalldataFloorTokens(tx)}`)
    console.log(`  intrinsic:    ${tx.getIntrinsicGas()}`)
    console.log(`  floor:        ${getCalldataFloorGas(tx)}`)
    console.log(`  minimum:      ${tx.getMinimumGasLimit()}`)
    console.log(`  gasLimit:     ${tx.gasLimit} valid=${tx.isValid()}`)
  }

  report(
    'Value transfer to another account (still 21_000)',
    createLegacyTx(
      { to: other, value: 1n, gasLimit: 21_000n, gasPrice: 10n },
      { common },
    ).sign(senderKey),
  )

  report(
    'Self-transfer (2780 extras skipped)',
    createLegacyTx(
      { to: sender, value: 1n, gasLimit: 21_000n, gasPrice: 10n },
      { common },
    ).sign(senderKey),
  )

  report(
    '100 calldata bytes (7976 floor exceeds 21_000)',
    createLegacyTx(
      { to: other, data: new Uint8Array(100).fill(1), gasLimit: 21_000n, gasPrice: 10n },
      { common },
    ).sign(senderKey),
  )
}

void main()

First-touch state gas is still a VM concern: @ethereumjs/vm Amsterdam gas dimensions.

Architecture

One directory per typed transaction (legacy/, 2930/, 1559/, 4844/, 7702/) with shared behavior in capabilities/. transactionFactory.ts dispatches createTx* calls. Every tx is built with a Common instance.

Browser

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.

Hardware Wallets

Use tx.getMessageToSign() for unsigned payloads. Legacy txs return an RLP values list; typed txs return serialized bytes. See ledgerSigner.mts for @ledgerhq/hw-app-eth integration.

API

Generated TypeDoc documentation.

WASM crypto backends can be plugged in via Common.customCrypto — see @ethereumjs/common.

EthereumJS

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.

License

MPL-2.0

Keywords