китаев.tech

Статусы транзакции — pending, confirmed, failed

Как проверять статус Ethereum-транзакции через ethers v6: pending до блока, confirmed после receipt и failed при откате выполнения.

Что значит статус транзакции

Статус транзакции показывает, где она находится: ждёт включения в блок, уже выполнена или выполнена с ошибкой.

Для приложения это критично: tx.hash означает только отправку в RPC, а не успешное изменение State.


Проверяем pending, confirmed и failed

Запустите локальную сеть Hardhat в отдельном терминале:

bash
npx hardhat node

Затем выполните TypeScript-код:

typescript
import { ethers } from "ethers";

type TransactionStatus = "unknown" | "pending" | "confirmed" | "failed";

const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");

// Первый тестовый аккаунт Hardhat. Не используйте этот ключ в mainnet.
const sender = new ethers.Wallet(
  "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
  provider,
);

// Второй тестовый аккаунт Hardhat.
const recipient = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";

async function getTransactionStatus(hash: string): Promise<TransactionStatus> {
  const tx = await provider.getTransaction(hash);
  const receipt = await provider.getTransactionReceipt(hash);

  if (!tx) {
    return "unknown";
  }

  if (!receipt) {
    return "pending";
  }

  return receipt.status === 1 ? "confirmed" : "failed";
}

async function printStatus(label: string, hash: string) {
  const status = await getTransactionStatus(hash);
  const receipt = await provider.getTransactionReceipt(hash);
  const confirmations = receipt ? await receipt.confirmations() : 0;

  console.log(`${label}:`, status);
  console.log("Hash:", hash);
  console.log("Confirmations:", confirmations);
}

try {
  await provider.send("evm_setAutomine", [false]);

  const successTx = await sender.sendTransaction({
    to: recipient,
    value: ethers.parseEther("0.1"),
  });

  await printStatus("После отправки ETH", successTx.hash);

  await provider.send("evm_mine", []);

  await printStatus("После майнинга ETH", successTx.hash);

  // Init code, который сразу делает REVERT. Такая contract creation транзакция попадёт в блок,
  // но receipt получит status = 0.
  const failedTx = await sender.sendTransaction({
    data: "0x60006000fd",
    gasLimit: 50_000n,
  });

  await printStatus("После отправки failed TX", failedTx.hash);

  await provider.send("evm_mine", []);

  await printStatus("После майнинга failed TX", failedTx.hash);
} finally {
  await provider.send("evm_setAutomine", [true]);
}

Код временно выключает automine в Hardhat, поэтому транзакция сначала видна как pending. После ручного evm_mine она получает receipt и становится confirmed или failed.


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

provider.getTransaction(hash) — ищет транзакцию по hash. Если RPC её не знает, статус unknown.

provider.getTransactionReceipt(hash) — возвращает результат выполнения. Пока receipt нет, транзакция ещё не попала в блок.

receipt.status === 1 — транзакция успешно выполнилась.

receipt.status === 0 — транзакция попала в блок, но EVM откатила выполнение.

receipt.confirmations() — считает, сколько блоков подтверждают транзакцию.

evm_setAutomine и evm_mine — методы локальной Hardhat-сети. Они нужны только для учебного примера с контролируемым pending.


Чем отличаются статусы

СтатусЧто значитЕсть receipt
unknownRPC не знает такой hashнет
pendingтранзакция в mempool, но ещё не в блокенет
confirmedтранзакция попала в блок и выполнилась успешнода
failedтранзакция попала в блок, но выполнение откатилосьда

failed не означает, что транзакции не было. Она была включена в блок, заняла место и потратила gas.


Pending не меняет State

Пока транзакция pending, она только ждёт включения в блок.

text
pending   → hash есть, receipt нет, State не изменился
confirmed → receipt есть, status = 1, State изменился
failed    → receipt есть, status = 0, State откатился

Интерфейс может показывать пользователю pending-состояние сразу после отправки hash, но не должен считать действие завершённым до receipt.


Почему транзакция может failed

Транзакция получает failed, когда EVM начала выполнение, но оно завершилось ошибкой.

Обычные причины:

  • контракт сделал revert
  • не хватило gas во время выполнения
  • проверка внутри контракта не прошла
  • изменился State между симуляцией и включением в блок

Комиссия всё равно списывается за уже выполненную работу EVM.


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

Считать tx.hash подтверждением → hash появляется до блока. Ждите receipt через tx.wait() или getTransactionReceipt().

Считать failed-транзакцию бесплатной → если receipt есть, транзакция попала в блок и gas уже потрачен.

Проверять только getTransaction() → transaction показывает команду, а receipt показывает результат выполнения.


Что дальше

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