Перейти к основному содержимому

Обработка платежей

Эта страница объясняет, как обрабатывать (отправлять и принимать) цифровые активы на блокчейне TON. Здесь в основном описывается работа с монетами TON, но теоретическая часть является важной, даже если Вы хотите обрабатывать только джеттоны.

Смарт-контракт кошелька

Смарт-контракты кошелька - это контракты в сети TON, которые служат для того, чтобы позволить участникам, находящимся вне блокчейна, взаимодействовать с сущностями блокчейна. В целом, они решают три задачи:

  • удостоверяет подлинность владельца: Отказывается обрабатывать и оплачивать запросы лиц, не являющихся владельцами.
  • Защита от повторов: Запрещает повторное выполнение одного запроса, например, отправку активов другому смарт-контракту.
  • инициирует произвольное взаимодействие с другими смарт-контрактами.

Стандартным решением первой задачи является криптография с открытым ключом: кошелек хранит открытый ключ и проверяет, что входящее сообщение с запросом подписано соответствующим закрытым ключом, который известен только его владельцу.

Решение третьей задачи также является общим; обычно запрос содержит полностью сформированное внутреннее сообщение, которое кошелек отправляет в сеть. Однако для защиты от воспроизведения существует несколько различных подходов.

Кошельки на основе Seqno

Кошельки на основе Seqno используют наиболее простой подход к определению последовательности сообщений. Каждое сообщение имеет специальное целое число seqno, которое должно совпадать со счетчиком, хранящимся в смарт-контракте wallet. wallet обновляет свой счетчик при каждом запросе, гарантируя тем самым, что один запрос не будет обработан дважды. Существует несколько версий wallet, которые отличаются от общедоступных методов: возможность ограничивать запросы по времени истечения срока действия и возможность иметь несколько кошельков с одним и тем же открытым ключом. Однако неотъемлемым требованием такого подхода является отправка запросов по одному, поскольку любой разрыв в последовательности seqno приведет к невозможности обработки всех последующих запросов.

Кошельки с высокой нагрузкой

Этот тип кошелька использует подход, основанный на хранении идентификатора непросроченных обработанных запросов в хранилище смарт-контрактов. При таком подходе любой запрос проверяется на предмет дублирования уже обработанного запроса и, в случае обнаружения повтора, отбрасывается. Из-за истечения срока действия контракт не может хранить все запросы вечно, но он будет удалять те, которые не могут быть обработаны из-за ограничения срока действия. Запросы к этому кошельку могут отправляться параллельно, не мешая друг другу; однако такой подход требует более сложного контроля за обработкой запросов.

Развертывание кошелька

Чтобы развернуть кошелек с помощью TonLib, необходимо:

  1. Сгенерируйте пару закрытый/открытый ключ с помощью createNewKey или ее функций-оберток (пример в tonlib-go). Обратите внимание, что закрытый ключ генерируется локально и не покидает хост-машину.
  2. Сформируйте структуру InitialAccountWallet, соответствующую одному из включенных кошельков. В настоящее время доступны wallet.v3, wallet.v4, wallet.highload.v1, wallet.highload.v2.
  3. Вычислите адрес нового смарт-контракта кошелька с помощью метода getAccountAddress. Мы рекомендуем использовать ревизию по умолчанию 0, а также развертывать кошельки в базовой цепи workchain=0 для снижения платы за обработку и хранение данных.
  4. Отправьте несколько Toncoin на вычисленный адрес. Обратите внимание, что отправлять их нужно в режиме non-bounce, поскольку этот адрес еще не имеет кода и поэтому не может обрабатывать входящие сообщения. Флаг non-bounce указывает, что даже если обработка не удалась, деньги не должны быть возвращены с сообщением об отказе. Мы не рекомендуем использовать флаг non-bounce для других транзакций, особенно при переводе больших сумм, поскольку механизм отказов обеспечивает некоторую степень защиты от ошибок.
  5. Сформируйте желаемое действие, например, actionNoop только для развертывания. Затем используйте createQuery и sendQuery, чтобы инициировать взаимодействие с блокчейном.
  6. Проверьте контракт за несколько секунд с помощью метода getAccountState.
подсказка

