|
本帖最后由 riyad 于 2025-3-20 00:45 编辑
; @- k/ \4 c5 S* d* G; j$ g. K& n/ q2 |& y3 |, x/ b
今天,我将带大家一起探索如何在 TON 区块链上用 FunC 语言和 Blueprint 工具开发一个红包智能合约。这篇教程是第一部分,重点介绍项目搭建、合约编写和成功编译的过程。我会分享完整的代码和项目结构,适合对 TON 开发感兴趣的新手。让我们开始吧! - B/ @5 O" ]4 b8 J+ @4 n1 p
什么是 TON 和红包合约? TON(The Open Network)是一个高效的区块链平台,以低成本和高吞吐量著称,非常适合开发去中心化应用。红包是中国文化中常见的分享方式,我想通过智能合约实现一个简单功能:用户存入 TON,指定份数,其他人可以领取。这篇文章将展示我到目前为止的进展——编写并成功编译合约。 ' W4 n1 g8 o: z9 P: C6 d
准备工作 在动手之前,确保你的环境准备好: 安装 Node.js:Blueprint 依赖 Node.js,建议使用最新 LTS 版本,从 nodejs.org 下载安装。 安装 Blueprint:在终端运行以下命令全局安装: 代码编辑器:我用的是 VS Code,安装 TON FunC 插件可以高亮语法,提升编码体验。 步骤 1:创建项目 我们用 Blueprint 初始化一个 TON 项目。打开终端,输入: 项目名称:我输入了 redpacket。 模板:选择 func-empty,因为我想从头写合约。 创建完成后,进入项目目录: 项目文件夹结构 Blueprint 生成的项目结构非常清晰,以下是 redpacket 文件夹的内容: 7 Q& c) U% b+ q
) u7 v( N) s. |6 J' E
步骤 2:编写红包合约代码 在 contracts/redpacket.fc 中,我编写了红包智能合约。以下是代码和详细注释: - #include "imports/stdlib.fc";7 X8 B, `; o x5 n7 Y0 u7 _9 J
* v* b" ~3 J5 _- // 准备内部消息的函数. X1 @2 V3 k( |* i6 c
- (cell) prepare_internal_msg(slice recipient, int amount, int bounce) {2 q0 a: r$ W4 L3 o
- return begin_cell()2 {/ ~! K( K- w1 Z
- .store_uint(0, 4) ;; 内部消息标记 (0x0)
: E* o1 |7 q, {, K, b7 c+ ^ - .store_slice(recipient) ;; 目标地址
' |% q! m! i% P6 o7 W! C8 `$ b - .store_coins(amount) ;; 发送金额$ w7 a, _, B! B) H) ]
- .store_uint(bounce, 1) ;; 是否可退回标志
! l* ?4 ]0 s0 n/ G! k! f1 A2 B7 q - .store_uint(0, 1 + 4 + 4) ;; ihr_disabled: 0, 无源地址和目标地址扩展, Q# L6 ?0 c( i7 y
- .store_uint(0, 64) ;; ihr_fee
# U% v) h, _3 I% U8 ^ - .store_uint(0, 64) ;; fwd_fee8 D. n, o/ E& [$ ]! u
- .store_uint(0, 64) ;; 创建逻辑时间, _/ i! d$ ]8 q* P0 T2 |5 f
- .store_uint(0, 64) ;; 创建时间戳
- [- l& W$ o# J, J - .store_uint(0, 1) ;; 无状态初始化
& j3 a3 S3 j( Q: y - .store_uint(0, 1) ;; 消息体为空 T4 T- I& |2 e- I0 E/ H
- .end_cell();1 j) o* {: |. h4 Z0 t: X
- }2 |1 f* e% w7 c/ R$ A3 z: v$ t
2 l: k6 u& d3 Z. I% w P A- // 处理内部消息的主函数
* C: @6 ?+ m1 J1 q) i- U( m - () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
6 z. ]* v& w A8 Y - ;; 检查合约是否有余额可用8 t( O+ w) X4 [+ f$ z: Y* \( ?9 U2 S
- if (my_balance <= 0) { throw(100); }
& F1 y! B& J# ]( I - ) K% ^/ f" T6 F( f4 u' O3 {- \& d
- ;; 解析传入消息
& m; a! B0 D9 A3 _/ Z - var parser = in_msg_full.begin_parse();" o6 {- I5 F( m/ P% m, ?0 s: H+ y% V; Z
- int flags = parser~load_uint(4);& P- J+ m; ^. s, y4 a9 P P. h
- slice sender_address = parser~load_msg_addr();
! t8 `' ^; g7 V' b! U; q' _ - int sender_hash = sender_address.slice_hash();: h' v; I4 O; L, N* V) f
- - d- P+ I+ y/ ?, s
- ;; 加载持久化存储(红包字典)' x1 x8 `8 F0 j+ n- H$ b: f
- var data = get_data();
1 J! } H; o* a% y# x# h - cell red_packets = new_dict();
% ]! F ~9 k; @4 W- m+ V& R - if (~ slice_empty?(data.begin_parse())) {
& f. ~) r' l$ h; z& e - var data_slice = data.begin_parse();8 W4 y5 s2 ~0 x6 k( n
- red_packets = data_slice~load_dict();( v3 R# m: R3 P( l0 K
- }- i9 W; d" Y6 m0 i, N
1 V. p% n$ \1 h( s9 D3 N3 O- ;; 检查消息体是否有操作(创建或领取)
. j# m/ _- {. f# O; A: u6 `9 M - if (~ slice_empty?(in_msg_body)) {
) f9 U" d) `( A+ j% G* N - var body_parser = in_msg_body;2 F- q# f0 r' u0 @, }* l- |% T* |+ S
- int op = body_parser~load_uint(8); ;; 操作码:1 = 创建,2 = 领取
; M" _$ d7 H8 D5 y( \4 U8 X - int red_packet_id = body_parser~load_uint(256); ;; 红包唯一ID$ `! x3 o( S$ V) m2 O# v
- : y' H/ Y0 R' m8 |( Q
- if ((op == 1) & (msg_value > 0)) { ;; 创建新红包
0 T6 S% o. X3 u3 @1 K - int claim_amount = body_parser~load_int(256); ;; 发送者指定每份领取金额/ \- N9 } e& T: d
- if ((claim_amount <= 0) | (claim_amount > msg_value)) { throw(103); } ;; 无效金额检查
) c/ x$ j( M% E9 P% {2 K
F$ e9 Q4 N+ {+ J, }- var packet_data = begin_cell()
: d: M2 h1 y# v - .store_int(msg_value, 256) ;; 红包总金额, C% w; i2 ]4 x! S) ~6 L4 g
- .store_int(claim_amount, 256) ;; 每份领取金额
. P8 w3 G9 `" J9 \ v - .store_dict(new_dict()) ;; 空领取记录字典
/ P1 R# o; z& e* C/ R) t: X5 P - .end_cell();' F7 {$ Y5 h) B
, d% B$ p0 b8 T( G' r- red_packets = udict_set_ref(red_packets, 256, red_packet_id, packet_data);
2 Z7 f$ l0 W/ T* e3 M4 f+ Q - set_data(begin_cell().store_dict(red_packets).end_cell());
; U& a8 q: b1 W! _* p: m G G/ @5 v - return ();
( G2 j! _ ^$ [" M& _+ @& E - } else {" N! M) Y7 k S, `/ ]
- if (op == 2) { ;; 领取红包4 M; k) {: N! T r
- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);( S! o- Q# O4 J( _$ {1 g
- if (exists == 0) { throw(104); } ;; 红包不存在3 M4 W4 k1 E5 |0 l( F3 Q1 ?
- - ^( v/ _ |2 L: w/ D
- var packet_parser = packet_cell.begin_parse();
; h& ?. A7 Y4 H* ]1 v - int stored_balance = packet_parser~load_int(256);
2 f$ {7 f, [. M6 Z; P: L/ y6 h - int claim_amount = packet_parser~load_int(256);
! n6 \7 S9 r+ R3 {. ? J - cell claimed = packet_parser~load_dict();: |) H# B3 i4 Z; S
- : \0 Z( I6 k9 ]0 C( Q' a% k8 n* t
- ;; 检查发送者是否已领取7 ?4 H0 l0 b5 m6 R4 S
- var (claimed_value, claimed_exists) = udict_get_ref?(claimed, 256, sender_hash);- R/ N. U% d0 w& P
- if (claimed_exists == 1) { throw(101); } ;; 已领取过
; N. k2 [4 ^: H0 J
; C* F; U, c% p9 W) T$ f( k- ;; 检查余额是否足够
- F6 P' @! z7 {, V* y - if (stored_balance < claim_amount) { throw(102); } ;; 余额不足
( K7 G9 A0 Z9 i$ h" C) C( t! } - 5 A0 ]% S" m7 Y; z& `( _3 ^7 @
- ;; 更新红包状态
. P3 K$ i: O" Y: f - stored_balance = stored_balance - claim_amount;5 B( u1 v& x2 v) b
- var value_cell = begin_cell().store_int(1, 8).end_cell();& C3 ]8 G' t5 T* [
- claimed = udict_set_ref(claimed, 256, sender_hash, value_cell);% [1 v; e: o* y
- , v4 V! J# ?( O
- ;; 保存更新后的红包数据6 E2 W" O! C0 o2 L
- red_packets = udict_set_ref(red_packets, 256, red_packet_id, 7 |/ r6 c, Z) k+ ?$ J
- begin_cell()
3 ^6 j% u8 f* k - .store_int(stored_balance, 256)+ P( e1 `* U/ ~, R4 H1 A: T
- .store_int(claim_amount, 256)
: k) P% v$ ]- Q( \ }5 U4 w' U - .store_dict(claimed)6 o% I$ o+ N$ R
- .end_cell());) M! Q" U0 H9 r6 x- M! |/ p
" z; ~6 I& P% f: }, K- set_data(begin_cell().store_dict(red_packets).end_cell());8 m1 M N0 S1 w% g( F6 ]
' W+ ~& B; o3 d( {' n- ;; 将领取金额发送给用户* G8 D, p- V; _9 Y0 B1 H1 [8 I
- send_raw_message(
/ _8 x# a; ~# d& ]: n D$ ^ - prepare_internal_msg(sender_address, claim_amount, 1),
$ ~, R8 r" y( W+ v - 64 ;; 单独支付 gas 费用) k# f: i: R ^! T t) c* }( g
- );
; z, ~! ~8 Q4 |% S* Y6 ]4 z, r9 A } - return ();
9 Z: H6 \2 s( a6 m4 O/ x# R$ x - }
0 k B: b9 y3 {! P W$ F - }2 H, y0 w, K) D. W, s* j& |* ~' q
- }
1 X& x% w/ ?$ E - throw(105); ;; 无效消息(未指定操作)
( P* C& Q3 {9 P5 ~- \9 @ - }$ \$ s. V9 v2 n
( S" J* f8 t: a- // 查询红包状态的外部方法: H4 W4 D' g/ s" Y6 g9 y* P% c
- ([int, int, cell]) get_red_packet_data(int red_packet_id) method_id {
+ X, @1 ]: I$ Z l - var data = get_data();
' X5 e9 i' d, z& ^% b3 ~ - cell red_packets = new_dict();5 ]6 J; J* k' V0 h* m1 G% W
- if (~ slice_empty?(data.begin_parse())) {% D Q0 |9 J ?* L f
- var data_slice = data.begin_parse();
( ?! G+ Q. H+ V( q; d - red_packets = data_slice~load_dict();' C" R* p& M/ x) u
- }/ L/ [, V$ p9 a1 ~# `
- , r8 i9 g6 J% [. g/ \
- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);- g7 P* z4 p" @# y8 @* j
- if (exists == 0) {- l* _) Q% t5 `' Z0 q. o
- return [0, 0, new_dict()];
* b6 K. V% K1 `& o' O: b2 d. g- K - }' O# R" x# I( }9 G4 a! @9 W) U
" t. R: I8 c( m% }* G+ H- var packet_parser = packet_cell.begin_parse();
, w! |* I( H, N J - int stored_balance = packet_parser~load_int(256);
4 d: x- Q5 B& j% [3 L - int claim_amount = packet_parser~load_int(256);5 \1 L1 e0 t. k- G" v V, E
- cell claimed = packet_parser~load_dict();$ k" \2 t( E4 K# Y8 t
- return [stored_balance, claim_amount, claimed];
7 E# Z {( _% ?/ a/ Q - }
复制代码代码说明 1. prepare_internal_msg 函数 这个函数用于构造一个内部消息,用于将 TON 转账给领取者。 7 m+ M: b& f$ m
功能:生成一个标准的 TON 内部消息单元(cell),包含目标地址、金额和是否可退回等信息。 参数: - recipient:目标地址(slice 类型)。
- amount:转账金额(单位:nanoTON)。
- bounce:是否可退回(1 表示可退回,0 表示不可退回)。
! J0 n e3 [8 \
实现: - 使用 begin_cell() 创建一个新的单元。
- 按 TON 消息格式依次存储:消息标记(0x0)、目标地址、金额、可退回标志等。
- 其他字段(如手续费、时间戳等)设为默认值 0。
' s! i( r. n; {9 j4 l
用途:在领取红包时,将金额发送给用户时调用。
' }0 G2 ^$ D* F& K# g% X7 B% x0 l2. recv_internal 函数 这是合约的核心函数,处理所有内部消息,实现创建和领取红包的逻辑。 2 e8 l+ Q j# ?) W/ ^7 S
功能:根据消息内容执行红包的创建或领取操作。 参数: - my_balance:合约当前余额。
- msg_value:消息附带的 TON 金额。
- in_msg_full:完整消息单元。
- in_msg_body:消息体(操作指令)。
6 D" Y; D, O1 b9 B
( I$ _$ V" M& \( P实现: - 余额检查:如果合约余额小于等于 0,抛出错误(代码 100)。
- 解析消息:提取发送者地址并计算其哈希值,用于记录领取状态。
- 加载存储:从合约持久化存储中读取红包字典(red_packets),如果为空则初始化一个新字典。
- 处理操作:* h( X5 {0 [8 ]8 v9 D: M! V3 Y3 y
创建红包(op == 1): - 检查消息附带金额是否大于 0。
- 从消息体读取每份领取金额(claim_amount),并验证其有效性(大于 0 且不超过总金额)。
- 创建红包数据(总金额、每份金额、空领取记录),存储到字典中。
- 更新合约存储。. j+ } j3 g/ g% `
领取红包(op == 2): - 根据红包 ID 从字典中查找红包数据,若不存在则抛出错误(代码 104)。
- 解析红包数据:总余额、每份金额、已领取记录。
- 检查发送者是否已领取(若已领取则抛出错误 101)。
- 检查余额是否足够(不足则抛出错误 102)。
- 更新红包状态:扣除领取金额,记录发送者为已领取。
- 保存更新后的红包数据。
- 调用 prepare_internal_msg 发送领取金额给用户。
5 U6 F! T$ N# l; R& m" A: J
错误处理:如果消息体为空或操作无效,抛出错误(代码 105)。
2 v+ c% C8 w. ~6 a3. get_red_packet_data 函数 这是一个外部查询方法,用于查看某个红包的状态。 功能:返回指定红包的总余额、每份金额和领取记录。 参数: - red_packet_id:红包唯一 ID。+ P/ V, a1 k0 l, s2 P& x
返回值: - [stored_balance, claim_amount, claimed]:分别为红包剩余金额、每份领取金额和已领取记录字典。
( @" r- i- M: B; r1 z+ H* F( f
实现: - 从合约存储中读取红包字典。
- 根据红包 ID 查找对应数据,若不存在则返回默认值 [0, 0, new_dict()]。
- 解析红包数据并返回。- \3 z5 F- c6 ?% }* R
; z+ i& m, I$ U8 j
这段代码实现了一个支持多红包的智能合约:
0 t F5 H: o& q- 用户可以用操作码 1 创建一个新红包,指定每份金额。
- 用户可以用操作码 2 领取指定红包,每次领取固定金额。
- 合约通过字典存储多个红包的状态,并防止重复领取。
- 提供外部查询方法查看红包详情。3 X8 J4 M* a: P; ~# _/ T) y
8 A# s0 _2 L, j2 u% j) f
$ h9 j* k+ U: k7 q7 H6 x步骤 3:编译合约编写完成后,我运行以下命令编译代码:! L8 C8 ?) L& h# x- u
终端显示“Compilation successful”,证明代码语法正确,生成了字节码和 ABI 文件。以下是我编译成功的截图:
# y2 x+ g7 T- x' o) e( |6 l1 L$ U; a) V: V$ Q
下一步计划
& }) h: ? v& x/ U/ x( ]9 }目前,我已经完成了合约的编写和编译,接下来我计划: ' T8 I- {8 ^! G# @. X
* ~+ w' E$ t3 N; }5 X! y- 测试:用 Blueprint 的 Sandbox 在本地测试合约逻辑。
- 部署:将合约部署到 TON 测试网,验证实际效果。 这些内容会在后续教程中分享,敬请期待!: C; G0 q# A2 h/ _! O
9 N1 ~# p+ I; }总结
d* o/ Q) S1 X9 O3 S7 C0 {这篇教程展示了如何用 FunC 和 Blueprint 开始一个 TON 红包合约项目。到目前为止,我成功搭建了项目、编写了合约并编译通过。完整代码和文件夹结构都已分享,你可以跟着试试。如果你也想尝试 TON 开发,不妨从这个简单项目入手。有什么问题,欢迎留言交流!
_: o5 o# i1 `
8 N0 b. i5 U' q$ _- U9 W |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|