付款处理
本页面包含了关于在TON区块链上处理(发送和接收)数字资产的概览和具体细节。
建议在阅读本教程之前先熟悉一下 资产处理概述。
钱包智能合约
TON 网络上的钱包智能合约允许外部参与者与区块链实体互动。
- 验证所有者身份:拒绝试图代表非所有者处理或支付费用的请求。
- 提供重放保护:防止重复执行同一请求,如向另一个智能合约发送资产。
- 启动与其他智能合约的任意交互。
第一个挑战的标准解决方案是公钥加密法:钱包
存储公钥,并检查传入的请求信息是否由相应的私钥签名,而该私钥只有所有者知道。
第三个挑战的解决方案也很常见;一般来说,请求包含一个完整的内部信息,由 钱包
发送到网络。不过,对于重放保护,有几种不同的方法。
自托管服务
基于 Seqno 的钱包使用最简单的方法对消息进行排序。每条信息都有一个特殊的 "seqno "整数,必须与存储在 wallet
智能合约中的计数器一致。钱包
会在每次请求时更新计数器,从而确保一个请求不会被处理两次。有几个 钱包
版本在公开可用的方法上有所不同:可以通过过期时间限制请求,也可以使用相同的公钥拥有多个钱包。不过,这种方法的一个固有要求是逐个发送请求,因为 seqno
序列中的任何间隙都会导致无法处理所有后续请求。
高负载钱包
这种 钱包
类型采用的方法是在智能合约存储中存储已处理的未过期请求的标识符。在这种方法中,任何请求都会被检查是否与已处理的请求重复,如果检测到重复请求,就会丢弃。由于过期,合约可能不会永久存储所有请求,但会删除因过期限制而无法处理的请求。向该 钱包
发出的请求可以并行发送,不会受到干扰,但这种方法需要对请求处理进行更复杂的监控。
社区制作
要通过 TonLib 部署钱包,您需要
- 创建钱包,获取余额,进行转账
- 形成与已启用的
钱包
之一相对应的 InitialAccountWallet 结构。目前可用的有:wallet.v3
、wallet.v4
、wallet.highload.v1
、wallet.highload.v2
。 - 通过 getAccountAddress 方法计算新的
wallet
智能合约的地址。我们建议使用默认版本0
,并在基链workchain=0
中部署钱包,以降低处理和存储费用。 - 向计算出的地址发送一些 Toncoin。请注意,您需要在
non-bounce
模式下发送,因为该地址还没有代码,无法处理收到的信息。non-bounce
标志表示即使处理失败,也不会通过退回消息返还钱款。我们不建议在其他交易中使用non-bounce
标记,尤其是在携带大额资金时,因为退回机制在一定程度上可以防止错误。 - 形成所需的 action,例如只用于部署的
actionNoop
。然后使用 createQuery 和 sendQuery 启动与区块链的交互。 - 使用 getAccountState 方法在几秒钟内检查合约。
在钱包教程中阅读更多内容。
社区制作
使用psylopunk/pytonlib(The Open Network的简单Python客户端):
- 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
}
智能合约地址 页面上的完整地址描述。
TON 上的数字资产
检查合约交易
可以使用 getTransactions 获取合约的交易。该方法允许从某个 last_transaction_id
和更早的交易中获取 10 个交易。要处理所有收到的交易,应遵循以下步骤:
- 可以使用 getAddressInformation 获取最新的
last_transaction_id
。 - 应通过
getTransactions
方法加载10笔交易。 - 处理输入信息中来源不为空且目的地等于账户地址的交易。
- 应加载接下来的 10 笔交易,然后重复步骤 2、3、4、5,直到处理完所有收到的交易。
检索传入/传出交易
可以在事务处理过程中跟踪消息流。由于消息流是一个 DAG,因此只需使用 getTransactions 方法获取当前事务,并使用 tryLocateResultTx 通过 out_msg
找到传入事务,或使用 tryLocateSourceTx 通过 in_msg
找到传出事务。
- 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();
基于 Seqno 的钱包
从 TMA USDT 支付演示 了解支付处理的基本示例
- 服务应部署一个
钱包
,并保持其资金充足,以防止因存储费用而导致合约毁坏。请注意,存储费一般每年少于 1 Toncoin 。 - 服务应从用户处获取
destination_address
和可选的comment
。请注意,在此期间,我们建议禁止未完成的、具有相同(destination_address
,value
,comment
)设置的外发付款,或适当安排这些付款的时间;这样,只有在上一笔付款得到确认后,才会启动下一笔付款。 - 用
comment
作为文本形成 msg.dataText。 - 形成包含
destination_address
、空public_key
、amount
和msg.dataText
的msg.message。 - 形成包含一组传出消息的Action。
- 使用 createQuery 和 sendQuery 查询向外发送付款。
- 服务应定期轮询
wallet
合约的 getTransactions 方法。通过(destination_address
,value
,comment
)匹配已确认的交易和已发出的付款,可以将付款标记为已完成;检测并向用户显示相应的交易哈希值和逻辑时间。 - 对
v3
高负载钱包的请求默认有 60 秒的过期时间。在此时间之后,未处理的请求可以安全地重新发送到网络(参见步骤 3-6)。
如果附加的 value
太小,交易会因错误 cskip_no_gas
而中止。在这种情况下, Toncoin 将被成功转移,但另一方的逻辑不会被执行(TVM 甚至不会启动)。关于 gas 限制,您可以在 此处 阅读更多信息。