在本课中,我们将回顾经典的 NFT 项目的代码。此外,我们还将学习 SoulBound (SBT) Item。
3 s- }' P, d2 h- E- i' |' g) X2 LNFT Item合约- int min_tons_for_storage() asm "50000000 PUSHINT"; ;; 0.05 TON6 B) {' {& H+ ]7 g/ v! {. O
复制代码就像在前面的课程中一样,我们看到有一些常量显示了我们应该在合约上存储的最低代币数量,以便能够支付租金。
+ {: v2 J# C) A5 v+ w, \- (int, int, slice, slice, cell) load_data() {- _3 H' W. a9 `
- slice ds = get_data().begin_parse();
9 A E: X @" x/ K" [ - var (index, collection_address) = (ds~load_uint(64), ds~load_msg_addr());
7 r2 a- N- O- F+ L - if (ds.slice_bits() > 0) {
3 [4 \$ ^3 n+ O; T+ i; F/ F& ~) d4 M - return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref());
( s5 N6 U' l/ O8 x( j! J$ ? - } else { * a' C% w! J, B! _! }) b
- return (0, index, collection_address, null(), null()); ;; nft not initialized yet
0 g; t% T+ {. r% h% w6 J - }
" S, ]. ?$ B u h6 G7 E" R - }
复制代码然后我们看到 load_data. 这个 load_data 与我们以前看到的有些不同。通常,我们只是从本地存储区读取数据,但在这里,我们有一些更复杂的逻辑。因此,我们打开本地存储的Cell进行读取,然后读出 index 和 collection_address. 我们知道什么是索引和 Collections 地址。 然后,我们尝试读取 NFT Item的元数据。首先,我们检查它是否存在。如果是,我们返回 –1, 以及Collections地址、所有者地址和元数据。或者我们只返回 0, 索引和Collections地址时,没有所有者和元数据。这意味着 NFT 尚未初始化。基于这一点,我们将在后面的代码中做出一些决定。 - () store_data(int index, slice collection_address, slice owner_address, cell content) impure {% i2 X; |1 @: ^" K/ H% T1 ~! N
- set_data(
0 M+ |# |, {% L1 W/ H. C! Y - begin_cell()) C2 Q) ^/ B7 Z) m/ @; m
- .store_uint(index, 64)
( H; c8 K5 r& K+ t6 C" b - .store_slice(collection_address)
3 c; S8 \8 u% G7 Y; M3 z - .store_slice(owner_address)
' C6 T! y' V* @ - .store_ref(content)3 H4 t; b) d0 s6 U* |$ y
- .end_cell()
6 u& h' z+ w: P9 u) G4 L - );. m4 Z* h: k3 O. }) K5 K
- }
复制代码我们还看到 store_data 就像我们调查的其他合约一样,这只是正常情况。 + A- |# s3 }' z- A
- () send_msg(slice to_address, int amount, int op, int query_id, builder payload, int send_mode) impure inline {5 k6 E$ K5 f; g
- var msg = begin_cell()
+ Q7 r* m0 U/ [ L) ?: E - .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000! A% \; V$ O% ?- N* r2 T7 w- ?' U N
- .store_slice(to_address)
$ f+ T; t& W' |7 o3 N, A - .store_coins(amount)
; I1 y9 p/ E1 l4 Q3 V - .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1), t# K7 n% q# _) |! I& K# z
- .store_uint(op, 32)
8 X0 d! K, M9 W# L) e - .store_uint(query_id, 64);" x# P" _3 |" U- m
- 6 n' f5 L+ b% @& _5 C
- if (~ builder_null?(payload)) {
8 _! L6 m7 G; v' T% \+ K: o - msg = msg.store_builder(payload);( ?' `7 {4 o1 l, Z- W
- }
复制代码在这里,我们封装了发送消息的逻辑,因此我们可以在合约的其他逻辑中使用这个函数。因此,只要我们想发送信息,就可以使用这个函数。它接受目标地址、我们应附加的金额、操作代码、查询 ID、可能的有效载荷(这次是以生成器格式)以及消息的发送模式。这就是我们发送信息的逻辑。 我们的另一个函数是 transfer_ownership. 让我们稍后再研究,一旦我们在 recv_internal 逻辑,因为它是这里最重要的功能。 - () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
F# c; L ?& o! q - if (in_msg_body.slice_empty?()) { ;; ignore empty messages
! `. S; ~0 m: m - return ();
3 C& i( u% O& J' u2 M( q; r# a - }/ L, C. H' r9 [3 ^+ v
/ T' p) p x1 U5 r. n- slice cs = in_msg_full.begin_parse();$ Y$ m) q* T! M: G; K
- int flags = cs~load_uint(4);
! V6 e& @. ~! T2 B. R/ l - 6 w' N; W! o y$ e0 l+ T
- if (flags & 1) { ;; ignore all bounced messages
7 Y+ r. i: v0 d, ? - return ();
. K, M; C8 F. J. L0 ~3 T - }
4 P$ E4 F5 W! s0 P: A - slice sender_address = cs~load_msg_addr();
# ?1 Y% m3 Z; D, N5 F, v; K
+ G7 w+ ~& T9 ?: u# U# N' z- cs~load_msg_addr(); ;; skip dst
& W, ~! k+ R- f4 k/ _$ Y2 X - cs~load_coins(); ;; skip value* }8 u6 b+ d' G+ I! i# u0 X
- cs~skip_bits(1); ;; skip extracurrency collection
! D! ?8 a+ e" A - cs~load_coins(); ;; skip ihr_fee+ J: v/ h$ m" U4 A. j9 x* \* z
- int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs) w6 H$ {/ j+ H4 E
' O K2 |, L7 X0 @+ P- (int init?, int index, slice collection_address, slice owner_address, cell content) = load_data();8 B7 T& g/ m5 S$ Y% O
- if (~ init?) {
, n4 n/ g$ I8 y# C2 B - throw_unless(405, equal_slices(collection_address, sender_address));
/ H' P! I( ~! J9 I4 _9 o - store_data(index, collection_address, in_msg_body~load_msg_addr(), in_msg_body~load_ref());
* O9 p$ R7 L, ?% c) W - return ();0 L* j7 D0 N ~8 W% ^# m
- }
复制代码我们来看看它是怎么做的。我们再次忽略空的 in_msg_body 消息,读取标志,忽略退回的信息,并检查发件人是谁。现在我们跳过一些内容,比如信息的目标地址之类的,因为你已经知道这个Cell的内容是什么了。接下来是 fwd_fee 再次根据上一笔交易估算发送下一笔交易的成本。 然后加载数据。正如你所记得的,我们加载数据的方式与这里的实现方式相同。因此,我们要检查它是否未初始化,然后我们期待实际获得这些数据,期待 in_msg_body 以获得所有者的地址和带有元数据的Cell。但这只能通过 collection_address, 因此我们要检查它是否等于发件人地址。然后我们就可以初始化 NFT Item了。 - int op = in_msg_body~load_uint(32); Q7 g' z4 ?1 c2 l5 [4 x
- int query_id = in_msg_body~load_uint(64);8 W( P4 k7 [2 _6 a5 |
- ! ^, r2 I, ^* |
- if (op == op::transfer()) {
1 a8 D4 c# p5 c5 V, H; P0 B - transfer_ownership(my_balance, index, collection_address, owner_address, content, sender_address, query_id, in_msg_body, fwd_fee);
- w$ Y8 w/ ?) ^' E - return ();8 ?4 k$ e) u/ O2 s6 K' e
- }
* ^$ o9 d$ S$ [) @0 F3 y; E% s: P" W - if (op == op::get_static_data()) {( p( J0 j1 b* j8 B0 E0 } w
- send_msg(sender_address, 0, op::report_static_data(), query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message
7 B; |+ d4 X, Z* E- j% i1 t0 u - return ();
: P! S" A8 S% c - }
复制代码接下来是操作代码,可以是 op::transfer 或 op::get_static_data. 这是我们要处理的两个可能值。当我们说 get_static_data, 我们会立即向发送静态数据请求的用户发送一条信息。我们只是报告数据,所以我们会向他发送 index 和 collection_address. 这是 get_static_data 函数。正如你所看到的,这是我们的第一个 send_ msg 用例 , |: v- [/ {/ J$ f7 f% G
- () transfer_ownership(int my_balance, int index, slice collection_address, slice owner_address, cell content, slice sender_address, int query_id, slice in_msg_body, int fwd_fees) impure inline {
a3 ^% Q, q7 R$ l2 N - throw_unless(401, equal_slices(sender_address, owner_address));6 x/ U% O) N! @$ S! w# `
* {) Y2 A2 h& l6 m& ^; {- slice new_owner_address = in_msg_body~load_msg_addr();
6 \$ E- Y6 m8 b - force_chain(new_owner_address);
0 ^4 j! D4 A6 Z/ d3 y - slice response_destination = in_msg_body~load_msg_addr();
, R7 E/ x/ K% c W1 ? - in_msg_body~load_int(1); ;; this nft don't use custom_payload+ Y, P9 A) r3 Q9 e/ i5 r2 ]9 R
- int forward_amount = in_msg_body~load_coins();
- R. f# ~5 ~' L0 v$ G5 [$ S' Z - throw_unless(708, slice_bits(in_msg_body) >= 1);
! I" x0 M9 @0 K" \
0 G& [1 a! l+ M- int rest_amount = my_balance - min_tons_for_storage();( P3 a" E$ _4 P% j9 [7 m
- if (forward_amount) {
3 O4 A- D) ]! r Z. p - rest_amount -= (forward_amount + fwd_fees);
% ?" a$ \2 {" w K# O# E - }
" A+ ^" q' M. A9 l - int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 00
2 ?2 s% K& v# S* m - if (need_response) {0 _* Y, \2 ?" L7 {2 w! F
- rest_amount -= fwd_fees;4 S* n: {. O9 |) H* A: F+ r
- }
5 k* ], }- I# A ~2 b8 _# s- c - " P/ _/ n9 \4 C: H3 d
- throw_unless(402, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response4 c0 R9 q. b1 |1 ^; h5 z* g; O
- 6 H4 Z' v9 k \5 d5 y
- if (forward_amount) {
4 r( r6 F3 Y. w; c8 K3 _0 {0 \ - send_msg(new_owner_address, forward_amount, op::ownership_assigned(), query_id, begin_cell().store_slice(owner_address).store_slice(in_msg_body), 1); ;; paying fees, revert on errors+ Q% y* E$ h: J% q# \
- }0 R. Z, l1 v. [ q, O
- if (need_response) {
. d4 [2 ]* g3 U9 w; q: @% w - force_chain(response_destination);
4 _+ C0 J* g8 ? - send_msg(response_destination, rest_amount, op::excesses(), query_id, null(), 1); ;; paying fees, revert on errors
9 ]6 @# g3 B" B) f - }
) b: B6 E- `! G( q - ( g( }; y; i" j5 e' I* f2 m
- store_data(index, collection_address, new_owner_address, content);8 x( t; v. Z* Y4 d! K4 a* R! J
- }
复制代码这里有一个转移所有权的功能。首先,我们要检查物品的所有者地址是否就是发送信息的人,因为只有他才能转移所有权。然后,我们读出新所有者的地址。我们 force_chain 以确保他在同一条链上。然后我们检查是否有 response_destination, 因此,一旦我们转移了这个所有权,我们是否应该告诉某人向另一个人发送信息。我们检查 forward_amount 是多少,如果有人要求的话。然后,我们就可以计算出,在发出信息之后,我们就会有足够的钱 forward_amount 到目的地。然后,我们要弄清楚是否需要回应,如果存在 response_destination 。如果存在,其剩余金额必须大于 0。 下面我就向大家简单介绍一下它的工作原理。如果存在 forward_amount ,我们将发送一条信息,通知用户所有权已由该 forward_amount 分配。如果我们需要回应,我们将 force_chain 以确保响应目的地在同一链上。我们发送的回复通常包括超额费用部分和所有额外资金;它们只是被转发到 response_destination. 然后,我们只需使用 new_owner_address 保存数据。基本上,这是唯一会发生变化的地方、 transfer_ownership 是这里的核心函数之一。您拥有一件物品,有时您会将其所有权转让给其他人或出售,这取决于交易的类型。 & Z6 [8 h0 B t, ?$ w6 w4 n
SBT item合约GetGems repository 正在主办一些不同的 NFT 合约、藏品、销售和市场以及一些拍卖活动。这里是了解更多与 NFT 藏品、物品及其相关的合约的好地方。但我想向您展示一份确切的合约,我们可以对其进行深入研究。 sbt-item 是一种灵魂绑定代币(SBT)。灵魂绑定与 NFT 非常相似,但不同之处在于灵魂绑定代币不能擅自转让给其他人。我们仔细看看它的代码。 - global int storage::index;
$ P q" t0 e- h - global int init?;
! X1 X: ?, U% O7 k- p - global slice storage::collection_address;
- S( e1 f' _' }' E7 v9 j8 z - global slice storage::owner_address;
) S2 l. m; q- y2 U' y - global slice storage::authority_address;: I# ^/ I- j: e! W1 I% u: P, I
- global cell storage::content;
* j% I0 x/ ~) i( L, M8 p+ L) ? - global int storage::revoked_at; d7 W0 ~' I( ~! {# @
- () load_data() impure {
1 ]+ S1 \8 s) C N6 D6 K - slice ds = get_data().begin_parse();
9 q" I" o/ V+ r4 z; K4 B- @7 T! x - 9 ]7 G( E4 ^: d% Z, z
- storage::index = ds~load_uint(64);
. T* e8 U) O" k2 z2 n. Z% F - storage::collection_address = ds~load_msg_addr();
# _# F& c# w* [ - init? = false;
6 n8 n: W/ `/ _- L/ V! A2 M5 ] - . r# G) r& U* N, C& D6 D4 ~9 T- H6 n
- if (ds.slice_bits() > 0) {5 f; v6 p/ t4 s, c% B+ ^& G
- init? = true;
( O4 S& r0 A- V- X; l1 w - storage::owner_address = ds~load_msg_addr();) B1 `4 A+ ^# i, o6 o
- storage::content = ds~load_ref();
& {: Y- F" s4 b+ e; M7 p9 z - storage::authority_address = ds~load_msg_addr();
/ g( o L( |) ~, t: {1 j$ ? - storage::revoked_at = ds~load_uint(64);
- Z* I( o9 p6 I4 T6 d5 K$ f9 z. x - }7 n0 k4 `0 h2 f
- }
复制代码这里有一些变量: storage::index, storage::collection_address. 我们还定义了其他全局变量,这只是 GetGems 实现的一个例子。你不必用同样的方法。我想在这里重点谈谈其他部分:
4 C3 Y/ S0 z# d; q: G% K- if (op == op::request_owner()) {
6 y6 S! r0 E% F" f - slice dest = in_msg_body~load_msg_addr();
- b/ t m3 o; J& ~- E# h) X1 E - cell body = in_msg_body~load_ref();
) D1 B. `5 Y# x, e! q - int with_content = in_msg_body~load_uint(1);" H; N, p1 R2 E
! n) w7 T8 n0 S7 q% B& x- var msg = begin_cell()
2 m( v5 n L# |8 C5 q - .store_uint(storage::index, 256)* B% E: U. {, l, i& B# X
- .store_slice(sender_address)7 v$ T% s1 `" R( `) P: p
- .store_slice(storage::owner_address)
. `5 Z" n: R% Q! D# u. e! } - .store_ref(body)+ R# a& j, }, e) \* q
- .store_uint(storage::revoked_at, 64)% V' G8 [6 ?/ J
- .store_uint(with_content, 1);* E( n3 T9 K* ?/ |: }
- % d3 s8 o3 S( N* z" n) N0 Q3 N
- if (with_content != 0) {7 b9 l% z' ]# d
- msg = msg.store_ref(storage::content);
5 V6 W) P5 O7 @+ H: J - }
5 Y% x: n" A) P1 D
! F8 h: D( ?! F& \+ M* }- ;; mode 64 = carry all the remaining value of the inbound message
" ?8 e/ D! {0 H9 j3 `0 @1 t - send_msg(flag::regular() | flag::bounce(), dest, 0, op::owner_info(), query_id, msg, 64);% `" M6 V& w s! [2 S
- return ();. y4 v8 k9 ]+ H U/ t7 \. y1 z; \
- }
6 Z! K3 O( I, p - if (op == op::prove_ownership()) {
6 r' ^6 ^* {: T* k x* g - throw_unless(401, equal_slices(storage::owner_address, sender_address));9 f' r8 l3 ~# c
- + d* Z+ t& N' O9 ?: f4 K
- slice dest = in_msg_body~load_msg_addr();" u4 c X8 v; U5 y0 U I
- cell body = in_msg_body~load_ref();% @2 a; y% [# E3 N* v$ w
- int with_content = in_msg_body~load_uint(1);
) v2 p9 V; K# g
W, e- r8 ?3 L! y& [" n- T- var msg = begin_cell()$ j: N/ v% n4 u% \6 v* k
- .store_uint(storage::index, 256)
) f+ J( y n K& p+ S3 e$ r - .store_slice(storage::owner_address) Q! C& f: X) m* V$ h
- .store_ref(body)
: j! i' I9 ]1 k. E - .store_uint(storage::revoked_at, 64)5 u: B( q- C, j2 k8 r5 b" U
- .store_uint(with_content, 1);
3 G" e6 P" @; W6 p. H. T$ g - ! n- ^' A, \: U# S0 V! E0 r
- if (with_content != 0) {2 `1 e3 ^/ u, E6 G' q7 }* j
- msg = msg.store_ref(storage::content);
+ O1 \! x# W) }$ U7 }. d" {; m7 P - }, V% N' K1 i( l
- 6 Y* S$ A, H- ? \, m3 q
- ;; mode 64 = carry all the remaining value of the inbound message
5 l1 H0 R$ M, @9 _% e - send_msg(flag::regular() | flag::bounce(), dest, 0, op::ownership_proof(), query_id, msg, 64);# y3 m: E0 F: s- N/ }5 i, c
- return ();. H# A" |8 ]+ N( h$ r# g
- }3 c- @- r/ X4 m; A8 v( @
- if (op == op::get_static_data()) {, `' o7 \6 L4 j1 T7 X6 A2 l* e( l
- var msg = begin_cell().store_uint(storage::index, 256).store_slice(storage::collection_address);; q4 x9 F' u& l
- : x$ E" H; N; n0 ^
- ;; mode 64 = carry all the remaining value of the inbound message
' J7 `% w( K& ^5 E - send_msg(flag::regular(), sender_address, 0, op::report_static_data(), query_id, msg, 64);
1 Q: X! S+ s; m. i& | - return ();) d" ]4 S6 H2 L# s
- }4 Z/ P6 R+ h2 d0 x- j- g2 c; h
- if (op == op::destroy()) {6 P0 m) r% }! b0 L- `
- throw_unless(401, equal_slices(storage::owner_address, sender_address));
' n2 m% z9 H2 r E- B( [
J0 d% m5 L, e! Y" r% D# }- send_msg(flag::regular(), sender_address, 0, op::excesses(), query_id, null(), 128);
. {8 F' Z" ?1 G- D
6 X" y% g+ I F }5 v' \! V/ ~7 A- H- storage::owner_address = null_addr();0 y1 X! d0 q0 {) y
- storage::authority_address = null_addr();
6 g4 Q. s7 {$ c9 e - store_data();
* ^8 M; W* j' T" F. c2 X! v' r - return ();3 S2 c0 ]0 J# S6 |/ E0 c8 K
- }- c$ M/ q2 h" C* R" g* C
- if (op == op::revoke()) {
& p# L5 K1 s: q - throw_unless(401, equal_slices(storage::authority_address, sender_address));
# [' s7 Q% |* F3 s9 l$ Q9 N - throw_unless(403, storage::revoked_at == 0);1 u/ }- r2 z* w6 x0 N3 I3 L
8 B. a7 r; H! z! N, \6 A- L- storage::revoked_at = now();- v) p$ |! h) N: ]+ d
- store_data();% T( h2 r" S& F
- return ();9 ]" h9 ~& [% J3 l" o" ?
- }. ^# S3 s. X2 `# `
- if (op == op::take_excess()) {
! G' o8 {1 T# y3 e2 W# B( t) y: M0 I2 S - throw_unless(401, equal_slices(storage::owner_address, sender_address));
% G% f# ^2 Q# ], }9 p4 Z" j
: x6 K. V3 r9 p# T# \+ u- ;; reserve amount for storage
5 J1 r. l9 r% B - raw_reserve(min_tons_for_storage(), 0);
$ o" W% _; K1 [ - ; z2 @4 @( Y+ C1 C
- send_msg(flag::regular(), sender_address, 0, op::excesses(), query_id, null(), 128);
^( I( u% j6 |( ]5 e - return ();
1 y ]4 b+ L2 i6 ]! Q: F2 e1 A - }+ K; V" E3 a0 Y G7 ~- B8 Y: A
- if (op == op::transfer()) {: _$ r3 @9 ?; p2 o; }+ H& _2 X
- throw(413);4 C M+ D W* E: ]
- }
复制代码如你所见,这里有许多注释,但我们不会逐行深入研究。你可以看到,这个通证将被用于多种不同的用途,但与普通的 NFT 不同。这里是合约,你可以申请这个合约的所有者。一些操作代码可以请求所有权证明。您可以获取静态数据。你还可以销毁或撤销这个标记。你可以拿走多余的东西,比如,存放在这里的一些钱。如果你是这个合约的所有者,你就可以申请。但你显然不能转让它,就像你在这里看到的一样。这是一种完全不同类型的代币,如果你对 NFT 感兴趣,就应该在 GetGems 存储库里多花点时间。 我想向你展示的是,NFT collections 的所有代码可以保持不变,但collections合约部署的 item 可以不同。例如,你可以用 SBT item 替换 NFT item。这样您就会有一个 SBT item collection,然后您只需更改 code 来初始化你的 collection: - (slice, int, cell, cell, cell) load_data() inline { F, H0 G; v6 Q1 R5 R, t9 ?+ c
- var ds = get_data().begin_parse();% i' s3 q3 ]6 I$ I
- return
3 c9 {2 F: B( ^ - (ds~load_msg_addr(), ;; owner_address
) E; L6 e% ]2 a - ds~load_uint(64), ;; next_item_index
# k6 u# P/ g9 J' i' L5 p/ V0 K5 O - ds~load_ref(), ;; content
! v* C( X% ?' {* Y - ds~load_ref(), ;; nft_item_code
4 }6 T. N& B! |# [8 ^ - ds~load_ref() ;; royalty_params
' O5 U( C! D; q6 ]' ?; A - );
5 \3 U8 v" b6 [2 |0 e R7 \" Y, V - }
复制代码这样,您就可以实际部署 SBT item collection。此外,您还可以创建一个独立的没有collection SBT item。我们不会掌握这些item的编程,我只是想向大家展示它在更大范围内的工作原理。
) f. \8 u$ z+ @1 t/ ^7 ~ 结论9 c8 d3 L. r( u; J( I7 \ x. I
从第 3 章中非常简单的逻辑开始,我们已经了解了 NFT 或 Jettons 等复杂合约中的许多内容。这就是一个很好的例子,你可以一步一步地学习某些语法和概念,然后就可以学习更复杂的逻辑。希望这几节课能帮助你理解 Jettons 和 NFTs 的真正含义,以及如何处理它们的代码库--弄清楚它们是如何工作的,并在使用 TON 构建时学习更多可用的语法和架构。 非常感谢您的关注。对你们来说,这可能是最难的一章,虽然我们没有编写任何代码;我们只是在阅读一些你们从未见过的东西,而且非常复杂。你们能坚持到最后一课,我真的很骄傲,我期待着在接下来的章节中看到你们。希望你们喜欢目前的课程。 . d! t& G, w7 A+ e' M
( K7 h {0 d: P0 R5 Y
* H* _$ c( k; i4 r
/ E9 h. E# r) e! N* j/ T$ f
( U% O# C2 c" G/ Y q4 P
) b, A# Y! u8 _3 X! G* W! U Y/ i3 B8 m* `' u4 z! L
' Q* u) W e8 ^/ @8 u |