An ERC-20 deposit watcher can filter Transfer logs. A native ETH deposit watcher has more work to do: top-level transaction values are visible in transaction objects, but transfers inside contract execution normally need a trace. Individual contracts may emit their own payment events, but there is no universal native-ETH event on mainnet today.

EIP-7708 proposes that missing event. Qualifying nonzero ETH transfers between different accounts emit a log from a fixed system address. Indexers can then use familiar log filters for those movements instead of tracing every transaction solely to discover value transfers.

Status checked September 16, 2026: EIP-7708 is in Review and scheduled for Glamsterdam. It is not active on Ethereum mainnet. The decoder below targets the published proposal; use a compatible test network to exercise actual emission. The Glamsterdam roadmap tracks the upgrade, not a promise that today’s mainnet endpoint already supports these logs.

EIP-7708 coverage: qualifying transaction value, CALL, contract creation and SELFDESTRUCT transfers produce system-address Transfer logs; fees, burns and withdrawals require other accounting

The log format

The proposed event has the same ABI layout as the familiar ERC-20 event:

event Transfer(address indexed from, address indexed to, uint256 value);

It is emitted by the protocol, not by calling a token contract.

Field Value
address 0xfffffffffffffffffffffffffffffffffffffffe
topics[0] 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
topics[1] Source address, left-padded to 32 bytes
topics[2] Destination address, left-padded to 32 bytes
data Transferred wei, ABI-encoded as one uint256

The first topic is keccak256("Transfer(address,address,uint256)"). The two addresses are indexed, while the amount is in the data field. An ABI fragment that omits indexed has the same signature hash but the wrong decoder layout.

Always filter by both the system address and event topic. The signature alone does not identify the asset. ERC-721 uses the same signature with a different indexed layout, and an arbitrary contract can emit a look-alike event. A non-system emitter is not automatically a trustworthy ERC-20 contract either.

Which transfers produce logs?

The specification requires a nonzero value and a different source and destination. It covers these paths:

DELEGATECALL does not itself transfer ETH. It executes code in another account’s context. If that code subsequently performs a value-bearing CALL, the balance transfer from the executing context is the event of interest. CALLCODE does not move ETH to the code-source address either.

The same ETH can move several times during one transaction. An executor might receive ETH, forward it to another contract and receive a refund. Those are separate transfers, not three independent deposits from the original sender. Preserve the transaction and log ordering instead of flattening the result into a single tx.value field.

Which balance changes remain outside the logs?

Zero-value and self-transfers are excluded. This differs from ERC-20, whose standard requires a Transfer event for zero-value transfers. Reusing an ERC-20 decoder is fine; reusing every ERC-20 event expectation is not.

Gas fees are excluded. Neither the base-fee burn nor the priority fee paid to the execution fee recipient produces an EIP-7708 transfer log. For an ordinary execution transaction, derive the execution fee from gasUsed * effectiveGasPrice in its receipt. The base-fee portion is gasUsed * baseFeePerGas, and the tip portion is gasUsed * (effectiveGasPrice - baseFeePerGas). Blob fees, where applicable, are separate. A direct ETH payment to the fee recipient through transaction value or CALL is still a value transfer, not a gas-fee payment.

Beacon-chain withdrawals are excluded. Their amounts appear in the execution payload’s withdrawal list, outside transaction receipts. Include that list when reconciling account balances.

Reverted transfers leave no persistent log. If a call transfers ETH and later reverts, its transfer log is rolled back with the call. A parent that catches the failure can continue and emit other surviving logs. If the whole transaction reverts, its transfer logs disappear, although gas fees are still charged.

These exclusions mean that summing Transfer logs is not a complete ETH balance reconciliation. Fees and withdrawals can change a balance without appearing in this event stream.

A runnable decoder and subscription

This is a live diagnostic stream, not a durable indexer. It prints an upsert for a received log and a remove when the node reports that log as reorged out. It deliberately stops on a connection loss instead of silently reconnecting and pretending no events were missed.

Use Node.js 22 or newer. Install the dependencies and save the program as eth-transfer-logs.mjs:

npm install ethers@6 ws
import WebSocket from "ws";
import { Interface } from "ethers";

const url = process.env.RPC_URL;

if (!url || !["ws:", "wss:"].includes(new URL(url).protocol)) {
  throw new Error("Set RPC_URL to a Glamsterdam-compatible WebSocket endpoint");
}

const systemAddress = "0xfffffffffffffffffffffffffffffffffffffffe";
const iface = new Interface([
  "event Transfer(address indexed from, address indexed to, uint256 value)",
]);
const topic = iface.getEvent("Transfer").topicHash;

