Статусы транзакции — pending, confirmed, failed
Как проверять статус Ethereum-транзакции через ethers v6: pending до блока, confirmed после receipt и failed при откате выполнения.
Что значит статус транзакции
Статус транзакции показывает, где она находится: ждёт включения в блок, уже выполнена или выполнена с ошибкой.
Для приложения это критично: tx.hash означает только отправку в RPC, а не успешное изменение State.
Проверяем pending, confirmed и failed
Запустите локальную сеть Hardhat в отдельном терминале:
npx hardhat nodeЗатем выполните 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 |
|---|---|---|
unknown | RPC не знает такой hash | нет |
pending | транзакция в mempool, но ещё не в блоке | нет |
confirmed | транзакция попала в блок и выполнилась успешно | да |
failed | транзакция попала в блок, но выполнение откатилось | да |
failed не означает, что транзакции не было. Она была включена в блок, заняла место и потратила gas.
Pending не меняет State
Пока транзакция pending, она только ждёт включения в блок.
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 показывает результат выполнения.
Что дальше
- Ускорение и отмена TX (replace-by-fee) — заменить pending-транзакцию с тем же nonce
- Работа с nonce вручную — понять, почему pending-транзакции блокируют следующие
- EIP-1559 — maxFeePerGas vs gasPrice — настроить комиссию, чтобы транзакция быстрее попала в блок