English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french

简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE คนไทย Français русский

回答

收藏

如何在 TON 区块链上用 FunC 和 Blueprint 打造红包智能合约(第一部分)

开源社区 开源社区 5749 人阅读 | 0 人回复 | 2025-03-17

本帖最后由 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:在终端运行以下命令全局安装:
  1. npm i @ton/blueprint
复制代码
代码编辑器:我用的是 VS Code,安装 TON FunC 插件可以高亮语法,提升编码体验。
步骤 1:创建项目
我们用 Blueprint 初始化一个 TON 项目。打开终端,输入:
  1. npm create ton@latest
复制代码
项目名称:我输入了 redpacket。
模板:选择 func-empty,因为我想从头写合约。
创建完成后,进入项目目录:
  1. cd redpacket
复制代码
项目文件夹结构
Blueprint 生成的项目结构非常清晰,以下是 redpacket 文件夹的内容:
  7 Q& c) U% b+ q
         
) u7 v( N) s. |6 J' E
步骤 2:编写红包合约代码
在 contracts/redpacket.fc 中,我编写了红包智能合约。以下是代码和详细注释:
  1. #include "imports/stdlib.fc";7 X8 B, `; o  x5 n7 Y0 u7 _9 J

  2. * v* b" ~3 J5 _
  3. // 准备内部消息的函数. X1 @2 V3 k( |* i6 c
  4. (cell) prepare_internal_msg(slice recipient, int amount, int bounce) {2 q0 a: r$ W4 L3 o
  5.     return begin_cell()2 {/ ~! K( K- w1 Z
  6.         .store_uint(0, 4)  ;; 内部消息标记 (0x0)
    : E* o1 |7 q, {, K, b7 c+ ^
  7.         .store_slice(recipient)  ;; 目标地址
    ' |% q! m! i% P6 o7 W! C8 `$ b
  8.         .store_coins(amount)  ;; 发送金额$ w7 a, _, B! B) H) ]
  9.         .store_uint(bounce, 1)  ;; 是否可退回标志
    ! l* ?4 ]0 s0 n/ G! k! f1 A2 B7 q
  10.         .store_uint(0, 1 + 4 + 4)  ;; ihr_disabled: 0, 无源地址和目标地址扩展, Q# L6 ?0 c( i7 y
  11.         .store_uint(0, 64)  ;; ihr_fee
    # U% v) h, _3 I% U8 ^
  12.         .store_uint(0, 64)  ;; fwd_fee8 D. n, o/ E& [$ ]! u
  13.         .store_uint(0, 64)  ;; 创建逻辑时间, _/ i! d$ ]8 q* P0 T2 |5 f
  14.         .store_uint(0, 64)  ;; 创建时间戳
    - [- l& W$ o# J, J
  15.         .store_uint(0, 1)  ;; 无状态初始化
    & j3 a3 S3 j( Q: y
  16.         .store_uint(0, 1)  ;; 消息体为空  T4 T- I& |2 e- I0 E/ H
  17.     .end_cell();1 j) o* {: |. h4 Z0 t: X
  18. }2 |1 f* e% w7 c/ R$ A3 z: v$ t

  19. 2 l: k6 u& d3 Z. I% w  P  A
  20. // 处理内部消息的主函数
    * C: @6 ?+ m1 J1 q) i- U( m
  21. () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    6 z. ]* v& w  A8 Y
  22.     ;; 检查合约是否有余额可用8 t( O+ w) X4 [+ f$ z: Y* \( ?9 U2 S
  23.     if (my_balance <= 0) { throw(100); }
    & F1 y! B& J# ]( I
  24. ) K% ^/ f" T6 F( f4 u' O3 {- \& d
  25.     ;; 解析传入消息
    & m; a! B0 D9 A3 _/ Z
  26.     var parser = in_msg_full.begin_parse();" o6 {- I5 F( m/ P% m, ?0 s: H+ y% V; Z
  27.     int flags = parser~load_uint(4);& P- J+ m; ^. s, y4 a9 P  P. h
  28.     slice sender_address = parser~load_msg_addr();
    ! t8 `' ^; g7 V' b! U; q' _
  29.     int sender_hash = sender_address.slice_hash();: h' v; I4 O; L, N* V) f
  30. - d- P+ I+ y/ ?, s
  31.     ;; 加载持久化存储(红包字典)' x1 x8 `8 F0 j+ n- H$ b: f
  32.     var data = get_data();
    1 J! }  H; o* a% y# x# h
  33.     cell red_packets = new_dict();
    % ]! F  ~9 k; @4 W- m+ V& R
  34.     if (~ slice_empty?(data.begin_parse())) {
    & f. ~) r' l$ h; z& e
  35.         var data_slice = data.begin_parse();8 W4 y5 s2 ~0 x6 k( n
  36.         red_packets = data_slice~load_dict();( v3 R# m: R3 P( l0 K
  37.     }- i9 W; d" Y6 m0 i, N

  38. 1 V. p% n$ \1 h( s9 D3 N3 O
  39.     ;; 检查消息体是否有操作(创建或领取)
    . j# m/ _- {. f# O; A: u6 `9 M
  40.     if (~ slice_empty?(in_msg_body)) {
    ) f9 U" d) `( A+ j% G* N
  41.         var body_parser = in_msg_body;2 F- q# f0 r' u0 @, }* l- |% T* |+ S
  42.         int op = body_parser~load_uint(8);  ;; 操作码:1 = 创建,2 = 领取
    ; M" _$ d7 H8 D5 y( \4 U8 X
  43.         int red_packet_id = body_parser~load_uint(256);  ;; 红包唯一ID$ `! x3 o( S$ V) m2 O# v
  44. : y' H/ Y0 R' m8 |( Q
  45.         if ((op == 1) & (msg_value > 0)) {  ;; 创建新红包
    0 T6 S% o. X3 u3 @1 K
  46.             int claim_amount = body_parser~load_int(256);  ;; 发送者指定每份领取金额/ \- N9 }  e& T: d
  47.             if ((claim_amount <= 0) | (claim_amount > msg_value)) { throw(103); }  ;; 无效金额检查
    ) c/ x$ j( M% E9 P% {2 K

  48.   F$ e9 Q4 N+ {+ J, }
  49.             var packet_data = begin_cell()
    : d: M2 h1 y# v
  50.                 .store_int(msg_value, 256)  ;; 红包总金额, C% w; i2 ]4 x! S) ~6 L4 g
  51.                 .store_int(claim_amount, 256)  ;; 每份领取金额
    . P8 w3 G9 `" J9 \  v
  52.                 .store_dict(new_dict())  ;; 空领取记录字典
    / P1 R# o; z& e* C/ R) t: X5 P
  53.             .end_cell();' F7 {$ Y5 h) B

  54. , d% B$ p0 b8 T( G' r
  55.             red_packets = udict_set_ref(red_packets, 256, red_packet_id, packet_data);
    2 Z7 f$ l0 W/ T* e3 M4 f+ Q
  56.             set_data(begin_cell().store_dict(red_packets).end_cell());
    ; U& a8 q: b1 W! _* p: m  G  G/ @5 v
  57.             return ();
    ( G2 j! _  ^$ [" M& _+ @& E
  58.         } else {" N! M) Y7 k  S, `/ ]
  59.             if (op == 2) {  ;; 领取红包4 M; k) {: N! T  r
  60.                 var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);( S! o- Q# O4 J( _$ {1 g
  61.                 if (exists == 0) { throw(104); }  ;; 红包不存在3 M4 W4 k1 E5 |0 l( F3 Q1 ?
  62. - ^( v/ _  |2 L: w/ D
  63.                 var packet_parser = packet_cell.begin_parse();
    ; h& ?. A7 Y4 H* ]1 v
  64.                 int stored_balance = packet_parser~load_int(256);
    2 f$ {7 f, [. M6 Z; P: L/ y6 h
  65.                 int claim_amount = packet_parser~load_int(256);
    ! n6 \7 S9 r+ R3 {. ?  J
  66.                 cell claimed = packet_parser~load_dict();: |) H# B3 i4 Z; S
  67. : \0 Z( I6 k9 ]0 C( Q' a% k8 n* t
  68.                 ;; 检查发送者是否已领取7 ?4 H0 l0 b5 m6 R4 S
  69.                 var (claimed_value, claimed_exists) = udict_get_ref?(claimed, 256, sender_hash);- R/ N. U% d0 w& P
  70.                 if (claimed_exists == 1) { throw(101); }  ;; 已领取过
    ; N. k2 [4 ^: H0 J

  71. ; C* F; U, c% p9 W) T$ f( k
  72.                 ;; 检查余额是否足够
    - F6 P' @! z7 {, V* y
  73.                 if (stored_balance < claim_amount) { throw(102); }  ;; 余额不足
    ( K7 G9 A0 Z9 i$ h" C) C( t! }
  74. 5 A0 ]% S" m7 Y; z& `( _3 ^7 @
  75.                 ;; 更新红包状态
    . P3 K$ i: O" Y: f
  76.                 stored_balance = stored_balance - claim_amount;5 B( u1 v& x2 v) b
  77.                 var value_cell = begin_cell().store_int(1, 8).end_cell();& C3 ]8 G' t5 T* [
  78.                 claimed = udict_set_ref(claimed, 256, sender_hash, value_cell);% [1 v; e: o* y
  79. , v4 V! J# ?( O
  80.                 ;; 保存更新后的红包数据6 E2 W" O! C0 o2 L
  81.                 red_packets = udict_set_ref(red_packets, 256, red_packet_id, 7 |/ r6 c, Z) k+ ?$ J
  82.                     begin_cell()
    3 ^6 j% u8 f* k
  83.                         .store_int(stored_balance, 256)+ P( e1 `* U/ ~, R4 H1 A: T
  84.                         .store_int(claim_amount, 256)
    : k) P% v$ ]- Q( \  }5 U4 w' U
  85.                         .store_dict(claimed)6 o% I$ o+ N$ R
  86.                     .end_cell());) M! Q" U0 H9 r6 x- M! |/ p

  87. " z; ~6 I& P% f: }, K
  88.                 set_data(begin_cell().store_dict(red_packets).end_cell());8 m1 M  N0 S1 w% g( F6 ]

  89. ' W+ ~& B; o3 d( {' n
  90.                 ;; 将领取金额发送给用户* G8 D, p- V; _9 Y0 B1 H1 [8 I
  91.                 send_raw_message(
    / _8 x# a; ~# d& ]: n  D$ ^
  92.                     prepare_internal_msg(sender_address, claim_amount, 1),
    $ ~, R8 r" y( W+ v
  93.                     64  ;; 单独支付 gas 费用) k# f: i: R  ^! T  t) c* }( g
  94.                 );
    ; z, ~! ~8 Q4 |% S* Y6 ]4 z, r9 A  }
  95.                 return ();
    9 Z: H6 \2 s( a6 m4 O/ x# R$ x
  96.             }
    0 k  B: b9 y3 {! P  W$ F
  97.         }2 H, y0 w, K) D. W, s* j& |* ~' q
  98.     }
    1 X& x% w/ ?$ E
  99.     throw(105);  ;; 无效消息(未指定操作)
    ( P* C& Q3 {9 P5 ~- \9 @
  100. }$ \$ s. V9 v2 n

  101. ( S" J* f8 t: a
  102. // 查询红包状态的外部方法: H4 W4 D' g/ s" Y6 g9 y* P% c
  103. ([int, int, cell]) get_red_packet_data(int red_packet_id) method_id {
    + X, @1 ]: I$ Z  l
  104.     var data = get_data();
    ' X5 e9 i' d, z& ^% b3 ~
  105.     cell red_packets = new_dict();5 ]6 J; J* k' V0 h* m1 G% W
  106.     if (~ slice_empty?(data.begin_parse())) {% D  Q0 |9 J  ?* L  f
  107.         var data_slice = data.begin_parse();
    ( ?! G+ Q. H+ V( q; d
  108.         red_packets = data_slice~load_dict();' C" R* p& M/ x) u
  109.     }/ L/ [, V$ p9 a1 ~# `
  110. , r8 i9 g6 J% [. g/ \
  111.     var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);- g7 P* z4 p" @# y8 @* j
  112.     if (exists == 0) {- l* _) Q% t5 `' Z0 q. o
  113.         return [0, 0, new_dict()];
    * b6 K. V% K1 `& o' O: b2 d. g- K
  114.     }' O# R" x# I( }9 G4 a! @9 W) U

  115. " t. R: I8 c( m% }* G+ H
  116.     var packet_parser = packet_cell.begin_parse();
    , w! |* I( H, N  J
  117.     int stored_balance = packet_parser~load_int(256);
    4 d: x- Q5 B& j% [3 L
  118.     int claim_amount = packet_parser~load_int(256);5 \1 L1 e0 t. k- G" v  V, E
  119.     cell claimed = packet_parser~load_dict();$ k" \2 t( E4 K# Y8 t
  120.     return [stored_balance, claim_amount, claimed];
    7 E# Z  {( _% ?/ a/ Q
  121. }
复制代码
代码说明
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 l
2. 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 a
3. 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
  1. npx blueprint build
复制代码
终端显示“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
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则