Skip to main content
This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.

Overview

Opcode: 0x41 Introduced: Frontier (EVM genesis) COINBASE retrieves the address of the block beneficiary - the account that receives the block reward and transaction fees for the current block. This is typically the miner’s address (pre-merge) or validator’s address (post-merge).

Specification

Stack Input:
(none)
Stack Output:
coinbase_address (as u256)
Gas Cost: 2 (GasQuickStep) Operation:
stack.push(block.coinbase as u256)

Behavior

COINBASE pushes the 20-byte beneficiary address onto the stack as a 256-bit unsigned integer. The address is right-aligned (lower-order bytes):
Address: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
As u256: 0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb
         └─ 12 zero bytes ─┘└────────── 20 address bytes ──────────┘

Examples

Basic Usage

import { coinbase } from '@tevm/voltaire/evm/block';
import { createFrame } from '@tevm/voltaire/evm/Frame';

const minerAddress = new Uint8Array([
  0x74, 0x2d, 0x35, 0xcc, 0x66, 0x34, 0xc0, 0x53,
  0x29, 0x25, 0xa3, 0xb8, 0x44, 0xbc, 0x9e, 0x75,
  0x95, 0xf0, 0xbe, 0xb0
]);

const frame = createFrame({
  stack: [],
  blockContext: {
    block_coinbase: minerAddress
  }
});

const err = coinbase(frame);
console.log(frame.stack); // [0x742d35cc6634c0532925a3b844bc9e7595f0beb0]
console.log(frame.gasRemaining); // Original - 2

Extract Address from Stack

// Execute COINBASE
coinbase(frame);
const coinbaseU256 = frame.stack[0];

// Convert u256 back to address
const addressBytes = new Uint8Array(20);
for (let i = 0; i < 20; i++) {
  addressBytes[19 - i] = Number((coinbaseU256 >> BigInt(i * 8)) & 0xFFn);
}

console.log(addressBytes); // Original 20-byte address

Compare with Current Address

import { address } from '@tevm/voltaire/evm/context';

// Get coinbase
coinbase(frame);
const coinbaseAddr = frame.stack[0];

// Get current contract address
address(frame);
const currentAddr = frame.stack[0];

// Check if contract is coinbase
const isCoinbase = coinbaseAddr === currentAddr;

Gas Cost

Cost: 2 gas (GasQuickStep) COINBASE is one of the cheapest operations, sharing the GasQuickStep tier with:
  • TIMESTAMP (0x42)
  • NUMBER (0x43)
  • DIFFICULTY (0x44)
  • GASLIMIT (0x45)
  • CHAINID (0x46)
Comparison:
  • COINBASE: 2 gas
  • SELFBALANCE: 5 gas
  • BLOCKHASH: 20 gas
  • BALANCE (cold): 2600 gas

Common Usage

Miner Tipping

contract MinerTip {
    // Send tip directly to block producer
    function tipMiner() external payable {
        require(msg.value > 0, "No tip sent");
        payable(block.coinbase).transfer(msg.value);
    }
}

Coinbase Verification

contract OnlyMiner {
    modifier onlyMiner() {
        require(msg.sender == block.coinbase, "Only miner can call");
        _;
    }

    function privilegedOperation() external onlyMiner {
        // Only callable by block producer
    }
}

Flashbots/MEV Protection

contract AntiMEV {
    // Ensure transaction is included by specific validator
    function protectedSwap(address expectedCoinbase) external {
        require(block.coinbase == expectedCoinbase, "Wrong validator");
        // Execute swap
    }
}

Block Producer Allowlist

contract ValidatorGated {
    mapping(address => bool) public approvedValidators;

    modifier onlyApprovedValidator() {
        require(approvedValidators[block.coinbase], "Validator not approved");
        _;
    }

    function sensitiveOperation() external onlyApprovedValidator {
        // Only execute if produced by approved validator
    }
}

Historical Validator Tracking

contract ValidatorTracker {
    struct BlockInfo {
        uint256 blockNumber;
        address validator;
        uint256 timestamp;
    }

    BlockInfo[] public history;

    function recordBlock() external {
        history.push(BlockInfo({
            blockNumber: block.number,
            validator: block.coinbase,
            timestamp: block.timestamp
        }));
    }
}

Pre-Merge vs Post-Merge

Pre-Merge (PoW)

