Move on Sui入门 003-在sui链上发布一个NFT合约

  • Gavin
  • 更新于 1天前
  • 阅读 107

一、知识点一次性见证,发布者权限,所有权的转移结构体和key、copy、store、drop能力错误码和断言基本数据类型的认识和使用display、table、transfer、event、object函数的认识和使用TxContent交易上下文对象公开函数的调用二、代码创建

一、知识点

  1. 一次性见证,发布者权限,所有权的转移
  2. 结构体和key、copy、store、drop能力
  3. 错误码和断言
  4. 基本数据类型的认识和使用
  5. display、table、transfer、event、object函数的认识和使用
  6. TxContent交易上下文对象
  7. 公开函数的调用

二、代码

  1. 创建项目

使用终端命令sui move mynft创建一个新项目,创建项目后找到Move.toml文件中的git地址,将github替换为gitee。

打开mynft.move文件,放开文件中的module注释。

代码书写时,相关标准库会自动引入。

  1. 设置NFT最大供应量
const MAX_SUPPLY: u64 = 10; // 允许铸造的最大NFT数量
  1. 设置相关错误码
const ENOTENOUGHSUPPLY: u64 = 101; // 当铸造的nft数量超过最大供应量时报错
const ENOTMINTAGAIN: u64 = 102; // 当同一个地址重复接收时报错
  1. 设置相关结构体及能力

结构体首字母必须大写。

NFT结构体:记录NFT元数据,有key,store能力

MintRecord结构体:记录铸造NFT的地址,有key能力

NFTMinted结构体:记录用户铸造NFT时需要传递给链下的地址,可共享,有copy,drop能力

MYNFT结构体:用于一次性见证,配合init函数使用,命名必须是module的大写,有drop能力,

// 结构体名称必须首字母大写
// 定义nft结构体,包含nft的基本信息,拥有key、store能力
public struct NFT has key, store {
    id: UID, // nft的唯一标识符,确保每个nft实例在区块链上都是唯一的
    nft_id: u64, // 铸造的nft编码,表示铸造的第几个nft
    name: String, // nft的名称
    image_url: String, // nft的图片地址
    creator: address, // nft的铸造者地址
    recipient: address // nft的接收者地址
}

// MintRecord结构体,用于记录NFT铸造的地址和编码的映射
public struct MintRecord has key {
    id: UID, // 结构体唯一标识符
    record: Table<address, u64> // 用户铸造NFT的映射,Table是一种映射集合,其中所有键值对的类型必须一致
}

// NFTMinted结构体,用于发送用户铸造 NFT 事件时需要传递给链下的数据
public struct NFTMinted has copy, drop {
    object_id: ID, // 记录铸造的NFT的UID
    creator: address, // 记录铸造者的地址
    name: String // 记录铸造的NFT的名称
}

// 用于一次性见证otw,命名必须是module的大写,而且要有drop能力
public struct MYNFT has drop {}
  1. 初始化init函数
    • 通过package::claim函数获取模块发布者权限
    • 创建一个display对象,了解display的作用
    • 创建MintRecord对象,记录NFT的铸造情况
    • 所有权的转移
// 初始化函数,otw在init函数出现一般在创建NFT、Coin等资产时
fun init(otw: MYNFT, ctx: &mut TxContext) {
    // 获取发布者权限,为了确保合约和数据的合法性、安全性、版本控制、元数据一致性以及透明性
    let publisher = package::claim(otw, ctx);
    // 创建一个vector,用来创建需要展示的NFT元数据的key值,用于display::new_with_fields方法的第二个参数
    let keys = vector[utf8(b"name"), utf8(b"image_url"), utf8(b"creator")];
    // 创建另一个vertor,用来创建需要展示的NFT元数据的value值,用于display::new_with_fields方法的第三个参数
    let values= vector[utf8(b"{name} #{nft_id}"), utf8(b"{image_url}"), utf8(b"{creator}")];
    // display对象用来定义每个 NFT 对象展示的关键元数据,传递泛型NFT结构体,是可变对象,需要传递NFT数据
    let mut display = display::new_with_fields<NFT>(&publisher, keys, values, ctx);

    // 创建MintRecord对象,用来追踪记录用户铸造NFT的情况
    let mintRecord = MintRecord{
        id: object::new(ctx),// 使用object::new(ctx)是唯一可以获取object UID的方法
        record: table::new<address, u64>(ctx)
    };

    // 设置display对象版本号
    display::update_version(&mut display);
    // 转移所有权
    // 将 Publisher 和 Display 对象转移给合约发布地址
    transfer::public_transfer(publisher, ctx.sender());
    transfer::public_transfer(display, ctx.sender());
    // MintRecord 对象需要共享所有权
    transfer::share_object(mintRecord);
}
  1. 实现铸造NFT功能

