Move学习: 基础part3

AI-摘要
sonia33 GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Move学习: 基础part3
SoniaChen盲盒NFT程序
本文将介绍如何使用Move语言编写一个盲盒NFT程序。该程序允许用户支付SUI资产,并随机生成具有不同稀有度的盲盒NFT。
1. 必要结构和对象
在实现盲盒NFT程序之前,我们需要定义以下核心数据结构:
- 盲盒Collection:管理NFT的铸造规则、统计信息和资金。
- NFT:代表生成的盲盒NFT对象。
- 管理员权限:用于管理Collection的权限对象。
- 事件:记录购买和铸造NFT的事件。
以下是这些结构的Move代码定义:
// === 数据结构 ===
public struct Collection has key {
id: UID,
name: String,
price: u64,
total_supply: u64,
minted: u64,
balance: Balance<SUI>,
}
public struct AdminCap has key, store {
id:UID,
}
public struct NFT has key, store {
id: UID,
name: String,
rarity: String, // TODO enum Common/Rare/Legendary
image_url: String,
}
// 事件
public struct BoxPurchased has copy, drop {
buyer: address,
nft_id: ID,
rarity: String,
}
2. 管理员指令
管理员拥有特殊的权限来管理盲盒Collection。以下是两个主要的管理员函数:
2.1 创建Collection
create_collection 函数用于创建Collection对象,该对象作为Minting规则和统计的中央数据中心,并设置为共享状态以供所有用户访问。
// ==== admin functions
/// create collection
/// 创建了一个可供所有用户交互的“中央数据中心”,用来管理 Minting 规则、统计和资金
public fun create_collection(
_: &AdminCap,
name: String,
price: u64,
total_supply: u64,
ctx: &mut TxContext
) {
let collection = Collection {
id: object::new(ctx),
name,
price,
total_supply,
minted: 0,
balance: balance::zero(),
};
transfer::share_object(collection); // 原本是owned 现在设为共享;任何人都可以访问
}
2.2 提取资金
withdraw 函数允许管理员从Collection中提取所有累积的资金到自己的钱包。
/// withdraw
public fun withdraw(
_: &AdminCap,
collection: &mut Collection,
ctx: &mut TxContext
) {
let amount = balance::value(&collection.balance);
let coin = coin::take(&mut collection.balance, amount, ctx);
transfer::public_transfer(coin, tx_context::sender(ctx));
}
3. 主函数entry
buy_and_open 是用户购买和打开盲盒NFT的主要入口函数。该函数执行以下步骤:
- 校验供应量:确保Collection中仍有可铸造的NFT。
- 校验支付金额:确认用户支付的SUI足够购买NFT。
- 处理支付:将用户支付的SUI存入Collection的余额。
- 生成随机稀有度:使用随机数生成器确定NFT的稀有度(Common: 60%, Rare: 30%, Legendary: 10%)。
- 铸造NFT:创建新的NFT对象。
- 发送事件:记录购买事件。
- 更新统计:增加已铸造NFT的数量。
- 转移NFT:将NFT发送给购买者。
/// purchase_and_open
public entry fun buy_and_open(
collection: &mut Collection,
r: &Random,
payment: Coin<SUI>,
ctx: &mut TxContext,
) {
// check supply
assert!(collection.total_supply > collection.minted,EBoxSoldOut);
// check payment
let paid = coin::value(&payment);
assert!(paid >= collection.price, EInsufficientPayment);
// pay
balance::join(&mut collection.balance, coin::into_balance(payment));
// collection
let mut generator = random::new_generator(r, ctx);
let rarity_roll = random::generate_u8_in_range(&mut generator, 1, 100);
let (rarity, name) = if (rarity_roll <= 60) {
(b"Common".to_string(), b"Common Mystery NFT".to_string())
} else if (rarity_roll <= 90) {
(b"Rare".to_string(), b"Rare Mystery NFT".to_string())
} else {
(b"Legendary".to_string(), b"Legendary Mystery NFT".to_string())
};
// mint NFT
let nft = NFT {
id:object::new(ctx),
name,
rarity,
image_url: b"ipfs://placeholder".to_string(),
};
let nft_id = object::id(&nft);
// 发送事件
event::emit(BoxPurchased{
buyer: tx_context::sender(ctx),
nft_id,
rarity: rarity,
});
// 更新计数
collection.minted = collection.minted + 1;
// 转移NFT给买家
transfer::public_transfer(nft, tx_context::sender(ctx));
}
4. 涉及的Move操作和注意点
4.1 UID、对象与资产
在Move中,任何具有key能力的结构体,只要内部包含一个UID字段,就会被Sui视为一个链上对象。这些对象具有持久化和唯一性,是区块链资产的基础。
4.2 Transfer操作
sui::transfer::{}模块提供了针对不同所有权类型的存储操作:
- Transfer:将对象发送到某个地址,使其进入地址所有(address owned)状态。
- Freeze:将对象置为不可变(immutable),成为对外公开且永不改变的常量。
- Share:将对象置为共享(shared)状态,任何人都可访问/修改。
4.3 Entry函数参数顺序
在Move的entry函数中,参数顺序有特定的约定:
- “对象(Objects)” 总是排在 “简单数据(Primitives)” 前面;
- “运行时提供的特殊对象”(如
&Random)总是排在 “交易上下文” (&mut TxContext) 的前面;&mut TxContext永远是最后一位。
4.4 NFT铸造行为
与Rust的区别
传统Rust语言
在传统Rust中,创建结构体实例只是在内存中创建数据结构,没有持久化或所有权转移机制。
Sui Move语言
Sui Move是以对象为中心的设计哲学:
- 对象ID和持久化:带有
id: object::new(ctx)的结构体被视为链上对象,object::new(ctx)分配全局唯一ID。 - 所有权语义:创建对象后必须明确处理,否则编译失败。
在Move中,创建NFT对象的代码就是"铸造(Minting)"行为:
铸造定义:在区块链上首次创建具有唯一身份和持久化存储的新资产对象。
// mint NFT
let nft = NFT {
id: object::new(ctx), // 赋予唯一的链上ID
name,
rarity,
image_url: b"ipfs://placeholder".to_string(),
};
4.5 Object相关操作
object::new(ctx):在链上创建新对象(如AdminCap)。object::id(&object):获取对象的唯一ID。
4.6 Balance相关操作
Balance是模块内部管理总资金的容器:
balance::zero():创建空的Balance容器,用于初始化。balance::value(&balance):获取Balance中的余额。balance::join(&mut target, source):将source合并到target中,消耗source。这是收款的标准第二步。balance::withdraw(&mut balance, amount):从Balance中取出指定金额,返回新的Balance。
4.7 Coin相关操作
Coin是可交易的资产对象:
coin::value(&coin):获取Coin的面值。coin::take(&mut balance, amount, ctx):从Balance中取出Coin,需要TxContext铸造新Coin对象。coin::into_balance(coin):消耗Coin,返回其Balance。这是存入Collection余额的标准第一步。
其他Coin操作:
coin::split:分割Coin(例如找零)。coin::join:合并多个Coin。
4.8 Coin与Balance对比
| 操作 | 目标 | 输入需求 | 核心区别 |
|---|---|---|---|
coin::take |
提现给用户 | 需要&mut TxContext铸造新Coin对象 |
返回Coin(可交易对象) |
balance::withdraw |
内部拆分价值 | 不需要&mut TxContext |
返回Balance(不可交易容器) |
简单来说:
- 想让用户拿到钱去花? → 使用
coin::take - 想在内部账户间分钱? → 使用
balance::withdraw







