Работа с nonce вручную
Как вручную задавать nonce в Ethereum-транзакциях через ethers v6 и безопасно отправлять несколько pending-транзакций подряд.
Когда nonce нужен вручную
Nonce — это порядковый номер транзакции от конкретного EOA.
Обычно кошелёк и RPC подставляют его автоматически. Вручную nonce нужен, когда вы отправляете несколько транзакций подряд, заменяете pending-транзакцию или строите собственную очередь отправки.
Отправляем несколько транзакций с ручным nonce
Запустите локальную сеть Hardhat в отдельном терминале:
npx hardhat nodeЗатем выполните TypeScript-код:
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
// Первый тестовый аккаунт Hardhat. Не используйте этот ключ в mainnet.
const sender = new ethers.Wallet(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
provider,
);
const recipients = [
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
"0x90F79bf6EB2c4f870365E785982E1f101E93b906",
];
try {
await provider.send("evm_setAutomine", [false]);
const startNonce = await provider.getTransactionCount(sender.address, "pending");
console.log("Sender:", sender.address);
console.log("Start nonce:", startNonce);
const txs = await Promise.all(
recipients.map((recipient, index) => {
return sender.sendTransaction({
to: recipient,
value: ethers.parseEther("0.01"),
nonce: startNonce + index,
});
}),
);
for (const tx of txs) {
console.log("Pending tx:", tx.hash);
console.log("Nonce:", tx.nonce);
}
const pendingNonce = await provider.getTransactionCount(sender.address, "pending");
const latestNonceBeforeMining = await provider.getTransactionCount(sender.address, "latest");
console.log("Latest nonce before mining:", latestNonceBeforeMining);
console.log("Pending nonce before mining:", pendingNonce);
await provider.send("evm_mine", []);
for (const tx of txs) {
const receipt = await tx.wait();
if (!receipt || receipt.status !== 1) {
throw new Error(`Транзакция ${tx.hash} не была успешно выполнена`);
}
console.log("Confirmed tx:", tx.hash);
console.log("Block:", receipt.blockNumber);
console.log("Gas used:", receipt.gasUsed.toString());
}
const latestNonceAfterMining = await provider.getTransactionCount(sender.address, "latest");
console.log("Latest nonce after mining:", latestNonceAfterMining);
} finally {
await provider.send("evm_setAutomine", [true]);
}Код отправляет три pending-транзакции от одного адреса и вручную назначает им последовательные nonce.
Как это работает
provider.getTransactionCount(sender.address, "pending") — возвращает следующий свободный nonce с учётом pending-транзакций.
startNonce + index — выдаёт каждой транзакции отдельный nonce: N, N + 1, N + 2.
Promise.all() — отправляет транзакции параллельно. Без ручного nonce несколько запросов могли бы получить один и тот же nonce.
evm_setAutomine — выключает автоматический майнинг в Hardhat, чтобы транзакции остались pending до ручного блока.
latest — считает только подтверждённые транзакции.
pending — считает подтверждённые и уже отправленные pending-транзакции.
Почему порядок важен
Ethereum выполняет транзакции одного EOA строго по nonce.
nonce 10 → выполнится первой
nonce 11 → ждёт nonce 10
nonce 12 → ждёт nonce 11Если транзакция с nonce 10 зависла, транзакции 11 и 12 тоже будут ждать, даже если у них высокая комиссия.
latest vs pending
Для отправки новых транзакций почти всегда нужен pending.
const latestNonce = await provider.getTransactionCount(address, "latest");
const pendingNonce = await provider.getTransactionCount(address, "pending");latestNonce не видит mempool. Если у адреса уже есть pending-транзакция, latest может вернуть старый nonce и вы случайно создадите замену вместо новой транзакции.
pendingNonce учитывает очередь, которую знает ваша RPC-нода.
Что делать с ошибками nonce
nonce too low означает, что транзакция использует nonce, который уже выполнен или уже занят другой транзакцией.
replacement transaction underpriced означает, что вы отправили транзакцию с тем же nonce, но комиссия недостаточно выше для замены.
nonce too high означает, что в очереди есть пропуск: сеть ждёт транзакции с более ранним nonce.
Есть nonce 7 и 9
Нет nonce 8
Транзакция 9 будет ждать, пока появится или выполнится nonce 8Частые ошибки
Брать nonce через latest для параллельной отправки → используйте pending, иначе можно случайно выдать одинаковый nonce.
Оставлять пропуск в nonce → все следующие транзакции от этого адреса будут ждать.
Управлять nonce из нескольких процессов без синхронизации → два воркера могут выдать один nonce и заменить транзакции друг друга.
Что дальше
- Ускорение и отмена TX через replace-by-fee — заменить pending-транзакцию с тем же nonce
- Статусы транзакции — pending, confirmed, failed — проверить, где находится транзакция
- EIP-1559 — maxFeePerGas vs gasPrice — настроить комиссию для pending-очереди