Почти все эти баги уже пофикшены в популярных кошельках (@wallet,
№1 Недостаточно денег
Проявляется баг так: у пользователя на балансе 1.001 GRAM, вы отправляете транзакцию на 1 GRAM, но в блокчейне транзакция упала с ошибкой на контракте кошелька. Это произошло, потому что кошельку не хватило GRAM на оплату комиссий блокчейна.
Кошельки не сразу научились точно рассчитывать, хватит ли GRAM на балансе с учетом всех комиссий или нет, и подписывали транзакцию «как есть». В итоге такая транзакция падала в блокчейне, а в UI пользователь бесконечно долго ждал подтверждения транзакции.
Сейчас кошельки умеют точно рассчитывать стоимость транзакции в GRAM и показывать человекочитаемую ошибку о нехватке средств, но для жетонов (USDT) такую проблему все еще можно встретить: если не хватает баланса жетонов, кошельки могут отправлять транзакции, которые упадут в блокчейне.
Чтобы сгладить проблему, приходится на стороне dApp проверять баланс пользователя и рисовать предупреждения. Перенести проверку баланса на сторону dApp полностью нельзя, потому что в кошельках бывают «gasless»-транзакции. Например, батарейка в Tonkeeper. В этом случае у пользователя на балансе нет GRAM вообще, а сторонний сервис пришлет чуть-чуть GRAM в момент совершения транзакции.
№2 Транзакция отправлена частично
Отправка двух сообщений за раз появилась в экосистеме TON не сразу, поэтому была желанным обновлением в кошельках. Из-за бага №1 бывали случаи, когда на отправку какого-то сообщения GRAM не хватало, и вместо двух сообщений с кошелька пользователя уходило только одно. Причем уйти могло любое сообщение: первое или второе. Например, в первом сообщении вы отправляли 5 GRAM, а во втором — 3 GRAM. Если у пользователя было только 4 GRAM на балансе, то первое сообщение не отправлялось вообще, а второе уходило. Но могло быть и по-другому: если на балансе 6 GRAM, то первое сообщение уходило, а на второе уже не хватало.
Для выставления NFT на продажу как раз надо два действия: деплой контракта продажи и передача NFT на этот контракт. Из-за этого бага бывали случаи, когда контракт продажи не создавался, а NFT на него отправлялась. Поэтому на Getgems для этого появился специальный контракт, который занимается выставлением NFT на продажу: он требует только одно сообщение с кошелька пользователя для выставления на продажу.
№3 Модификация транзакции
Этот баг несколько раз встречался в связке MyTonWallet + Ledger. Суть в том, что ранние версии Ledger (это такой аппаратный криптокошелек) не умели отправлять все типы транзакций. Они умели только простые переводы GRAM или переводы с текстовым комментарием. В TON Connect таких ограничений никогда не было.
Из-за этого получалась такая цепочка событий: dApp просит кошелек отправить 5 GRAM + текстовый комментарий + код контракта. MyTonWallet получает этот запрос, видит текстовый комментарий, думает, что все ок, и пересылает данные в Ledger. Код контракта где-то в этом месте теряется. В итоге мы имеем GRAM, которые лежат в блокчейне на незадеплоенном контракте (потому что код так и не прислали).
Этот баг я встретил, когда мы делали в Getgems офферы на NFT. Само собой, без кода контракта оффера фича не работала. Команда MyTonWallet пофиксила этот момент, но после этого похожий баг был с трансфером NFT. Для поддержки Ledger сообщение для трансфера пересобиралось на стороне кошелька, и иногда некоторые биты терялись.
Этот баг на стороне dApp никак не пофиксить. Благо таких проблем я не встречал уже давно.
№4 Дубли транзакций
Такое казалось невозможным, но случилось в кошельках @wallet и Tonkeeper. В @wallet это было так: dApp отправляет одну транзакцию в TON Connect, а с кошелька пользователя уходят две транзакции с разницей в пару секунд. В транзакциях разный seqno, то есть это клиентский код кошелька решил одно и то же отправить дважды. Насколько я понял, это был баг в UI, где пользователь успевал дважды кликнуть по кнопке. В Tonkeeper было чуть по-другому: пользователь делал разные транзакции в dApp, и иногда Tonkeeper из какого-то кеша доставал «старую» транзакцию и предлагал подписать ее вместо новой.
То есть пользователь хотел купить NFT и выставить эту NFT на продажу. dApp присылал две разные транзакции в TON Connect, а в итоге с кошелька уходили две транзакции на покупку NFT.
Этот баг на стороне dApp не пофиксить, но иногда можно пофиксить в контрактах. Например, контракт продажи NFT будет выбрасывать ошибку, если попытаться купить NFT дважды. К сожалению, такое поведение не везде можно реализовать.
№5 Баунс-флаг
Когда TON Connect только появился, он принимал адреса в raw-формате. В этом формате не было информации о bounce-флаге, поэтому кошелек «рассчитывал» значение этого флага самостоятельно. Логика была простая: кошелек смотрел в блокчейн, и если по этому адресу уже был контракт, то ставил баунс-флаг, а если контракта не было, то не ставил. Сейчас в TON Connect надо передавать адреса в friendly-формате, а значит (по идее) кошелек должен брать значение bounce из адреса.
Баг проявлялся с NFT, которые только что выставили на продажу по низкой цене. Для каждой продажи NFT создается отдельный контракт продажи. Кошелек смотрит в блокчейн и не видит там контракта, потому что индексер кошелька отстал, а контракт продажи только-только появился в блокчейне. Поэтому кошелек отправляет транзакцию без баунс-флага, а в этом случае GRAM не вернутся, если выполнение контракта завершится с ошибкой. Поскольку желающих купить дешевую NFT очень много, побеждает всегда кто-то один, а остальные получают ошибку, и из-за неправильного баунс-флага возврат GRAM не происходил.
Сейчас такой проблемы быть и не должно: адреса передаются в friendly-формате. «Подмену» баунс-флага можно ловить в коде контракта: если вы рассчитываете на bounce-флаг, можно вручную в коде контракта обрабатывать возвраты для входящих сообщений без этого флага.
После того как этот баг был обнаружен, во все контракты продаж Getgems добавили возможность вернуть застрявшие GRAM и жетоны с контракта.
№6 dApp не знает, что его отключили
В кошельках есть список подключенных dApp. В любой момент любой dApp можно отключить, и после этого транзакции от этого dApp не придут в кошелек, даже если он их отправит. Функция полезная, но проблема в том, что dApp не узнает, что вы его отключили. Обычно dApp — это просто сайт, открытый в браузере, и работает он, только пока открыт браузер. Как правило, факт подключения к кошельку хранится в самом браузере в localStorage. Когда вы отключаете dApp, кошелек отправляет сообщение «тебя отключили», но это сообщение некому обработать: браузер закрыт, и никакой код не исполняется. Когда вы откроете браузер снова, dApp прочитает данные из localStorage и будет думать, что он все еще подключен к кошельку, но никакие транзакции больше не будут отправляться в кошелек. Будет просто бесконечное ожидание подтверждения транзакции.
Если вы попробуете «протестировать» это поведение — отключить dApp в кошельке и сразу открыть его, — то увидите, что dApp отключился. Так происходит, потому что мост между кошельком и dApp умеет кешировать недоставленные сообщения на несколько минут. Но какой нормальный пользователь будет открывать dApp, который он только что отключил?
Кстати, команда @wallet пофиксила это в своем кошельке: они кешируют сообщения об отключении на несколько дней.
№7 Ничего не работает без VPN
Это классическая проблема многих сервисов в РФ. Исправить ее никак нельзя. Даже если ваш dApp открывается без VPN, это еще не значит, что TON Connect будет нормально работать. Для связи с кошельком используются мосты, которые могут быть недоступны без VPN. Проявляться это будет в виде бесконечной загрузки или бесконечно долгого ожидания подключения.
№8 Потерянные уведомления