TON:Jetton主合约介绍
🌽

TON:Jetton主合约介绍

Jetton,也被称为同质化代币标准,编号是74。在这个包含所有标准的存储库中可以找到关于Jetton合约如何运作的完整描述。里面可以看到两个合约:一个是主合约(或铸币合约),另一个是钱包合约。
钱包智能合约必须处理某些消息。例如,转账消息及其所有相关操作,还需要处理以及销毁消息。还需要调用get_wallet_data方法,用于返回其自身的数据。这是实现钱包合约的主要方式。
在主合约部分我们不放置代码,而是放置功能描述,规定功能应如何实现,以便其他合约能够与Jetton合约进行交互。铸币合约实际上负责铸造所有这些Jettons。你还会为每个Jetton的用户和外部服务创建钱包合约,这些服务有时会与用户的钱包合约交互,有时与主合约(铸币合约)交互。为了避免每次都需要理解其工作原理,我们设定了一套标准,规定它们必须如何运作。例如,钱包显示余额,而不处理每种Jetton类型。
假设某些功能不符合Jetton标准的要求,无论是钱包、区块链浏览器还是其他服务,都不能被视为Jetton,必须遵守这些规则才能被视为Jetton。
阅读Jetton主合约和钱包合约对理解其运作原理非常有帮助,这能帮助熟练掌握如何构建数据单元,以及如何在自定义合约中实现复杂逻辑,即使这些合约与Jetton无关。Jetton和NFT合约是学习如何编写复杂代码的绝佳途径。
现在让我们深入研究Jetton合约的代码。正如前文所说,这里有两个合约。我们已经有了它们的最佳实现实践。你不必使用这些代码,但实现必须符合这些标准。
在这个存储库中,进入ft文件夹(同质化代币,即Jetton)。这里可以看到了多个合约。将重点关注Jetton铸币合约和Jetton钱包合约上面。这里还有其他合约,例如Jetton Minter ICO——它只是初始代币发行功能的实现,不会深入探讨这个,但去查看了解这些代码,了解复杂的逻辑如何实现,会很有益处。
这里还会使用一些文件,比如Jetton utils,因为Jetton utilities包含某些逻辑,可以帮助Jetton铸币合约的主文件。此外还有操作码文件和参数文件——这些只是帮助文件,我们封装了一些逻辑以便重用。
克隆这个代币合约标准实现的存储库:
git clone https://github.com/ton-blockchain/token-contract.git
从Jetton铸币合约开始,因为这是初始化其他一切的核心合约,一开始会看到存储方案:
;; storage scheme ;; storage#_ total_supply:Coins admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage; (int, slice, cell, cell) load_data() inline { slice ds = get_data().begin_parse(); return ( ds~load_coins(), ;; total_supply ds~load_msg_addr(), ;; admin_address ds~load_ref(), ;; content ds~load_ref() ;; jetton_wallet_code ); } () save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline { set_data(begin_cell() .store_coins(total_supply) .store_slice(admin_address) .store_ref(content) .store_ref(jetton_wallet_code) .end_cell() ); }
这个逻辑封装了读取和保存数据到存储中的操作,可以在代码的任何地方使用这个函数来简化数据读取的过程。首先,我们有total_supplyadmin_address:这是管理整个合约的管理员,有些消息只能从管理员那里接受。content:它基本上是描述代币独特性的数据信息。还可以查看数据标准——这里有关于这些数据应如何组织的标准,包括标题、描述及其他内容,因此浏览器和其他服务可以使用这些合约的元数据向用户展示他们正在处理的Jetton是什么。
最后,是jetton_wallet_code。之前提到过合约是可以扩展的,我们有一个主要合约——即铸币合约。它在铸造代币时会部署钱包合约。然后,钱包合约也可以部署其他钱包合约。所以,如果我向某个还没有钱包的人发送资金,我会携带代码发送。这些钱包合约也会保留钱包代码,稍后会看到。
现在,让我们去查看标准存储库,看看关于数据的标准。这里有一些指导,比如创建NFT集合元数据、NFT项目元数据、Jetton元数据示例——这些标准详细描述了将存储哪些数据。
有一个重要提示,对于链下内容,你应当将第一个字节设置为0x01,这表明URI指向一个JSON。因此,你基本上是在告诉任何读取该数据的人:“我有链下数据。看这个链接,下载JSON,里面有你需要的东西。
但如果你是存储在链上,你必须设置0x00。这表明键值字典存储在内存的其他部分,了解这些存储在内容单元中的内容很重要。
在读取和保存数据到存储后进入mint_tokens函数。这个函数实现了铸造功能:
() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure { cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); slice to_wallet_address = calculate_jetton_wallet_address(state_init); var msg = begin_cell() .store_uint(0x18, 6) .store_slice(to_wallet_address) .store_coins(amount) .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) .store_ref(state_init) .store_ref(master_msg); send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors }
接下来我们看到的是recv_internal,这是任何智能合约的核心函数。所以在这里,你实际会读取消息的第一个参数。
() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { if (in_msg_body.slice_empty?()) { ;; ignore empty messages return (); } slice cs = in_msg_full.begin_parse(); int flags = cs~load_uint(4); if (flags & 1) { ;; ignore all bounced messages return (); } slice sender_address = cs~load_msg_addr(); int op = in_msg_body~load_uint(32); int query_id = in_msg_body~load_uint(64); (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data();
然后,根据收到的操作码,可以运行更复杂的功能:
if (op == op::mint()) { throw_unless(73, equal_slices(sender_address, admin_address)); slice to_address = in_msg_body~load_msg_addr(); int amount = in_msg_body~load_coins(); cell master_msg = in_msg_body~load_ref(); slice master_msg_cs = master_msg.begin_parse(); master_msg_cs~skip_bits(32 + 64); ;; op + query_id int jetton_amount = master_msg_cs~load_coins(); mint_tokens(to_address, jetton_wallet_code, amount, master_msg); save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code); return (); } if (op == op::burn_notification()) { int jetton_amount = in_msg_body~load_coins(); slice from_address = in_msg_body~load_msg_addr(); throw_unless(74, equal_slices(calculate_user_jetton_wallet_address(from_address, my_address(), jetton_wallet_code), sender_address) ); save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code); slice response_address = in_msg_body~load_msg_addr(); if (response_address.preload_uint(2) != 0) { var msg = begin_cell() .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddressInt dest:MsgAddressInt ... .store_slice(response_address) .store_coins(msg_value - 1) .store_bit(1) ;; null custom payload .store_uint(1 + 2 + 1 + 2 + 1 + 1 + 1, 1 + 4 + 4 + 32 + 32 + 32 + 1) .store_ref(begin_cell() .store_uint(0x178d4519, 32) ;; burn_notification#178d4519 query_id:uint64 amount:(VarUInteger 16) .store_uint(0, 64) .store_coins(jetton_amount) .end_cell()); send_raw_message(msg.end_cell(), 1); } return (); } if (op == 3) { ;; change admin throw_unless(73, equal_slices(sender_address, admin_address)); slice new_admin_address = in_msg_body~load_msg_addr(); save_data(total_supply, new_admin_address, content, jetton_wallet_code); return (); } if (op == 4) { ;; change content, delete this for immutable tokens throw_unless(73, equal_slices(sender_address, admin_address)); save_data(total_supply, admin_address, in_msg_body~load_ref(), jetton_wallet_code); return (); } throw(0xffff); }
最后要做的是保存数据。刚刚展示了save_data,这是封装数据保存的地方。所以这是铸币合约的一般结构。实际上,有一个消息处理程序recv_internal,它基于不同的操作码执行不同的操作。所以这是操作Jetton的方式——处理消息,处理代币,进行转账或执行其他功能。
好了,这就是Jetton的实现方式,最好继续阅读代码,并研究其他功能,分析Jetton代码是掌握智能合约编程的一个绝佳方式。