Подробнее читайте в [Учебнике по кошельку] (/develop/smart-contracts/tutorials/wallet#-deploying-a-wallet)

Проверьте действительность адреса кошелька

Большинство SDK заставляют Вас проверять адрес (большинство проверяет его в процессе создания кошелька или подготовки транзакции), поэтому, как правило, это не требует от Вас никаких дополнительных сложных действий.

  const TonWeb = require("tonweb")
TonWeb.utils.Address.isValid('...')
подсказка

Полное описание адресов на странице Адреса смарт-контрактов.

Работа с переводами

Проверьте сделки по контракту

Транзакции контракта можно получить с помощью метода getTransactions. Этот метод позволяет получить 10 транзакций с некоторого последнего_transaction_id и более ранних. Чтобы обработать все входящие транзакции, необходимо выполнить следующие шаги:

  1. Последний последний_транзакционный_ид можно получить с помощью getAddressInformation
  2. Список из 10 транзакций должен быть загружен с помощью метода getTransactions.
  3. Обрабатывайте транзакции с непустым источником во входящем сообщении и адресом назначения, равным адресу счета.
  4. Следующие 10 транзакций должны быть загружены, и шаги 2,3,4,5 должны повторяться до тех пор, пока Вы не обработаете все входящие транзакции.

Получение входящих/исходящих транзакций

Можно отслеживать поток сообщений во время обработки транзакций. Поскольку поток сообщений представляет собой DAG, достаточно получить текущую транзакцию с помощью метода getTransactions и найти входящую транзакцию по out_msg с помощью tryLocateResultTx или исходящую транзакцию по in_msg с помощью tryLocateSourceTx.

import { TonClient, Transaction } from '@ton/ton';
import { getHttpEndpoint } from '@orbs-network/ton-access';
import { CommonMessageInfoInternal } from '@ton/core';

async function findIncomingTransaction(client: TonClient, transaction: Transaction): Promise<Transaction | null> {
const inMessage = transaction.inMessage?.info;
if (inMessage?.type !== 'internal') return null;
return client.tryLocateSourceTx(inMessage.src, inMessage.dest, inMessage.createdLt.toString());
}

async function findOutgoingTransactions(client: TonClient, transaction: Transaction): Promise<Transaction[]> {
const outMessagesInfos = transaction.outMessages.values()
.map(message => message.info)
.filter((info): info is CommonMessageInfoInternal => info.type === 'internal');

return Promise.all(
outMessagesInfos.map((info) => client.tryLocateResultTx(info.src, info.dest, info.createdLt.toString())),
);
}

async function traverseIncomingTransactions(client: TonClient, transaction: Transaction): Promise<void> {
const inTx = await findIncomingTransaction(client, transaction);
// now you can traverse this transaction graph backwards
if (!inTx) return;
await traverseIncomingTransactions(client, inTx);
}

async function traverseOutgoingTransactions(client: TonClient, transaction: Transaction): Promise<void> {
const outTxs = await findOutgoingTransactions(client, transaction);
// do smth with out txs
for (const out of outTxs) {
await traverseOutgoingTransactions(client, out);
}
}

async function main() {
const endpoint = await getHttpEndpoint({ network: 'testnet' });
const client = new TonClient({
endpoint,
apiKey: '[API-KEY]',
});

const transaction: Transaction = ...; // Obtain first transaction to start traversing
await traverseIncomingTransactions(client, transaction);
await traverseOutgoingTransactions(client, transaction);
}

main();

Отправляйте платежи

  1. Сервис должен развернуть кошелек и постоянно пополнять его, чтобы предотвратить разрушение контракта из-за платы за хранение. Обратите внимание, что плата за хранение обычно составляет менее 1 Тонкоина в год.
  2. Сервис должен получить от пользователя адрес_назначения и необязательный комментарий. Обратите внимание, что на данный момент мы рекомендуем либо запретить незавершенные исходящие платежи с одинаковым набором (адрес_назначения, значение, комментарий), либо правильно планировать эти платежи; таким образом, следующий платеж будет инициирован только после подтверждения предыдущего.
  3. Сформируйте msg.dataText с comment в качестве текста.
  4. Форма msg.message, содержащая адрес_назначения, пустой публичный_ключ, сумму и msg.dataText.
  5. Форма Действие, содержащая набор исходящих сообщений.
  6. Используйте запросы createQuery и sendQuery для отправки исходящих платежей.
  7. Сервис должен регулярно опрашивать метод getTransactions для контракта wallet. Сопоставление подтвержденных транзакций с исходящими платежами по (адрес_назначения, значение, комментарий) позволяет пометить платежи как завершенные; обнаружить и показать пользователю соответствующий хэш транзакции и lt (логическое время).
  8. Запросы к v3 кошелькам с высокой нагрузкой по умолчанию имеют время истечения, равное 60 секундам. По истечении этого времени необработанные запросы могут быть безопасно повторно отправлены в сеть (см. шаги 3-6).

Получите идентификатор транзакции

Может быть неясно, что для получения дополнительной информации о транзакции пользователь должен просканировать блокчейн с помощью функции getTransactions. Невозможно получить идентификатор транзакции сразу после отправки сообщения, поскольку транзакция сначала должна быть подтверждена сетью блокчейн. Чтобы понять, что требуется для этого, внимательно прочитайте Send payments, особенно 7-й пункт.

Подход на основе счета-фактуры

Чтобы принимать платежи на основании прикрепленных комментариев, сервис должен

  1. Разверните контракт wallet.
  2. Сгенерируйте уникальный счет для каждого пользователя. Строкового представления uuid32 будет достаточно.
  3. Пользователям следует дать указание отправить Тонкоин на кошелек сервиса с приложенным счетом-фактурой в качестве комментария.
  4. Сервис должен регулярно опрашивать метод getTransactions для контракта wallet.
  5. Для новых транзакций входящее сообщение должно быть извлечено, комментарий сопоставлен с базой данных, а значение входящего сообщения зачислено на счет пользователя.

Чтобы вычислить значение входящего сообщения, которое сообщение приносит контракту, необходимо разобрать транзакцию. Это происходит, когда сообщение попадает в контракт. Транзакцию можно получить с помощью getTransactions. Для входящей транзакции кошелька правильные данные состоят из одного входящего сообщения и нуля исходящих сообщений. В противном случае либо в кошелек отправляется внешнее сообщение, и тогда владелец тратит Toncoin, либо кошелек не развернут, и входящая транзакция возвращается обратно.

В любом случае, в общем случае, сумма, которую сообщение приносит контракту, может быть рассчитана как стоимость входящего сообщения минус сумма стоимостей исходящих сообщений минус комиссия: value_{in_msg} - SUM(value_{out_msg}) - fee. Технически, представление транзакций содержит три различных поля с fee в имени: fee, storage_fee и other_fee, то есть общая плата, часть платы, связанная с расходами на хранение, и часть платы, связанная с обработкой транзакций. Следует использовать только первую.

Счета-фактуры с помощью TON Connect

Лучше всего подходит для dApp, которым нужно подписывать несколько платежей/транзакций в течение сессии или поддерживать соединение с кошельком в течение некоторого времени.

  • ✅ Существует постоянный канал связи с кошельком, информация об адресе пользователя

  • ✅ Пользователям нужно сканировать QR-код только один раз

  • ✅ Можно узнать, подтвердил ли пользователь транзакцию в кошельке, отследить транзакцию по возвращенному BOC

  • ✅ Готовые SDK и наборы пользовательского интерфейса доступны для разных платформ

  • ❌ Если Вам нужно отправить только один платеж, пользователю необходимо выполнить два действия: подключить кошелек и подтвердить транзакцию

  • ❌ Интеграция сложнее, чем просто ссылка ton://.

Подробнее

Счета-фактуры со ссылкой ton://

осторожно

Ссылка Ton устарела, избегайте ее использования

Если Вам нужна простая интеграция для простого потока пользователей, лучше всего использовать ссылку ton://. Лучше всего подходит для разовых платежей и счетов-фактур.

ton://transfer/<destination-address>?
[nft=<nft-address>&]
[fee-amount=<nanocoins>&]
[forward-amount=<nanocoins>]
  • ✅ Легкая интеграция

  • ✅ Нет необходимости подключать кошелек

  • ❌ Пользователям необходимо сканировать новый QR-код для каждого платежа

  • ❌ Невозможно отследить, подписал ли пользователь транзакцию или нет.

  • ❌ Нет информации об адресе пользователя

  • ❌ Необходимы обходные пути на платформах, где такие ссылки не кликабельны (например, сообщения от ботов для настольных клиентов Telegram).

Подробнее о тонких ссылках здесь

Explorers

Исследователь блокчейна - https://tonscan.org.

Чтобы создать ссылку на транзакцию в проводнике, служба должна получить lt (логическое время), хэш транзакции и адрес счета (адрес счета, для которого lt и txhash были получены с помощью метода getTransactions). https://tonscan.org и https://explorer.toncoin.org/ могут затем показать страницу для этого tx в следующем формате:

https://tonviewer.com/transaction/{txhash as base64url}.

https://tonscan.org/tx/{lt as int}:{txhash as base64url}:{account address}

https://explorer.toncoin.org/transaction?account={account address}&lt={lt as int}&hash={txhash as base64url}

Лучшие практики

Создание кошелька

Депозиты в тонкоинах (Получите тонкоины)

Вывод Тонкоинов (Отправить Тонкоины)

Получите транзакции контракта

SDKs

Вы можете найти список SDK для различных языков (JS, Python, Golang, C#, Rust и т.д.) список здесь.