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
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 или другой контракт внутри уже запущенной транзакции.
EOA подписал транзакцию
↓
Контракт A получил вызов
↓
Контракт A вызвал Контракт BЭто значит, что смарт-контракт может вызывать другие контракты, но не может первым начать действие в сети.
Частые ошибки
Считать любой адрес кошельком → адрес может принадлежать контракту. Перед отправкой средств или вызовом функций проверяйте getCode().
Думать, что контракт может подписать сообщение → у контракта нет приватного ключа. Для contract wallets используют отдельные стандарты проверки подписи, например EIP-1271.
Проверять только баланс → ETH-баланс не говорит о типе аккаунта. Контракт тоже может хранить ETH.
Что дальше
- Провайдер vs Сайнер — как разделяются чтение данных и подпись транзакций
- Как работает транзакция изнутри — что происходит после подписи EOA
- Gas и комиссии — почему запись в State стоит денег