定义一个名为 mint 的公开函数,该函数需要5个参数:被铸造 NFT 的 name、image_url、recipient、MintRecord 对象和 TxContext交易上下文对象。

铸造过程中需要判断是否超出最大供应量、是否重复为同一个地址铸造。这里需要注意assert!(bool,code)方法的使用。

// 定义一个公开的mint函数,铸造方法,包含5个参数:name、image_url、recipient、mintRecord、TxContext
public entry fun mint (name: String, image_url: String, recipient: address, mint_record: &mut MintRecord, ctx: &mut TxContext) {
    // 断言 assert!(<bool expression>, <code>),bool 必须判断为真,否者(即bool为false)会报错误码并中断
    // 如果给同一个地址重复铸造就报错
    assert!(!table::contains(&mint_record.record, recipient), ENOTMINTAGAIN);
    // 记录NFT编号
    let nft_id = table::length(&mint_record.record) + 1;
    // 验证NFT是否超过最大供应量
    assert!(nft_id <= MAX_SUPPLY, ENOTENOUGHSUPPLY);
    // 完成铸造记录并映射
    table::add(&mut mint_record.record, recipient, nft_id);
    // 铸造NFT
    let nft = NFT{
        id: object::new(ctx), // nft的唯一标识符,确保每个nft实例在区块链上都是唯一的
        nft_id: nft_id, // 铸造的nft编码,表示铸造的第几个nft
        name: name, // nft的名称
        image_url: image_url, // nft的图片地址
        creator: ctx.sender(), // nft的铸造者地址
        recipient: recipient // nft的接收者地址
    };
    // 记录事件,在转移之前记录
    let nft_minted = NFTMinted{
        object_id: object::id(&nft), // 记录铸造的NFT的UID,object::id(obj)获取obj的UID
        creator: ctx.sender(), // 记录铸造者的地址
        name: nft.name // 记录铸造的NFT的名称
    };
    // 转移nft
    transfer::public_transfer(nft, recipient);
    // 发送事件通知
    event::emit(nft_minted);
}
  1. 实现销毁NFT的功能

销毁NFT需要将NFT MintRecord中的记录删除,并将拥有的NFT对象删除。

定义一个公开函数burn,传入铸造记录MintRecord、NFT两个参数。

// 燃烧已铸造nft,传入两个参数:铸造记录MintRecord、自己拥有的NFT
public entry fun burn(mint_record: &mut MintRecord, nft: NFT) {
    // 删除记录中的映射关系
    table::remove(&mut mint_record.record, nft.recipient);
    // 解包Object(解构?)
    let NFT {id, nft_id, name, image_url, creator, recipient} = nft;
    object::delete(id);
}
  1. 完整代码
module mynft::mynft;
use std::string::{String, utf8};
use sui::display;
use sui::event;
use sui::object;
use sui::package;
use sui::table;
use sui::table::Table;
use sui::transfer;

const MAX_SUPPLY: u64 = 10; // 允许铸造的最大NFT数量
// 错误码
const ENOTENOUGHSUPPLY: u64 = 101; // 当铸造的nft数量超过最大供应量时报错
const ENOTMINTAGAIN: u64 = 102; // 当同一个地址重复接收时报错

// 结构体名称必须首字母大写
// 定义nft结构体,包含nft的基本信息,拥有key、store能力
public struct NFT has key, store {
    id: UID, // nft的唯一标识符,确保每个nft实例在区块链上都是唯一的
    nft_id: u64, // 铸造的nft编码,表示铸造的第几个nft
    name: String, // nft的名称
    image_url: String, // nft的图片地址
    creator: address, // nft的铸造者地址
    recipient: address // nft的接收者地址
}

// MintRecord结构体,用于记录NFT铸造的地址和编码的映射
public struct MintRecord has key {
    id: UID, // 结构体唯一标识符
    record: Table<address, u64> // 用户铸造NFT的映射,Table是一种映射集合,其中所有键值对的类型必须一致
}

// NFTMinted结构体,用于发送用户铸造 NFT 事件时需要传递给链下的数据
public struct NFTMinted has copy, drop {
    object_id: ID, // 记录铸造的NFT的UID
    creator: address, // 记录铸造者的地址
    name: String // 记录铸造的NFT的名称
}