const socket = new WebSocket(url, { handshakeTimeout: 15000 });
let subscription;
let stopping = false;

function fail(message) {
  console.error(message);
  process.exitCode = 1;
  socket.terminate();
}

socket.on("open", () => {
  socket.send(
    JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "eth_subscribe",
      params: ["logs", { address: systemAddress, topics: [topic] }],
    })
  );
});

socket.on("message", (bytes) => {
  try {
    const message = JSON.parse(bytes.toString());

    if (message.id === 1) {
      if (message.error) {
        throw new Error(JSON.stringify(message.error));
      }

      if (typeof message.result !== "string") {
        throw new Error("Missing subscription identifier");
      }

      subscription = message.result;
      console.error("Subscribed. This is a live feed, not a historical backfill.");
      return;
    }

    if (
      message.method !== "eth_subscription" ||
      !subscription ||
      message.params?.subscription !== subscription
    ) {
      return;
    }

    const log = message.params.result;

    if (
      log.address?.toLowerCase() !== systemAddress ||
      log.topics?.length !== 3 ||
      log.topics[0]?.toLowerCase() !== topic ||
      !/^0x[0-9a-fA-F]{64}$/.test(log.data)
    ) {
      throw new Error("Unexpected EIP-7708 log layout");
    }

    const parsed = iface.parseLog(log);

    if (!parsed) {
      throw new Error("Unrecognized transfer log");
    }

    // A removal must identify the same observation as its original upsert.
    // Include the block hash so competing chain branches remain distinct.
    const { from, to, value } = parsed.args;

    console.log(
      JSON.stringify({
        action: log.removed ? "remove" : "upsert",
        id: `${log.blockHash}:${log.transactionHash}:${log.logIndex}`,
        asset: "ETH",
        from,
        to,
        // Keep wei as a string; ordinary ETH amounts exceed safe integers.
        wei: value.toString(),
        blockNumber: Number(BigInt(log.blockNumber)),
        blockHash: log.blockHash,
        transactionHash: log.transactionHash,
        logIndex: Number(BigInt(log.logIndex)),
      })
    );
  } catch (error) {
    fail(`Subscription failed: ${error.message}`);
  }
});

socket.on("error", (error) => fail(`WebSocket failed: ${error.message}`));

// Reconnecting alone would hide a gap: subscriptions do not replay missed logs.
socket.on("close", (code) => {
  if (!stopping) {
    console.error(`Feed closed (${code}); backfill the gap before resuming.`);
    process.exitCode = 1;
  }
});

process.on("SIGINT", () => {
  stopping = true;
  socket.terminate();
});

Set RPC_URL to your chosen compatible test-network endpoint, then run:

node eth-transfer-logs.mjs

There is no default mainnet URL: running this filter against current Ethereum mainnet will not make the proposal’s logs exist. BLAZED.sh’s injected ws://eth:8545 endpoint serves mainnet, not a Glamsterdam testnet.

The system address is compared in lowercase because JSON-RPC address casing is not an identity distinction. The amount remains a decimal string in the output; converting wei to a JavaScript Number would lose precision for ordinary ETH amounts.

Reorgs and reconnects are separate problems

A removed notification reverses an earlier observation. Ignoring it does not make an indexer reorg-safe: the original record may already have triggered a payment or webhook. The example emits the same identity with a remove action so a consumer can undo that observation.

A dropped connection is different. Subscriptions are connection-scoped and do not replay the period you missed. A reliable consumer needs persisted progress and a bounded eth_getLogs backfill, followed by a canonical-hash check. Use block hash, transaction hash and log index as an observation identity, and retain enough history to reverse events from an orphaned branch.

Confirmation depth is an application policy, not an absolute guarantee. A deposit service may wait for stronger finality than an analytics dashboard. Either way, distinguish “observed” from “accepted” in storage and webhook payloads. Our ERC-20 webhook tutorial covers the surrounding delivery pattern; verify the same reorg and replay behavior when changing its asset source.

What still needs a tracer?

EIP-7708 answers where qualifying native value moved. It does not supply the complete call tree, zero-value calls, opcode-level gas use or the reasons behind a reverted attempt. A swap simulation that needs those details still needs tracing or a purpose-built executor result.

The logs are also not retroactive. Historical blocks before activation do not acquire new receipt logs. If your product offers a user’s complete ETH-transfer history, keep a separate pre-fork trace-based path and record the activation boundary for the network being indexed.

For post-fork monitoring, the useful change is narrower and concrete: the same eth_getLogs and subscription machinery used for token events can discover qualifying native ETH transfers. Keep the exclusions, canonical-block handling and activation boundary explicit, and the new event can replace tracing where tracing was only being used to find those transfers.