从本文开始,我们将以 Aptos 为基础,Step by Step 的阐述如何在 Move 上进行全链游戏的开发。
今天我们会从一个简单的 NFT 合约开始。麻雀虽小,五脏俱全,这个合约里面会涉及如下特性:
SignerCapability —— 账户操作权限 NFT Collection & NFT Token —— 对 NFT 合集与 NFT Token 的操作 NFT Properties —— 如何正确的管理 NFT 的属性 Fun, Entry Fun & View Fun —— 不同类型的函数
Hero 的完整代码如下:
module hero::hero {
use aptos_framework::account::{Self, SignerCapability};
use std::option;
use std::signer;
use std::string::{Self, String};
use aptos_framework::object::{Self, ConstructorRef, Object};
use aptos_token_objects::collection;
use aptos_token_objects::token;
use aptos_token_objects::property_map;
/// To generate resource account
const STATE_SEED: vector<u8> = b"hero_signer";
const COLLECTION_NAME: vector<u8> = b"HERO COLLECTION";
const COLLECTION_URI: vector<u8> = b"https://google.com";
/// error code
const ENOT_CREATOR: u64 = 1004;
/// Global state
struct State has key {
// the signer cap of the module's resource account
signer_cap: SignerCapability
}
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct Hero has key {
level: u64,
mutator_ref: token::MutatorRef,
property_mutator_ref: property_map::MutatorRef,
}
fun init_module(account: &signer) {
let (resource_account, signer_cap) = account::create_resource_account(account, STATE_SEED);
let collection = string::utf8(COLLECTION_NAME);
collection::create_unlimited_collection(
&resource_account,
string::utf8(COLLECTION_NAME),
collection,
option::none(),
string::utf8(COLLECTION_URI),
);
move_to(account, State {
signer_cap
});
// move_to(&resource_account, State {
// signer_cap
// });
}
fun create(
_creator: &signer,
description: String,
name: String,
uri: String,
): ConstructorRef acquires State {
let state = borrow_global_mut<State>(@hero);
let resource_account = account::create_signer_with_capability(&state.signer_cap);
token::create_named_token(
&resource_account,
string::utf8(COLLECTION_NAME),
description,
name,
option::none(),
uri,
)
}
// Creation methods
public fun create_hero(
creator: &signer,
name: String,
description: String,
uri: String,
level: u64,
): Object<Hero> acquires State {
// generate resource acct
let state = borrow_global_mut<State>(@hero);
let resource_account = account::create_signer_with_capability(&state.signer_cap);
let constructor_ref = create(&resource_account, description, name, uri);
let token_signer = object::generate_signer(&constructor_ref);
// <-- create properties
let property_mutator_ref = property_map::generate_mutator_ref(&constructor_ref);
let properties = property_map::prepare_input(vector[], vector[], vector[]);
property_map::init(&constructor_ref, properties);
property_map::add_typed<u64>(
&property_mutator_ref,
string::utf8(b"level"),
level,
);
// create properties -->
let hero = Hero {
level: level,
mutator_ref: token::generate_mutator_ref(&constructor_ref),
property_mutator_ref,
};
move_to(&token_signer, hero);
// move to creator
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
let creator_address = signer::address_of(creator);
object::transfer_with_ref(object::generate_linear_transfer_ref(&transfer_ref), creator_address);
object::address_to_object(signer::address_of(&token_signer))
}
// Entry functions
public entry fun mint_hero(
account: &signer,
name: String,
description: String,
uri: String,
) acquires State {
create_hero(account, name, description, uri, 0);
}
public entry fun update_hero_level(
account: &signer,
collection: String,
name: String,
level: u64
) acquires Hero, State {
// ger resource account
let state = borrow_global_mut<State>(@hero);
let resource_account = account::create_signer_with_capability(&state.signer_cap);
// get hero by creator
let (_hero_obj, hero) = get_hero(
&signer::address_of(&resource_account),
&collection,
&name
);
let account_address = signer::address_of(account);
// rule: only owner could modify this one.
// assert!(object::is_owner(hero_obj, account_address), ENOT_OWNER);
// rule 0x02: only cap owner could modify this one.
assert!(account_address == @hero, ENOT_CREATOR);
// Gets `property_mutator_ref` of hero.
let property_mutator_ref = &hero.property_mutator_ref;
// Updates the description property.
property_map::update_typed(property_mutator_ref, &string::utf8(b"level"), level);
}
// View functions
#[view]
public fun view_hero(creator: address, collection: String, name: String): Hero acquires Hero {
let token_address = token::create_token_address(
&creator,
&collection,
&name,
);
move_from<Hero>(token_address)
}
#[view]
public fun view_hero_by_object(hero_obj: Object<Hero>): Hero acquires Hero {
let token_address = object::object_address(&hero_obj);
move_from<Hero>(token_address)
}
inline fun get_hero(creator: &address, collection: &String, name: &String): (Object<Hero>, &Hero) {
let token_address = token::create_token_address(
creator,
collection,
name,
);
(object::address_to_object<Hero>(token_address), borrow_global<Hero>(token_address))
}
}
可以通过如下链接查看线上部署的合约:
https://explorer.aptoslabs.com/account/0x3fb7233a48d6f0a8c50e1d1861521790af0c5d7cfaf95ec81dc5bed4541becf1/modules/run/hero/mint_hero?network=testnet
以下是在浏览器中进行函数操作:
Mint 一个 Hero。
Mint 到的 Hero 可以在 Token 列表中看到,也可以在钱包中看到。
升级 hero,这个函数仅能被合约发布人调用。
hero 的等级从 0 级上升为 1 级。
0x01 init_module 函数
fun init_module(account: &signer) {
let (resource_account, signer_cap) = account::create_resource_account(account, STATE_SEED);
let collection = string::utf8(COLLECTION_NAME);
collection::create_unlimited_collection(
&resource_account,
string::utf8(COLLECTION_NAME),
collection,
option::none(),
string::utf8(COLLECTION_URI),
);
move_to(account, State {
signer_cap
});
// move_to(&resource_account, State {
// signer_cap
// });
}
init_module
是合约中的特殊函数,在部署的时候会被一次调用。
在本合约的init_module
中,执行了如下功能:
在发布账户下创建资源账户 resource_ccount
创建 collection
将资源的账户的签名权限转移到发布账户下
💡如果将现有的
move_to
替换为注释中的move_to
,会有什么改变?
0x02 mint_hero 函数
遵循函数尽量「原子化」的原则,我们将mint_hero
函数进行拆分:
mint_hero -> create_hero -> create
2.1 mint_hero
public entry fun mint_hero(
account: &signer,
name: String,
description: String,
uri: String,
) acquires State {
create_hero(account, name, description, uri, 0);
}
public entry
函数修饰符表示此函数可以被外部调用。
2.2 create_hero
// Creation methods
public fun create_hero(
creator: &signer,
name: String,
description: String,
uri: String,
level: u64,
): Object<Hero> acquires State {
// generate resource acct
let state = borrow_global_mut<State>(@hero);
let resource_account = account::create_signer_with_capability(&state.signer_cap);
let constructor_ref = create(&resource_account, description, name, uri);
let token_signer = object::generate_signer(&constructor_ref);
// <-- create properties
let property_mutator_ref = property_map::generate_mutator_ref(&constructor_ref);
let properties = property_map::prepare_input(vector[], vector[], vector[]);
property_map::init(&constructor_ref, properties);
property_map::add_typed<u64>(
&property_mutator_ref,
string::utf8(b"level"),
level,
);
// create properties -->
let hero = Hero {
level: level,
mutator_ref: token::generate_mutator_ref(&constructor_ref),
property_mutator_ref,
};
move_to(&token_signer, hero);
// move to creator
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
let creator_address = signer::address_of(creator);
object::transfer_with_ref(object::generate_linear_transfer_ref(&transfer_ref), creator_address);
object::address_to_object(signer::address_of(&token_signer))
}
create_hero
函数里面实现了标准的NFT
创建流程。
从发布者的账户中借出 signer_cap
,生成资源账户:
// generate resource acct
let state = borrow_global_mut<State>(@hero);
let resource_account = account::create_signer_with_capability(&state.signer_cap);
let constructor_ref = create(&resource_account, description, name, uri);
let token_signer = object::generate_signer(&constructor_ref);
初始化 properties_map
属性表
// <-- create properties
let property_mutator_ref = property_map::generate_mutator_ref(&constructor_ref);
let properties = property_map::prepare_input(vector[], vector[], vector[]);
property_map::init(&constructor_ref, properties);
property_map::add_typed<u64>(
&property_mutator_ref,
string::utf8(b"level"),
level,
);
// create properties -->
创建 hero_object 并转移给交易发起人 account
:
let hero = Hero {
level: level,
mutator_ref: token::generate_mutator_ref(&constructor_ref),
property_mutator_ref,
};
move_to(&token_signer, hero);
// move to creator
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
let creator_address = signer::address_of(creator);
object::transfer_with_ref(object::generate_linear_transfer_ref(&transfer_ref), creator_address);
object::address_to_object(signer::address_of(&token_signer))
2.3 create
fun create(
_creator: &signer,
description: String,
name: String,
uri: String,
): ConstructorRef acquires State {
let state = borrow_global_mut<State>(@hero);
let resource_account = account::create_signer_with_capability(&state.signer_cap);
token::create_named_token(
&resource_account,
string::utf8(COLLECTION_NAME),
description,
name,
option::none(),
uri,
)
}
create
处于被调用的最底端,其作用是创建 NFT。
0x03 view 函数
本实例中包含两个view
函数:
#[view]
public fun view_hero(creator: address, collection: String, name: String): Hero acquires Hero {
let token_address = token::create_token_address(
&creator,
&collection,
&name,
);
move_from<Hero>(token_address)
}
#[view]
public fun view_hero_by_object(hero_obj: Object<Hero>): Hero acquires Hero {
let token_address = object::object_address(&hero_obj);
move_from<Hero>(token_address)
}
这两个函数用以让 dApp 更方便地查看 hero 资料。
0x04 update_hero_level 函数
public entry fun update_hero_level(
account: &signer,
collection: String,
name: String,
level: u64
) acquires Hero, State {
// ger resource account
let state = borrow_global_mut<State>(@hero);
let resource_account = account::create_signer_with_capability(&state.signer_cap);
// get hero by creator
let (_hero_obj, hero) = get_hero(
&signer::address_of(&resource_account),
&collection,
&name
);
let account_address = signer::address_of(account);
// rule 0x01: only owner could modify this one.
// assert!(object::is_owner(hero_obj, account_address), ENOT_OWNER);
// rule 0x02: only cap owner could modify this one.
assert!(account_address == @hero, ENOT_CREATOR);
// gets `property_mutator_ref` of hero.
let property_mutator_ref = &hero.property_mutator_ref;
// Updates the description property.
property_map::update_typed(property_mutator_ref, &string::utf8(b"level"), level);
}
这个函数遵循如下过程:
拿到资源账户 通过创建人拿到 NFT 对 NFT 的属性进行升级
💡如果将 rule 0x02 改成 rule 0x01,会发生什么?