|
本帖最后由 riyad 于 2025-3-20 00:45 编辑
0 L0 P2 B% {, T) b, I5 L8 N" J3 U; w+ C; P8 q, |
今天,我将带大家一起探索如何在 TON 区块链上用 FunC 语言和 Blueprint 工具开发一个红包智能合约。这篇教程是第一部分,重点介绍项目搭建、合约编写和成功编译的过程。我会分享完整的代码和项目结构,适合对 TON 开发感兴趣的新手。让我们开始吧! J3 Y$ E8 ^! o, A
什么是 TON 和红包合约? TON(The Open Network)是一个高效的区块链平台,以低成本和高吞吐量著称,非常适合开发去中心化应用。红包是中国文化中常见的分享方式,我想通过智能合约实现一个简单功能:用户存入 TON,指定份数,其他人可以领取。这篇文章将展示我到目前为止的进展——编写并成功编译合约。
# M3 t5 e9 u! @6 N6 Z准备工作 在动手之前,确保你的环境准备好: 安装 Node.js:Blueprint 依赖 Node.js,建议使用最新 LTS 版本,从 nodejs.org 下载安装。 安装 Blueprint:在终端运行以下命令全局安装: 代码编辑器:我用的是 VS Code,安装 TON FunC 插件可以高亮语法,提升编码体验。 步骤 1:创建项目 我们用 Blueprint 初始化一个 TON 项目。打开终端,输入: 项目名称:我输入了 redpacket。 模板:选择 func-empty,因为我想从头写合约。 创建完成后,进入项目目录: 项目文件夹结构 Blueprint 生成的项目结构非常清晰,以下是 redpacket 文件夹的内容: 6 P0 q6 W, M) y* c P( |8 _
; E4 m1 o9 p h( j2 n9 k7 e, r+ M
步骤 2:编写红包合约代码 在 contracts/redpacket.fc 中,我编写了红包智能合约。以下是代码和详细注释: - #include "imports/stdlib.fc";
! S) ~+ o1 b2 X6 B( C/ D: {
* k7 @4 @4 N- D8 M" v# G- // 准备内部消息的函数
% V9 {+ K3 _0 |8 T - (cell) prepare_internal_msg(slice recipient, int amount, int bounce) {5 }- g2 [: e, I2 n
- return begin_cell()
) G3 q0 ~; `/ ?* k/ X0 `1 K - .store_uint(0, 4) ;; 内部消息标记 (0x0)4 L6 K% j5 B J, g% ]1 R ^9 x; y! J
- .store_slice(recipient) ;; 目标地址- w4 ^3 t- s! P
- .store_coins(amount) ;; 发送金额
. c3 o& i4 @. ? - .store_uint(bounce, 1) ;; 是否可退回标志
# _) K) U* J4 L, K4 r& p- q1 { - .store_uint(0, 1 + 4 + 4) ;; ihr_disabled: 0, 无源地址和目标地址扩展. b! U4 G \ F/ [0 U1 ]
- .store_uint(0, 64) ;; ihr_fee
: Z1 S: S5 a5 I; _4 E. w" ?# _ - .store_uint(0, 64) ;; fwd_fee3 e6 h6 u. Z- U: N3 w
- .store_uint(0, 64) ;; 创建逻辑时间
# j& O% N9 I7 J2 ^. _% ~; p. s - .store_uint(0, 64) ;; 创建时间戳
- p9 u& u9 w- c6 ^$ g - .store_uint(0, 1) ;; 无状态初始化8 k9 R& r& q5 S7 I1 J
- .store_uint(0, 1) ;; 消息体为空/ @( M. s3 W! d7 O
- .end_cell();6 V5 G( P5 G3 Z P
- }; ~7 Y( X% y8 B3 u! [9 P. U
- 9 q" Z& z `# C1 e+ x* k' O" ~
- // 处理内部消息的主函数, e) u; T7 P7 A4 |& C6 M/ C
- () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {3 d. K6 W/ I; |4 C% ^; s
- ;; 检查合约是否有余额可用8 z& A/ x. G& | l+ E
- if (my_balance <= 0) { throw(100); }7 d3 U3 t' `2 n% ^7 @) B! P* \. t: f
- 4 n& t1 N8 h8 o* `: _* r
- ;; 解析传入消息
: f5 l% x# I% G - var parser = in_msg_full.begin_parse();
: E0 ~/ F% j5 [/ u+ C- v$ I( @3 f - int flags = parser~load_uint(4);* g' N r2 h) q, D6 E$ V
- slice sender_address = parser~load_msg_addr();* V* f. c1 S/ }
- int sender_hash = sender_address.slice_hash();0 j* F( W R Y
6 h! X$ R. G. N% X- ;; 加载持久化存储(红包字典). y' `+ `7 i! [& Z- _8 L% k
- var data = get_data();1 c- @ q$ S1 ?1 i9 T
- cell red_packets = new_dict();* c/ Q6 M2 n' s+ ?
- if (~ slice_empty?(data.begin_parse())) {
q5 c! t" f/ V5 p5 ? - var data_slice = data.begin_parse();
+ \* F# ?* R: ? - red_packets = data_slice~load_dict();
6 k& p. }/ H# h - }- B& Q$ F! Q5 _3 a1 T
- 0 e1 D4 o$ |7 N1 ]- }
- ;; 检查消息体是否有操作(创建或领取)
: b) h7 R! j/ m; e5 T% s5 b - if (~ slice_empty?(in_msg_body)) {
' L$ k# u/ B7 }3 I4 ?6 K _. M - var body_parser = in_msg_body;# I* A. |& u4 \9 Q0 K3 H2 _) ?8 j
- int op = body_parser~load_uint(8); ;; 操作码:1 = 创建,2 = 领取$ S# y1 d1 E2 m- Y* C: R7 d
- int red_packet_id = body_parser~load_uint(256); ;; 红包唯一ID
$ I. x3 F* i; F' k - ; U; K8 d0 S5 Y1 i4 e, ?- L
- if ((op == 1) & (msg_value > 0)) { ;; 创建新红包
; y1 G) f& I3 ?. [3 l& B, X5 v2 S - int claim_amount = body_parser~load_int(256); ;; 发送者指定每份领取金额* h U Z1 ^+ C/ y; t" n. v
- if ((claim_amount <= 0) | (claim_amount > msg_value)) { throw(103); } ;; 无效金额检查- F6 }/ Y" s4 e8 I- |! u: ?+ s
- & V1 J* ~; E! b
- var packet_data = begin_cell()
6 |: [* i% w" A0 z/ e4 e - .store_int(msg_value, 256) ;; 红包总金额" j( `6 K9 G6 N5 ~2 e6 l
- .store_int(claim_amount, 256) ;; 每份领取金额
! {6 d5 o- ~- c# K4 m& p9 u - .store_dict(new_dict()) ;; 空领取记录字典
0 \ ^; z# x, n" h( ? - .end_cell();( ?7 e, [& [" r+ a: _% n) T+ j! r
- ) ~; A) H" \4 f7 w- V; b
- red_packets = udict_set_ref(red_packets, 256, red_packet_id, packet_data);
4 I. W4 R7 L" F6 V8 L; }0 U- J - set_data(begin_cell().store_dict(red_packets).end_cell());
& B: X/ `: o: T" A9 g - return ();
" x ]" v- D G" l - } else {
7 [- O9 S/ m: o* [( d% d - if (op == 2) { ;; 领取红包- a( K' @) F- l) Q% B
- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);
9 f: _; _; M5 w: J6 ^% H - if (exists == 0) { throw(104); } ;; 红包不存在
+ _6 O+ ]7 ~5 h3 ]" e' J - " P; y# |. w, I. q; e/ ~( [
- var packet_parser = packet_cell.begin_parse();
3 `' o, r) B" D/ _4 ]5 } - int stored_balance = packet_parser~load_int(256);
8 a( q; _) z- L5 W - int claim_amount = packet_parser~load_int(256);
$ R& q) J' S# v% d9 J2 Z7 i' i - cell claimed = packet_parser~load_dict();( r/ C- e% ~6 g
- 2 V7 N3 g0 g% v+ z1 d% r
- ;; 检查发送者是否已领取* Y" y- D$ t* B0 B) b1 b
- var (claimed_value, claimed_exists) = udict_get_ref?(claimed, 256, sender_hash);3 j5 o6 D$ I2 @ z1 s3 f
- if (claimed_exists == 1) { throw(101); } ;; 已领取过
* @0 t. u0 `/ o8 \
. H" u) G+ C! Y- ;; 检查余额是否足够
/ k0 b6 q4 b4 u' s* p: A5 n, T. i - if (stored_balance < claim_amount) { throw(102); } ;; 余额不足
0 J- \2 \+ ^' Y G
8 y; c3 @8 x6 ~5 _$ l+ }- ;; 更新红包状态
/ h/ Q! W; \/ ` - stored_balance = stored_balance - claim_amount;
2 ~( Y. {# X6 ^2 }8 R7 K% x - var value_cell = begin_cell().store_int(1, 8).end_cell();: L- h K: B- u: N: m! I& L
- claimed = udict_set_ref(claimed, 256, sender_hash, value_cell);
7 R: f" f0 _" W& @' y - 5 @; u* S# H) {/ ^
- ;; 保存更新后的红包数据2 T. k% c% w" S+ l
- red_packets = udict_set_ref(red_packets, 256, red_packet_id,
; ^/ w$ ~8 J+ x0 [5 k; U - begin_cell()
7 m. j' y4 I' P1 z* d3 _8 w - .store_int(stored_balance, 256)
- i- p$ ~$ w9 m- t" ]: c2 g6 J - .store_int(claim_amount, 256)
- }. u) o0 w7 m4 X - .store_dict(claimed)2 `: V; I; O/ q: u! T C
- .end_cell());( M; Q+ o: a' c' N9 z0 Q( A4 T
- 2 y* J$ |# R% I
- set_data(begin_cell().store_dict(red_packets).end_cell());7 |6 ~2 \) G8 C0 o4 _+ u: S" i
6 y- a. `, V# e6 f& E; k- ;; 将领取金额发送给用户
/ n# V" ^* Z9 p* ~7 @$ g6 } - send_raw_message(
! u0 p3 o9 g" R - prepare_internal_msg(sender_address, claim_amount, 1),
& A4 D% Q5 v. r& m u) u: H. l* ] - 64 ;; 单独支付 gas 费用, N/ N1 j' ] }3 f- U
- );
: ]# }; p* z$ [6 x - return ();- @' e: j9 L0 P4 A3 i- I8 h
- }9 u5 m- B4 _! a
- } _. i- c2 `2 p# z& \! q1 |
- }/ _0 G' \; M p
- throw(105); ;; 无效消息(未指定操作)3 k+ U5 ^ A/ i! E/ W
- }+ R0 f# M" ^" O8 o; u
- 2 |& ]) Q; q' }1 D+ V
- // 查询红包状态的外部方法1 `+ p$ ~: O1 x0 q: Y! Q
- ([int, int, cell]) get_red_packet_data(int red_packet_id) method_id {
, i+ z! X$ w) N7 C3 r - var data = get_data(); Q" y3 K$ o& _& m% ~0 V" Q
- cell red_packets = new_dict();
! R6 ?; Z- @$ l: i3 A6 Y7 U1 T - if (~ slice_empty?(data.begin_parse())) {
" x* Z& p3 o2 f2 w) O' k - var data_slice = data.begin_parse();4 M& M5 ~8 I" y% H
- red_packets = data_slice~load_dict();9 B, j- C1 M* {# Y3 s* k
- }
Z3 l0 v8 k: ~7 w( h6 n2 P5 S - . d7 A2 n. E% k! R
- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);
6 O$ J+ u2 w) `+ h - if (exists == 0) {6 L* j; w5 l- j H1 @; h8 ?2 Y
- return [0, 0, new_dict()];( @! w u# ~! K$ ~" q0 j, j4 h
- }: c3 {9 X1 b y! P% Y
, U9 X) y, s w/ |* a/ O- var packet_parser = packet_cell.begin_parse();
4 s6 S8 V, m; Y - int stored_balance = packet_parser~load_int(256);( w$ b, W9 ~& v7 z
- int claim_amount = packet_parser~load_int(256);
8 Y* w3 ?1 i2 _/ P8 V% M1 n6 ] - cell claimed = packet_parser~load_dict();- ~ w& f8 B. Q5 F9 _. K+ @3 c
- return [stored_balance, claim_amount, claimed];/ x9 G2 @2 {! N
- }
复制代码代码说明 1. prepare_internal_msg 函数 这个函数用于构造一个内部消息,用于将 TON 转账给领取者。
# ~" g! R+ Y9 _功能:生成一个标准的 TON 内部消息单元(cell),包含目标地址、金额和是否可退回等信息。 参数: - recipient:目标地址(slice 类型)。
- amount:转账金额(单位:nanoTON)。
- bounce:是否可退回(1 表示可退回,0 表示不可退回)。3 M$ r& w) _, N
实现: - 使用 begin_cell() 创建一个新的单元。
- 按 TON 消息格式依次存储:消息标记(0x0)、目标地址、金额、可退回标志等。
- 其他字段(如手续费、时间戳等)设为默认值 0。$ ^7 q' Q. P5 |/ w
用途:在领取红包时,将金额发送给用户时调用。
0 H5 ~7 D" i1 h3 N2. recv_internal 函数 这是合约的核心函数,处理所有内部消息,实现创建和领取红包的逻辑。 8 m2 u0 Y7 y- l, r- o
功能:根据消息内容执行红包的创建或领取操作。 参数: - my_balance:合约当前余额。
- msg_value:消息附带的 TON 金额。
- in_msg_full:完整消息单元。
- in_msg_body:消息体(操作指令)。
Q8 o+ N' v: |
1 m9 w# j/ [9 d实现: - 余额检查:如果合约余额小于等于 0,抛出错误(代码 100)。
- 解析消息:提取发送者地址并计算其哈希值,用于记录领取状态。
- 加载存储:从合约持久化存储中读取红包字典(red_packets),如果为空则初始化一个新字典。
- 处理操作:
% @9 I8 E$ i3 P j; I
创建红包(op == 1): - 检查消息附带金额是否大于 0。
- 从消息体读取每份领取金额(claim_amount),并验证其有效性(大于 0 且不超过总金额)。
- 创建红包数据(总金额、每份金额、空领取记录),存储到字典中。
- 更新合约存储。# @% ?6 P, K: c* \5 [ Z' X: D
领取红包(op == 2): - 根据红包 ID 从字典中查找红包数据,若不存在则抛出错误(代码 104)。
- 解析红包数据:总余额、每份金额、已领取记录。
- 检查发送者是否已领取(若已领取则抛出错误 101)。
- 检查余额是否足够(不足则抛出错误 102)。
- 更新红包状态:扣除领取金额,记录发送者为已领取。
- 保存更新后的红包数据。
- 调用 prepare_internal_msg 发送领取金额给用户。
+ E( u( V k9 N+ i7 _
错误处理:如果消息体为空或操作无效,抛出错误(代码 105)。
, i1 C# _9 Z0 D4 @% G% F" j( H3. get_red_packet_data 函数 这是一个外部查询方法,用于查看某个红包的状态。 功能:返回指定红包的总余额、每份金额和领取记录。 参数: - red_packet_id:红包唯一 ID。
3 ], A; m4 j1 o$ e# @5 L [+ A
返回值: - [stored_balance, claim_amount, claimed]:分别为红包剩余金额、每份领取金额和已领取记录字典。 h9 r- @; z# T8 F) }5 w
实现: - 从合约存储中读取红包字典。
- 根据红包 ID 查找对应数据,若不存在则返回默认值 [0, 0, new_dict()]。
- 解析红包数据并返回。+ x& w T: ^" H6 k6 j1 i
' t9 l9 @% c& K8 x! n, b$ f这段代码实现了一个支持多红包的智能合约: + L, W" i7 u7 q/ w. S" l& B
- 用户可以用操作码 1 创建一个新红包,指定每份金额。
- 用户可以用操作码 2 领取指定红包,每次领取固定金额。
- 合约通过字典存储多个红包的状态,并防止重复领取。
- 提供外部查询方法查看红包详情。' E- E6 I# ]$ {5 Y( c8 O8 F; ^& s
; N3 l! e' G) v1 o. c
5 T( L0 P/ s" E. d: c步骤 3:编译合约编写完成后,我运行以下命令编译代码:1 r* A9 Q0 M* E3 X/ Z, o! b
终端显示“Compilation successful”,证明代码语法正确,生成了字节码和 ABI 文件。以下是我编译成功的截图:
! f" }; V4 _! r. F; j/ [% B" F U3 a+ g% Z) Z. H
下一步计划% g& I' s1 |! e
目前,我已经完成了合约的编写和编译,接下来我计划:
' Q9 E. Q) J( Z2 ]; E4 \* k! \; W- A9 q4 w
- 测试:用 Blueprint 的 Sandbox 在本地测试合约逻辑。
- 部署:将合约部署到 TON 测试网,验证实际效果。 这些内容会在后续教程中分享,敬请期待!
. y' F' a0 r4 i1 o7 a, O; [ , [8 f M, C+ |8 P+ u* E! T* \/ o/ h
总结% s/ `- x! d! o; X
这篇教程展示了如何用 FunC 和 Blueprint 开始一个 TON 红包合约项目。到目前为止,我成功搭建了项目、编写了合约并编译通过。完整代码和文件夹结构都已分享,你可以跟着试试。如果你也想尝试 TON 开发,不妨从这个简单项目入手。有什么问题,欢迎留言交流! " o9 E! N) [ j. N* [
) G1 S$ s0 Y% I- S% x. t' L" ^
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|