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: 0x32 Introduced: Frontier (EVM genesis) ORIGIN pushes the address of the account that originated the transaction (tx.origin) onto the stack. This address never changes throughout the entire call chain, unlike CALLER which changes with each call.

Specification

Stack Input:
[]
Stack Output:
origin (uint160 as uint256)
Gas Cost: 2 (GasQuickStep) Operation:
stack.push(transaction.origin)

Behavior

ORIGIN provides the address of the externally owned account (EOA) that signed and initiated the transaction. This value remains constant throughout the entire execution, regardless of how many contract calls are made. Key characteristics:
  • Always an EOA (never a contract address)
  • Immutable throughout transaction execution
  • Same value in all contracts called during transaction
  • Cannot be a contract (contracts cannot initiate transactions)

Examples

Basic Usage

import { origin } from '@tevm/voltaire/evm/context';
import { createFrame } from '@tevm/voltaire/evm/Frame';
import * as Address from '@tevm/voltaire/Address';

// Transaction originated by this EOA
const originAddr = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb');
const frame = createFrame({ stack: [] });

const err = origin(frame, originAddr);

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

Call Chain Comparison

contract ContractC {
    function check() public view returns (address txOrigin, address caller) {
        return (tx.origin, msg.sender);
    }
}

contract ContractB {
    function forward(ContractC c) public returns (address, address) {
        return c.check();
    }
}

contract ContractA {
    function start(ContractB b, ContractC c) public returns (address, address) {
        return b.forward(c);
    }
}

// Call chain: EOA (0xAAA) → A (0xBBB) → B (0xCCC) → C (0xDDD)
//
// In ContractC.check():
// - tx.origin = 0xAAA (EOA that started transaction)
// - msg.sender = 0xCCC (ContractB called us)

Gas Cost

Cost: 2 gas (GasQuickStep) ORIGIN shares the lowest gas cost tier with other environment access opcodes:
  • ADDRESS (0x30)
  • CALLER (0x33)
  • CALLVALUE (0x34)
  • CALLDATASIZE (0x36)
  • CODESIZE (0x38)
  • GASPRICE (0x3a)
  • RETURNDATASIZE (0x3d)

Common Usage

Logging Transaction Source

contract EventLogger {
    event Action(address indexed origin, address indexed sender, string action);

    function logAction(string memory action) public {
        emit Action(tx.origin, msg.sender, action);
        // tx.origin: Who initiated the transaction
        // msg.sender: Who called this contract
    }
}

Gas Refunds

contract GasRefunder {
    function expensiveOperation() public {
        // ... expensive operations ...

        // Refund gas to transaction originator
        uint256 gasUsed = gasleft();
        payable(tx.origin).transfer(gasUsed * tx.gasprice);
    }
}

Security

CRITICAL: Never Use for Authorization

VULNERABLE pattern:
// EXTREMELY DANGEROUS - DO NOT USE
contract Vulnerable {
    address public owner;

    function withdraw() public {
        require(tx.origin == owner, "Not owner");  // WRONG!
        payable(owner).transfer(address(this).balance);
    }
}
Attack scenario:
contract Attacker {
    Vulnerable victim;

    constructor(Vulnerable _victim) {
        victim = _victim;
    }

    function attack() public {
        // Trick the owner into calling this
        victim.withdraw();
    }
}

// Attack flow:
// 1. Owner (tx.origin) calls Attacker.attack()
// 2. Attacker.attack() calls Vulnerable.withdraw()
// 3. tx.origin == owner ✓ (passes the check!)
// 4. Funds are stolen
SAFE pattern - use msg.sender:
contract Safe {
    address public owner;

    function withdraw() public {
        require(msg.sender == owner, "Not owner");  // CORRECT!
        payable(owner).transfer(address(this).balance);
    }
}

tx.origin vs msg.sender

Critical distinction:
Propertytx.originmsg.sender
ValueOriginal EOAImmediate caller
Changes in call chainNoYes
Can be contractNeverYes
Safe for authNOYES
OpcodeORIGIN (0x32)CALLER (0x33)
Example:
User EOA (0xAAA) → Contract A (0xBBB) → Contract B (0xCCC)

In Contract B:
- tx.origin = 0xAAA (never changes)
- msg.sender = 0xBBB (Contract A called us)

Phishing Attack Vector

// Attacker creates malicious contract
contract Phishing {
    Vulnerable target;

    function harmlessLooking() public {
        // Owner thinks this is safe to call
        // But it triggers the vulnerable withdraw
        target.withdraw();
        // tx.origin is still the owner, so it works!
    }
}

Limited Valid Use Cases

Valid (but rare) use case - gas payment:
contract Relayer {
    function executeForUser(address target, bytes memory data) public {
        // Meta-transaction: we pay gas, user signs intent
        (bool success,) = target.call(data);
        require(success);

        // Charge the original user (who signed the transaction)
        // This is ONE OF THE RARE valid uses of tx.origin
        payable(tx.origin).transfer(calculateGasCost());
    }
}
Even better - explicit parameter:
contract BetterRelayer {
    function executeForUser(
        address user,
        address target,
        bytes memory data
    ) public {
        // Don't use tx.origin at all
        (bool success,) = target.call(data);
        require(success);

        payable(user).transfer(calculateGasCost());
    }
}

Implementation

import { consumeGas } from "../Frame/consumeGas.js";
import { pushStack } from "../Frame/pushStack.js";
import { toU256 } from "../../primitives/Address/AddressType/toU256.js";

/**
 * ORIGIN opcode (0x32) - Get execution origination address
 *
 * Stack: [] => [origin]
 * Gas: 2 (GasQuickStep)
 */
export function origin(
  frame: FrameType,
  origin: Address
): EvmError | null {
  const gasErr = consumeGas(frame, 2n);
  if (gasErr) return gasErr;

  const originU256 = toU256(origin);
  const pushErr = pushStack(frame, originU256);
  if (pushErr) return pushErr;

  frame.pc += 1;
  return null;
}

Edge Cases

Stack Overflow

// Stack at maximum capacity
const frame = createFrame({ stack: new Array(1024).fill(0n) });
const err = origin(frame, originAddr);

console.log(err); // { type: "StackOverflow" }

Out of Gas

// Insufficient gas
const frame = createFrame({ gasRemaining: 1n });
const err = origin(frame, originAddr);

console.log(err); // { type: "OutOfGas" }

Zero Address Origin

// Theoretically invalid (can't sign transaction)
// but handled by EVM
const frame = createFrame({});
const err = origin(frame, Address('0x0000000000000000000000000000000000000000'));

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

Best Practices

❌ DON’T: Use for authorization

// WRONG
require(tx.origin == owner);

✅ DO: Use msg.sender for authorization

// CORRECT
require(msg.sender == owner);

❌ DON’T: Trust tx.origin in access control

// WRONG
modifier onlyOwner() {
    require(tx.origin == owner);
    _;
}

✅ DO: Use for logging/analytics only

// ACCEPTABLE
event UserAction(address indexed origin, string action);
emit UserAction(tx.origin, "purchased");

⚠️ CAUTION: Meta-transactions

// Acceptable but prefer explicit parameters
function chargeUser() internal {
    payable(tx.origin).transfer(gasCost);
}

References