// 用于一次性见证otw,命名必须是module的大写,而且要有drop能力
public struct MYNFT has drop {}

// 初始化函数,otw在init函数出现一般在创建NFT、Coin等资产时
fun init(otw: MYNFT, ctx: &mut TxContext) {
    // 获取发布者权限,为了确保合约和数据的合法性、安全性、版本控制、元数据一致性以及透明性
    let publisher = package::claim(otw, ctx);
    // 创建一个vector,用来创建需要展示的NFT元数据的key值,用于display::new_with_fields方法的第二个参数
    let keys = vector[utf8(b"name"), utf8(b"image_url"), utf8(b"creator")];
    // 创建另一个vertor,用来创建需要展示的NFT元数据的value值,用于display::new_with_fields方法的第三个参数
    let values= vector[utf8(b"{name} #{nft_id}"), utf8(b"{image_url}"), utf8(b"{creator}")];
    // display对象用来定义每个 NFT 对象展示的关键元数据,传递泛型NFT结构体,是可变对象,需要传递NFT数据
    let mut display = display::new_with_fields<NFT>(&publisher, keys, values, ctx);

    // 创建MintRecord对象,用来追踪记录用户铸造NFT的情况
    let mintRecord = MintRecord{
        id: object::new(ctx),// 使用object::new(ctx)是唯一可以获取object UID的方法
        record: table::new<address, u64>(ctx)
    };

    // 设置display对象版本号
    display::update_version(&mut display);
    // 转移所有权
    // 将 Publisher 和 Display 对象转移给合约发布地址
    transfer::public_transfer(publisher, ctx.sender());
    transfer::public_transfer(display, ctx.sender());
    // MintRecord 对象需要共享所有权
    transfer::share_object(mintRecord);
}

// 定义一个公开的mint函数,铸造方法,包含5个参数:name、image_url、recipient、mintRecord、TxContext
public entry fun mint (name: String, image_url: String, recipient: address, mint_record: &mut MintRecord, ctx: &mut TxContext) {
    // 断言 assert!(<bool expression>, <code>),bool 必须判断为真,否者(即bool为false)会报错误码并中断
    // 如果给同一个地址重复铸造就报错
    assert!(!table::contains(&mint_record.record, recipient), ENOTMINTAGAIN);
    // 记录NFT编号
    let nft_id = table::length(&mint_record.record) + 1;
    // 验证NFT是否超过最大供应量
    assert!(nft_id <= MAX_SUPPLY, ENOTENOUGHSUPPLY);
    // 完成铸造记录并映射
    table::add(&mut mint_record.record, recipient, nft_id);
    // 铸造NFT
    let nft = NFT{
        id: object::new(ctx), // nft的唯一标识符,确保每个nft实例在区块链上都是唯一的
        nft_id: nft_id, // 铸造的nft编码,表示铸造的第几个nft
        name: name, // nft的名称
        image_url: image_url, // nft的图片地址
        creator: ctx.sender(), // nft的铸造者地址
        recipient: recipient // nft的接收者地址
    };
    // 记录事件,在转移之前记录
    let nft_minted = NFTMinted{
        object_id: object::id(&nft), // 记录铸造的NFT的UID,object::id(obj)获取obj的UID
        creator: ctx.sender(), // 记录铸造者的地址
        name: nft.name // 记录铸造的NFT的名称
    };
    // 转移nft
    transfer::public_transfer(nft, recipient);
    // 发送事件通知
    event::emit(nft_minted);
}

// 燃烧已铸造nft,传入两个参数:铸造记录MintRecord、自己拥有的NFT
public entry fun burn(mint_record: &mut MintRecord, nft: NFT) {
    // 删除记录中的映射关系
    table::remove(&mut mint_record.record, nft.recipient);
    // 解包Object(解构?)
    let NFT {id, nft_id, name, image_url, creator, recipient} = nft;
    object::delete(id);
}

三、调用

合约调用有两种方法:前端引入SDK使用代码调用;在终端使用命令调用。

这里先讲使用命令调用,调用命令:

sui client call --package <package ID> --module <moduleName> --function <function> --args <args1 args2 ...>

<>中的内容根据调用合约方法填写

package ID:合约发布后,产生的合约 package ID

moduleName:项目中module 的命名

function:需要调用的合约方法

args:合约方法中的参数,如果参数中包含TxContent交易上下文对象,不需要填写,一般放在方法参数的最后

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Gavin
Gavin
江湖只有他的大名,没有他的介绍。