китаев.tech

EOA vs Contract Account

Два типа аккаунтов в Ethereum: кто подписывает транзакции, где хранится код и как отличить кошелёк от смарт-контракта через RPC.

EOA vs Contract Account

В Ethereum любой адрес принадлежит аккаунту. Аккаунты бывают двух типов: EOA и Contract Account.

EOA управляется приватным ключом. Contract Account управляется кодом смарт-контракта, который уже лежит в сети.


Главное отличие

Тип аккаунтаКто управляетЕсть кодМожет подписывать
EOAПриватный ключНетДа
Contract AccountКод контрактаДаНет

EOA — это обычный кошелёк: MetaMask, Rabby, Ledger, backend wallet.

Contract Account — это адрес смарт-контракта: ERC-20 токен, Uniswap пул, Safe multisig, NFT коллекция.


Проверяем адрес через RPC

typescript
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");

const addresses = [
  {
    name: "Vitalik EOA",
    address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
  },
  {
    name: "WETH contract",
    address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
  },
];

for (const item of addresses) {
  const code = await provider.getCode(item.address);
  const balance = await provider.getBalance(item.address);

  const type = code === "0x" ? "EOA" : "Contract Account";

  console.log(`${item.name}: ${type}`);
  console.log(`ETH balance: ${ethers.formatEther(balance)}`);
  console.log(`Has code: ${code !== "0x"}`);
  console.log("---");
}

У EOA getCode() вернёт 0x, потому что у кошелька нет bytecode. У контракта вернётся байткод, который Ethereum выполняет при вызове функций.


Как это работает

ethers.JsonRpcProvider() — подключается к Ethereum mainnet через публичный RPC.

provider.getCode(address) — читает байткод по адресу. Если результат 0x, в этом аккаунте нет кода.

provider.getBalance(address) — читает ETH-баланс. Баланс может быть и у EOA, и у контракта.

code === "0x" — простой способ отличить обычный кошелёк от уже задеплоенного контракта.


Почему Contract Account не подписывает транзакции

Транзакцию в Ethereum всегда начинает EOA. Для этого нужен приватный ключ и цифровая подпись.

Контракт не хранит приватный ключ и не может сам отправить транзакцию. Он выполняется только тогда, когда его вызывает EOA или другой контракт внутри уже запущенной транзакции.

text
EOA подписал транзакцию

Контракт A получил вызов

Контракт A вызвал Контракт B

Это значит, что смарт-контракт может вызывать другие контракты, но не может первым начать действие в сети.


Частые ошибки

Считать любой адрес кошельком → адрес может принадлежать контракту. Перед отправкой средств или вызовом функций проверяйте getCode().

Думать, что контракт может подписать сообщение → у контракта нет приватного ключа. Для contract wallets используют отдельные стандарты проверки подписи, например EIP-1271.

Проверять только баланс → ETH-баланс не говорит о типе аккаунта. Контракт тоже может хранить ETH.


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.