Обработка платежей
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
На этой странице объясняется, как обрабатывать (отправлять и принимать) цифровые активы
в блокчейне TON. В основном она описывает, как работать с TON coins
, но теоретическая часть важна, даже если вы хотите обрабатывать только жетоны
.
Рекомендуется ознакомиться с Обзором обработки активов перед прочтением этого руководства.
Смарт-контракт кошелька
Смарт-контракты кошелька в сети TON позволяют внешним акторам взаимодействовать с сущностями блокчейна.
- Аутентифицирует владельца: отклоняет запросы, которые пытаются обработать или оплатить комиссии от имени лиц, не являющихся владельцами.
- Обеспечивает защиту от повторного использования: предотвращает повторное выполнение одного и того же запроса, например отправку активов в другой смарт-контракт.
- Инициирует произвол ьные взаимодействия с другими смарт-контрактами.
Стандартное решение для первой проблемы — криптография с открытым ключом: wallet
хранит открытый ключ и проверяет, что входящее сообщение с запросом подписано соответствующим закрытым ключом, который известен только владельцу.
Решение третьей проблемы также распространено; Как правило, запрос содержит полностью сформированное внутреннее сообщение, которое wallet
отправляет в сеть. Однако для защиты от повторного использования существует несколько различных подходов.
Кошельки на основе Seqno
Кошельки на основе Seqno используют простейший подход к упорядочиванию сообщений. Каждое сообщение имеет специальное целое число seqno
, которое должно совпадать со счетчиком, хранящимся в смарт-контракте wallet
. wallet
обновляет свой счетчик при каждом запросе, тем самым гарантируя, что один запрос не будет обработан дважды. Существует несколько версий wallet
, которые отличаются открытыми методами: возможностью ограничивать запросы по времени истечения срока действия и возможностью иметь несколько кошельков с одним и тем же открытым ключом. Однако неотъемлемым требованием этого подхода является отправка запросов по одному, поскольку любой пропуск в последовательности seqno
приведет к невозможности обработки всех последующих запросов.
Высоконагруженные кошельки
Этот тип wallet
следует подходу, основанному на хранении идентификатора непросроченных обработанных запросов в хранилище смарт-контракта. При этом подходе любой запрос проверяется на предмет дубликата уже обработанного запроса и, если обнаруживается повтор, отклоняется. Из-за истечения срока действия контракт может не хранить все запросы вечно, но он удалит те, которые не могут быть обработаны из-за ограничения срока действия. Запросы в этот wallet
можно отправлять параллельно без помех, но этот подход требует более сложного отслеживания обработки запросов.
Развертывание кошелька
Чтобы развернуть кошелек через TonLib, необходимо:
- Сгенерировать пару закрытый/открытый ключ с помощью createNewKey или его функций-оберток (пример в tonlib-go). Обратите внимание, что закрытый ключ генерируется локально и не покидает хост-машину.
- Сформировать структуру InitialAccountWallet, соответствующую одному из включенных
wallet
. В настоящее время доступныwallet.v3
,wallet.v4
,wallet.highload.v1
иwallet.highload.v2
. - Рассчитать адрес нового смарт-контракта кошелька с помощью метода getAccountAddress. Мы рекомендуем использовать ревизию по умолчанию
0
, а также развернуть кошельки в basechainworkchain=0
для более низких комиссий за обработку и хранение. - Отправить немного Toncoin на рассчитанный адрес. Обратите внимание, что вам нужно отправлять их в режиме
non-bounce
, так как этот адрес пока не имеет кода и не может обрабатывать входящие сообщения. Флагnon-bounce
указывает, что даже если обработка не удалась, деньги не должны быть возвращены с сообщением о недоставке. Мы не рекомендуем использовать флагnon-bounce
для других транзакций, особенно при переносе больших сумм, так как механизм возврата обеспечивает некоторую степень защиты от ошибок. - Сформируйте желаемое действие, например
actionNoop
только для развертывания. Затем используйте createQuery и sendQuery, чтобы инициировать взаимодействие с блокчейном. - Проверьте ко нтракт за несколько секунд с помощью метода getAccountState.
Подробнее в руководстве по кошельку
Проверьте правильность адреса кошелька
Большинство SDK принудительно проверяют адреса (большинство проверяют их во время создания кошелька или процесса подготовки транзакции), поэтому, как правило, от вас не требуется никаких дополнительных сложных шагов.
- JS (Tonweb)
- tonutils-go
- Ton4j
- ton-kotlin
const TonWeb = require("tonweb")
TonWeb.utils.Address.isValid('...')
package main
import (
"fmt"
"github.com/xssnick/tonutils-go/address"
)
if _, err := address.ParseAddr("EQCD39VS5j...HUn4bpAOg8xqB2N"); err != nil {
return errors.New("invalid address")
}
try {
Address.of("...");
} catch (e) {
// not valid address
}
try {
AddrStd("...")
} catch(e: IllegalArgumentException) {
// not valid address
}
Полное описание адреса на странице адресов смарт-контракта.
Работа с переводами
Проверка транзакций контракта
Транзакции контракта можно получить с помощью getTransactions. Этот метод позволяет получить 10 транзакций из некоторого last_transaction_id
и более ранних. Для обработки всех входящих транзакций необходимо выполнить следующие шаги:
- Последний
last_transaction_id
можно получить с помощью getAddressInformation - Список из 10 транзакций необходимо загрузить с помощью метода
getTransactions
. - Обрабатывайте транзакции с непустым источником во входящем сообщении и назначением, равным адресу аккаунта.
- Следующие 10 транзакций долж ны быть загружены, и шаги 2,3,4,5 должны быть повторены, пока не будут обработаны все входящие транзакции.
Отслеживание входящих/исходящих транзакций
Во время обработки транзакций можно отслеживать поток сообщений. Поскольку поток сообщений представляет собой DAG, достаточно получить текущую транзакцию с помощью метода getTransactions и найти входящую транзакцию по out_msg
с помощью tryLocateResultTx или исходящие транзакции по in_msg
с помощью tryLocateSourceTx.
- JS
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
- Сервис должен развернуть
wallet
и поддерживать его финансирование, чтобы предотвратить уничтожение контракта из-за платы за хранение. Обратите внимание, что плата за хранение обычно составля ет менее 1 Toncoin в год. - Сервис должен получить от пользователя
destination_address
и необязательныйcomment
. Обратите внимание, что на данный момент мы рекомендуем либо запретить незавершенные исходящие платежи с тем же набором (destination_address
,value
,comment
), либо правильно запланировать эти платежи; таким образом, следующий платеж будет инициирован только после подтверждения предыдущего. - Форма msg.dataText с
comment
в виде текста. - Форма msg.message, которая содержит
destination_address
, пустойpublic_key
,amount
иmsg.dataText
. - Форма Action, которая содержит набор исходящих сообщений.
- Используйте запросы createQuery и sendQuery для отправки исходящих платежей.
- Сервис должен регулярно опраш ивать метод getTransactions для контракта
wallet
. Сопоставление подтвержденных транзакций с исходящими платежами по (destination_address
,value
,comment
) позволяет отмечать платежи как завершенные; обнаруживать и показывать пользователю соответствующий хэш транзакции и lt (логическое время). - Запросы к
v3
высоконагруженных
кошельков имеют срок действия, равный по умолчанию 60 секундам. По истечении этого времени необработанные запросы можно безопасно повторно отправлять в сеть (см. шаги 3-6).
Если прикрепленное value
слишком мало, транзакция может быть прервана с ошибкой cskip_no_gas
. В этом случае Toncoins будут успешно переведены, но логика на другой стороне не будет выполнена (TVM даже не запустится). Подробнее об ограничениях газа можно прочитать здесь.
Получение идентификатора транзакции
Может быть непонятно, что для получения дополнительной информации о транзакции пользователь должен сканировать блокчейн с помощью функции getTransactions. Невозможно получить идентификатор транзакции сразу после отправки сообщения, так как транзакция должна быть сначала подтверждена сетью блокчейна. Чтобы понять требуемый конвейер, внимательно прочтите Отправка платежей, особенно 7-й пункт.