Аккаунт TRON не активирован: что это блокирует и как это снять
Адрес — это пара ключей до тех пор, пока у сети нет для него счёта. В этом весь смысл фразы «аккаунт TRON не активирован», и удивляет людей именно такой ответ: адрес правильной формы, кошелёк его показывает, QR-код читается, а сеть о нём никогда не слышала. У службы выплат это состояние всплывает в двух разных местах — на кошельке, который отправляет, и на адресе, которому платят, — и отказом является только одно из них.
Что такое активация на самом деле
Адрес TRON выводится из пары ключей офлайн. При его создании нигде ничего не регистрируется, и сеть узнаёт о нём только тогда, когда транзакция заводит для него запись счёта. Наша собственная проверка здесь буквальна: мы спрашиваем у ноды счёт, стоящий за адресом, и пустой ответ — это и есть activated: false. Не «мы его не нашли» и не «он выглядит новым»: счёта там нет.
Обычный способ его появления — входящий перевод TRX: отправьте TRX на адрес, и сеть заведёт счёт, пока будет исполнять этот перевод. Заведение счёта не бесплатно. Сколько оно стоит — параметр сети, а не цифра, которую стоит печатать на странице, поэтому читайте её у сети, а не там, где она может отставать на год. Здесь важно другое: это одна обычная транзакция, а после неё адрес — такой же счёт, как любой другой.
Что это блокирует: кошелёк, который будет отправлять
Энергия делегируется счёту, а когда счёта нет, делегировать некуда. В этом весь отказ. Заказ в mode A или mode C, называющий такой кошелёк в from, возвращает 422 invalid_address с details.reason в inactive_wallet, а вместе с ним details.index — какой это перевод пакета — и details.address. Это один из четырёх отказов, с которыми служба выплат встречается снова и снова, и один из двух, которые заказ отбивает сразу, вместо того чтобы оценить.
Три свойства этого отказа важны до того, как написан обработчик.
Он не расходует Idempotency-Key. Ни один отказ этого семейства его не расходует: запрос правильной формы, просто на этих адресах мы взять его не можем. Как только кошелёк появится, тот же ключ с байт-идентичным телом будет законным повтором, а не конфликтом, — и ничего заново выводить у себя не придётся.
Он не отвечается из кэша. Состояние адреса у нас кэшируется, но активация перечитывается мимо кэша до того, как заказ будет по ней отбит: кошелёк, активированный секунды назад, принимается сразу, а не после того, как протухнет какая-то запись. Повторяйте немедленно — пережидать нечего.
В mode B его не бывает. Там транзакция приходит уже подписанной владельцем адреса-отправителя, поэтому счёт есть по построению, и проверка задавала бы вопрос, который сам себе ответил.
Не активирован — это не то же, что неправильный адрес
Два ответа несут один и тот же код ошибки и означают противоположное. 400 invalid_address, вообще без details.reason, — про форму строки: это не адрес TRON, и ничто из происходящего в сети им его не сделает. 422 invalid_address с details.reason — про состояние совершенно нормального адреса, и чинится оно одной транзакцией. Конвейер, который обращается с ними одинаково, будет вечно повторять первый и бросит второй, — поэтому ветвиться стоит по наличию details.reason, а не по коду ошибки, который у них общий.
Во что это обходится: адрес, которому платят
На принимающей стороне не отказывают ни в чём: неактивированный получатель — это цена. Перевод на адрес без баланса USDT должен не только сдвинуть токены, но и открыть получателю счёт токена, а это около 131,000 единиц энергии против примерно 65,000 для адреса, где USDT уже есть (почему эти две цифры разные). По протокольной ставке это 13.1 TRX сжигания вместо 6.5 TRX, а в прогоне выплат новым клиентам в таком состоянии может оказаться разом почти весь пакет: сколько стоит арендовать эту энергию двигается за рынком, а сжигание не двигается.
Здесь два вопроса расходятся, а имена подталкивают их спутать, поэтому будьте точны. activated спрашивает, есть ли у сети счёт для этого адреса. holds_usdt спрашивает, больше ли нуля его баланс USDT. Цена идёт за вторым: kind равен double всегда, когда на адресе нет USDT, активирован он или нет. Предупреждение inactive_recipient идёт за первым. Поэтому адрес, активированный годы назад, которому просто ни разу не платили в USDT, не несёт никакого предупреждения и всё равно оценивается как double; а адрес, который никогда не активировали, может при этом держать USDT — баланс TRC-20 живёт в собственном хранилище контракта токена, поэтому входящий перевод создаёт баланс, а не счёт, — и такой оценивается как single и одновременно получает предупреждение inactive_recipient. Эти два вопроса выглядят одним ровно до того адреса, который отвечает на них по-разному.
Проверить один адрес и проверить пятьсот
По одному адресу GET /v1/address-check отвечает на оба вопроса и ещё на два за один вызов: activated, holds_usdt, blacklisted, is_contract и expected_kind, который из них следует.
По пакету POST /v1/estimate принимает до 500 переводов и не отказывает ни в чём: всё, что он заметил, приходит как warnings на позиции, включая находки, которые остановили бы заказ. Получателей смотрят всегда. Отправителя смотрят, только если вы его назвали: from в позиции оценки необязателен и цену не меняет, а покупает он ровно одно — ответ. inactive_sender здесь — та же самая находка, о которой заказ сообщает как о 422, только приходит она, когда на кону ничего нет.
POST /v1/estimate
{"transfers":[{"from":"TN3W…","to":"TMu1…"}]}
# предупреждения, которые может нести позиция: blacklisted, inactive_recipient,
# contract_recipient, inactive_sender, blacklisted_sender
Ни один из этих вызовов ничего не резервирует и ничего не списывает, поэтому список можно прогонять так часто, как позволяют лимиты. Чего не репетирует ни один из них — самого заказа, а он в любом режиме тратит настоящие деньги.
Что делать
Отправьте на адрес немного TRX, дождитесь подтверждения этой транзакции и повторите заказ тем же ключом и теми же байтами. Ни шага регистрации, ни заявки, ни второго обращения к нам. Единственное правило порядка, которое стоит записать: кошелёк должен существовать до заказа, а не до перевода, — энергия доставляется счёту, поэтому счёт должен быть на месте, чтобы её принять.
То же требование стоит и для кошелька, который вы хотите держать пополняемым. Правилу Auto-refill на неактивированном адресе откажут с тем же словом причины, а на адресе контракта — с contract_wallet: расход энергии у контракта устроен иначе, и правилу нечего отмерять. Сначала активация, потом правило.