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

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

warning

Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.

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

подсказка

Рекомендуется ознакомиться с Обзором обработки активов перед прочтением этого руководства.

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

Смарт-контракты кошелька в сети TON позволяют внешним акторам взаимодействовать с сущностями блокчейна.

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

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

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

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

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

Высоконагруженные кошельки

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

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

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

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

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

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

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

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

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

Проверка транзакций контракта

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

  1. Последний last_transaction_id можно получить с помощью 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();

Отправка платежей

подсказка

Изучите базовый пример обработки платежей из TMA USDT Payments demo

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

Если прикрепленное value слишком мало, транзакция может быть прервана с ошибкой cskip_no_gas. В этом случае Toncoins будут успешно переведены, но логика на другой стороне не будет выполнена (TVM даже не запустится). Подробнее об ограничениях газа можно прочитать здесь.

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

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

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

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

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

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

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

Счета с TON Connect

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

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

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

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

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

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

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

Узнать больше

Счета со ссылкой ton://

warning

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

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

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

  • ✅ Не нужно подключать кошелек

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

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

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

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

Узнайте больше о ссылках ton здесь

Обозреватели

Обозреватель блокчейна - https://tonscan.org.

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

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}

Обратите внимание, что tonviewer и tonscan поддерживают внешний хэш сообщения вместо хэша транзакции для ссылки в проводнике. Это может быть полезно, когда вы генерируете внешнее сообщение и хотите мгновенно сгенерировать ссылку. Подробнее о транзакциях и хэшах сообщений здесь

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

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

Создание кошелька для разных шардов

При большой нагрузке блокчейн TON может разделиться на [шарды] (/v3/documentation/smart-contracts/shards/shards-intro). Простая аналогия шарда в мире Web3 — это сегмент сети.

Точно так же, как мы распределяем инфраструктуру услуг в мире Web2, чтобы она была как можно ближе к конечному пользователю, в TON мы можем развертывать контракты, которые будут находиться в том же шарде, что и кошелек пользователя или любой другой контракт, который взаимодействует с ним.

Например, DApp, который собирает плату с пользователей за будущий эирдроп, может подготовить отдельные кошельки для каждого шарда, чтобы улучшить пользовательский опыт в дни пиковой нагрузки. Чтобы достичь максимальной скорости обработки, вам нужно будет развернуть один кошелек collector на шард.

Префикс шарда SHARD_INDEX контракта определяется первыми 4 битами его хэша адреса. Чтобы развернуть кошелек в определенном шарде, можно использовать логику, основанную на следующем фрагменте кода:


import { NetworkProvider, sleep } from '@ton/blueprint';
import { Address, toNano } from "@ton/core";
import {mnemonicNew, mnemonicToPrivateKey} from '@ton/crypto';
import { WalletContractV3R2 } from '@ton/ton';

export async function run(provider?: NetworkProvider) {
if(!process.env.SHARD_INDEX) {
throw new Error("Shard index is not specified");
}

const shardIdx = Number(process.env.SHARD_INDEX);
let testWallet: WalletContractV3R2;
let mnemonic: string[];
do {
mnemonic = await mnemonicNew(24);
const keyPair = await mnemonicToPrivateKey(mnemonic);
testWallet = WalletContractV3R2.create({workchain: 0, publicKey: keyPair.publicKey});
} while(testWallet.address.hash[0] >> 4 !== shardIdx);

console.log("Mnemonic for shard found:", mnemonic);
console.log("Wallet address:",testWallet.address.toRawString());
}

if(require.main === module) {
run();
}

В случае контракта кошелька можно использовать subwalletId вместо мнемоники, однако subwalletId не поддерживается приложениями кошелька.

После завершения развертывания вы можете выполнить обработку по следующему алгоритму:

  1. Пользователь заходит на страницу DApp и запрашивает действие.
  2. DApp выбирает ближайший к пользователю кошелек (соответствует 4-битному префиксу)
  3. DApp предоставляет пользователю payload, отправляя его плату в выбранный кошелек.

Таким образом, вы сможете обеспечить наилучший возможный пользовательский опыт независимо от текущей загрузки сети.

Депозиты Toncoin (получение toncoins)

Вывод Toncoin (отправка toncoins)

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

SDK

Полный список SDK для различных языков программирования (JS, Python, Golang и т. д.) доступен здесь.