Перейти к основному содержанию
Транзакция оказывается в одном из трёх состояний, и для каждого нужно своё решение. Прежде чем что-либо менять, выясните, в каком именно.

Шаг 1: выясните, что произошло

Запросите у кластера статус подписи. searchTransactionHistory позволяет найти транзакцию любого возраста.
Если ошибку вернул сам sendTransaction, подписи для проверки нет. Транзакция была отклонена до отправки. Перейдите к разделу Отклонена до отправки. Чтобы проверять подписи из кода, используйте рецепт ожидания подтверждения.

Отклонена до отправки

По умолчанию sendTransaction сначала симулирует транзакцию и отказывается её отправлять, если симуляция не прошла. Вы получаете ошибку -32002 с причиной и логами программ:
Читайте её в таком порядке:
  1. data.logs обычно простыми словами говорит, что пошло не так. Здесь у плательщика 0,06 SOL, а он пытался отправить 1000.
  2. data.err показывает, какая инструкция завершилась ошибкой. InstructionError: [0, ...] — это первая инструкция, счёт идёт с нуля.
  3. Собственный код ошибки принадлежит программе, которая завершилась ошибкой, а не Solana. Custom: 1 от System Program означает «недостаточно средств», но Custom: 1 от другой программы означает что-то другое. Найдите номер в списке ошибок этой программы (в её IDL или исходном коде). В сообщении код указан в шестнадцатеричном виде, поэтому 0x1770 — это 6000, первая собственная ошибка в программе на Anchor.
Отклонение ничего не стоит. Транзакция так и не покинула RPC-узел.

Частые причины отклонения

Отладка с помощью simulateTransaction

Если нужны логи без отправки, выполните симуляцию. Два параметра делают её гораздо полезнее при отладке:
  • replaceRecentBlockhash: true подставляет актуальный blockhash, поэтому одну и ту же транзакцию можно симулировать и после истечения её blockhash. Нельзя сочетать с sigVerify: true.
  • sigVerify: false пропускает проверку подписей, поэтому можно симулировать неподписанную или частично подписанную транзакцию.
Результат содержит err, logs и unitsConsumed, даже если симуляция прошла успешно. По unitsConsumed задайте точный лимит вычислительных единиц: чем плотнее лимит, тем эффективнее работает приоритетная комиссия, которую вы платите за единицу.
Не используйте skipPreflight: true во время отладки. Транзакция отправляется без симуляции, поэтому сломанная транзакция попадает в блок, завершается ошибкой и списывает комиссию, а ошибка видна только позже в getSignatureStatuses. Включайте этот параметр, когда транзакция уже точно работает и вам нужна скорость.

Попала в блок, но завершилась ошибкой

Транзакция в блокчейне, комиссия списана, и ни одно её изменение не применилось. Получите полную запись:
meta.err имеет тот же формат, что и ошибка симуляции, а meta.logMessages содержит логи программ. Читайте их так, как описано в разделе Отклонена до отправки. Транзакция, которая успешно прошла симуляцию, всё равно может завершиться ошибкой в блокчейне, потому что за это время изменилось состояние. Обычные причины — проскальзывание при обмене, аккаунт, который кто-то другой закрыл или изменил, или уже использованный nonce. Повторите симуляцию на текущем состоянии, чтобы убедиться, а затем заложите запас: более широкое проскальзывание, свежую котировку или повтор с пересборкой транзакции.

Так и не попала в блок

getSignatureStatuses возвращает null: RPC-узел принял транзакцию, но ни один лидер её не включил. Ничего не списано.

Blockhash истёк

Транзакция действительна только 150 блоков после своего blockhash. При длительности слота около 215 мс это примерно 30–35 секунд. После этого транзакция уже никогда не попадёт в блок, поэтому перестаньте ждать и соберите её заново. Проверьте, можно ли ещё использовать blockhash:
Сохраняйте lastValidBlockHeight, который возвращает getLatestBlockhash, вместе с blockhash. Сравнивайте его с getBlockHeight: пока высота блока меньше, продолжайте повторно отправлять ту же подписанную транзакцию. Когда она стала больше, транзакция потеряна.

Слишком низкая приоритетная комиссия

Когда блоки загружены, лидеры заполняют их транзакциями, которые платят больше всего за вычислительную единицу. Проверьте, что недавно попало в блоки для аккаунтов, с которыми вы работаете:
Выберите значение из последних результатов, задайте его через SetComputeUnitPrice и добавьте к нему точный SetComputeUnitLimit по результатам симуляции.

Транзакция потерялась по пути

Отправленная транзакция может потеряться между RPC-узлом и лидером, особенно при перегрузке сети. Повторно отправляйте те же подписанные байты каждые пару секунд, пока транзакция не подтвердится или не истечёт blockhash. Повторная отправка безопасна: транзакция с одной и той же подписью может попасть в блок только один раз. Если важно, чтобы транзакция попала в блок быстро, отправляйте её через Apex. Apex отправляет каждую транзакцию ближайшим лидерам сразу несколькими путями и повторяет попытки, пока она не попадёт в блок или не истечёт. Собственные ошибки Apex перечислены в разделе Ошибки и лимиты Apex.

Если проблема не решена

Соберите подпись (или транзакцию в base64, если подпись так и не была получена), полный текст ошибки вместе с data.logs, регион, через который вы отправляли, и было ли включено preflight. Затем задайте вопрос в Discord.