// Coinbase = Miner's address
contract MinerReward {
    // Miners could redirect rewards
    function() external payable {
        // Miner can set coinbase to this contract
        // to receive rewards + fees here
    }
}
Characteristics:
  • Miner can set coinbase to any address
  • Often set to mining pool contract
  • Can change between blocks

Post-Merge (PoS)

// Coinbase = Validator's fee recipient
contract ValidatorOperator {
    mapping(address => address) public feeRecipients;

    // Validators configure their fee recipient
    function setFeeRecipient(address recipient) external {
        feeRecipients[msg.sender] = recipient;
    }
}
Characteristics:
  • Set by validator client configuration
  • Typically validator’s withdrawal address
  • More predictable than PoW mining pools

Security Considerations

Centralization Risk

Relying on block.coinbase for access control creates centralization:
// RISKY: Single point of failure
contract CentralizedControl {
    function privilegedAction() external {
        require(msg.sender == block.coinbase, "Only validator");
        // Critical operation controlled by single validator
    }
}
Mitigation: Use multi-signature or DAO governance instead of validator-gated logic.

Validator Collusion

Validators can coordinate to manipulate coinbase-dependent logic:
// VULNERABLE: Validators can coordinate
contract CoinbaseDependent {
    mapping(address => uint256) public validatorScores;

    function rewardValidator() external {
        validatorScores[block.coinbase] += 1;
    }
}
Attack:
  • Multiple validators coordinate
  • Take turns producing blocks
  • Maximize collective score

MEV Considerations

block.coinbase enables MEV-aware contract designs:
contract MEVAware {
    // Pay validators to include transaction
    function urgentSwap() external payable {
        uint256 validatorBribe = msg.value / 10; // 10% to validator
        payable(block.coinbase).transfer(validatorBribe);

        // Execute swap with remaining value
    }
}

Coinbase Replay Attacks

Be careful with coinbase-based authentication across chains:
// VULNERABLE: Validator could exist on multiple chains
contract CrossChainVulnerable {
    function authenticate() external view returns (bool) {
        return msg.sender == block.coinbase; // Same validator on different chain!
    }
}

// SAFE: Include chain ID
contract CrossChainSafe {
    function authenticate(uint256 expectedChain) external view returns (bool) {
        require(block.chainid == expectedChain, "Wrong chain");
        return msg.sender == block.coinbase;
    }
}

Implementation

/**
 * COINBASE opcode (0x41) - Get block coinbase address
 */
export function coinbase(frame: FrameType): EvmError | null {
  // Consume gas (GasQuickStep = 2)
  frame.gasRemaining -= 2n;
  if (frame.gasRemaining < 0n) {
    frame.gasRemaining = 0n;
    return { type: "OutOfGas" };
  }

  // Convert 20-byte address to u256
  const coinbaseAddr = frame.evm.blockContext.block_coinbase;
  let coinbaseU256 = 0n;
  for (let i = 0; i < 20; i++) {
    coinbaseU256 = (coinbaseU256 << 8n) | BigInt(coinbaseAddr[i]);
  }

  // Push to stack
  if (frame.stack.length >= 1024) return { type: "StackOverflow" };
  frame.stack.push(coinbaseU256);

  frame.pc += 1;
  return null;
}

Edge Cases

Zero Address Coinbase

// Coinbase set to 0x0000...0000
const frame = createFrame({
  blockContext: {
    block_coinbase: new Uint8Array(20) // All zeros
  }
});

coinbase(frame);
console.log(frame.stack); // [0n]

Maximum Address Value

// Coinbase = 0xFFFF...FFFF
const frame = createFrame({
  blockContext: {
    block_coinbase: new Uint8Array(20).fill(0xFF)
  }
});

coinbase(frame);
console.log(frame.stack); // [0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF]

Stack Overflow

// Stack already full (1024 items)
const frame = createFrame({
  stack: new Array(1024).fill(0n),
  blockContext: { block_coinbase: minerAddress }
});

const err = coinbase(frame);
console.log(err); // { type: "StackOverflow" }

Out of Gas

// Insufficient gas
const frame = createFrame({
  gasRemaining: 1n,
  blockContext: { block_coinbase: minerAddress }
});

const err = coinbase(frame);
console.log(err); // { type: "OutOfGas" }
console.log(frame.gasRemaining); // 0n

Benchmarks

Performance:
  • Address to u256 conversion: O(20) - iterate 20 bytes
  • Stack push: O(1)
Gas efficiency:
  • 2 gas per query
  • ~500,000 queries per million gas
  • One of the cheapest EVM operations

References