Sui NFT应用实例:将NFT变成咖啡

近期在台北智慧城市峰会和博览会中,展示了使用NFT购买咖啡的系统。

Sui NFT应用实例:将NFT变成咖啡

在2023年3月28–31日举行的台北智慧城市峰会暨博览会中,参与者向大家演示了如何使用NFT兑换一杯香醇的咖啡。此系统由Sui基金会、MomentX以及Suia共同创建,演示了如何使用在Sui网络上的NFT兑换真实的商品或是服务。

展开来说,即参与者会被提示与虚拟AI助理聊天,AI助理询问参与者是否想要一杯现做的咖啡。如果参与者给出肯定的回复,AI助理将会展示一个二维码,扫码后,系统将会为其创建一个NFT,该NFT可在附近的咖啡铺兑换咖啡。

博览会上的智慧城市咖啡店照片
此间位于台北智慧城市峰会和博览会的咖啡铺接受NFT作为付款方式。

更重要的是,当参与者获得了他们的咖啡,系统将会在Sui网络上传送另外一个交易,将其NFT状态从未使用改成已使用。

Sui Object如何将NFT兑换为咖啡

在Sui上,所有的NFT皆为Object,而实际上Object具有非常强大的功能。在此实例中,Object代码中的一个URL指向一个NFT图片,该URL会根据NFT是否被使用而发生变化。以下代码展示了CoffeeNFT的Object。

struct CoffeeNFT has key, store {
		id: UID,
		name: String,
		description: String,
		url: String,
		redeemed: bool,
	}

以上代码代表了NFT在Sui上的标准实现。其中最值得注意的是,赎回区块与一般Object不同。我们由此做出判断该NFT是否已经被使用,而前面提及的指向NFT照片的URL则会根据此处的状态进行发生变化。

显示一杯咖啡的NFT
参加台北智慧城市峰会暨博览会的与会者可以用 NFT 兑换一杯咖啡,之后 NFT 将变为已消费状态。

需要注意的是,该对象本可以使用链上图片文件,而不是像其他区块链上的 NFT 一样,包含一个指向图片文件的 URL,这是Sui 支持的一项功能,可增强永久性。此外,该对象代码依赖于Move 的旧版本,在.28 版本发布和引入对象显示标准之前。我们将在本文末尾提供一个最新示例,说明如何编写此代码。

赠送NFT

系统使用 gift_nft 函数,将原始NFT交给参会者。当用户扫描虚拟助手出示的二维码时,就会调用该函数。

public entry fun gift_nft(
		global: &mut Global,
		to: address,
		name: vector<u8>,
		description: vector<u8>,
		ctx: &mut TxContext,
	) {
		assert!(tx_context::sender(ctx) == global.admin, ENOT_AUTHORIZED);
		let nft = CoffeeNFT {
			id: object::new(ctx),
			name: utf8(name),
			description: utf8(description),
			url: global.url_init,
			redeemed: false,
		};
		let coffee_nft_config = CoffeeNFTConfig {
			merchant_white_list: vec_set::empty(),
			merchant_redeemed: none(),
		};
		table::add(&mut global.nfts, object::id(&nft), coffee_nft_config);
		transfer(nft, to)
	}

当系统使用以下代码初始化NFT时 let nft = CoffeeNFT { ... }会将url设置为 global.url_init 并将其兑换为 错误。如图所示,该模块还包括一些检查,以确保用户不能兑换可能已不存在的咖啡。 redeem_request 函数如下:

public entry fun redeem_request(
		global: &mut Global,
		nft_id: ID,
		ctx: &mut TxContext,
	) {
		let merchant = tx_context::sender(ctx);
		assert!(vec_set::contains(&global.merchants, &merchant), EMERCHANT_NOT_AUTHORIZED);
		let nft_config = table::borrow_mut(&mut global.nfts, nft_id);
		assert!(option::is_none(&nft_config.merchant_redeemed), ENFT_ALREADY_REDEEMED);
		assert!(!vec_set::contains(&nft_config.merchant_allow_list, &merchant), EMERCHANT_ALREADY_AUTHORIZED);
		vec_set::insert(&mut nft_config.merchant_allow_list, merchant);
	}

该函数包括三个断言。第一个 EMERCHANT_NOT_AUTHORIZED断言调用此函数的用户是管理员确定的允许列表中的商家。第二个 ENFT_ALREADY_REDEEMED断言NFT尚未被赎回。第三个也是最后一个, EMERCHANT_ALREADY_AUTHORIZED断言NFT尚未获得商家授权。如果符合上述三项条件,则 redeem_request 函数将被成功执行。

NFT展示一棵咖啡树
一旦NFT兑换了一杯咖啡,CoffeeNFT对象就会显示新的图像。

可用库存统计

广告有时会在特价商品上注明 "售完即止"。通过这种方式使用NFT,我们可以在链上充分执行限量供应的概念!

最后,如果仍有可用库存,且用户希望赎回NFT,系统会调用 redeem_nft 函数,如下面的代码所示。

public entry fun redeem_confirm(
		global: &mut Global,
		nft: &mut CoffeeNFT,
		merchant: address,
		_ctx: &mut TxContext,
	) {
		// check if the merchant authorized to redeem
		let nft_config = table::borrow_mut(&mut global.nfts, object::id(nft));
		assert!(vec_set::contains(&nft_config.merchant_white_list, &merchant), EMERCHANT_NOT_AUTHORIZED);
		// check stock
		let stock = vec_map::get_mut(&mut global.stocks, &merchant);
		assert!(*stock > 0, ENOT_ENOUGH_STOCK);
 
        // redeem
		// update nft config
		nft_config.merchant_redeemed = some(merchant);
		// update stock
		*stock = *stock - 1;
		// update nft
		nft.redeemed = true;
		nft.url = global.url_redeemed;
	}

上述函数将库存量减1,并将NFT的状态更改为 redeemed并显示新的图像URL。这样,NFT就能让参会者和咖啡摊直观地确认NFT已被兑换。

更新Object Display

如上所述,博览会上使用的程序代码依赖于早期版本的Move ,特别是与对象显示有关的部分。根据当前的对象显示标准编写的程序可以如下所示。

module momentx::coffee_nft {
	use sui::tx_context::{sender, TxContext};
	use std::string::{utf8, String};
	use sui::transfer;
	use sui::object::UID;
    
	use sui::package;
	use sui::display;
	
    struct CoffeeNFT has key, store {
		id: UID,
		name: String,
		description: String,
		img_url: String,
		redeemed: bool,
	}
    
	struct COFFEE_NFT has drop {}
    
	fun init(otw: COFFEE_NFT, ctx: &mut TxContext) {
		let keys = vector[
			utf8(b"name"),
			utf8(b"description"),
			utf8(b"image_url"),
			utf8(b"redeemed")
		];
        
		let values = vector[
			utf8(b"{name}"),
			utf8(b"{description}"),
			utf8(b"ipfs://{img_url}"),
			utf8(b"{redeemed}"),
		];
        
		let publisher = package::claim(otw, ctx);
        
		let display = display::new_with_fields<CoffeeNFT>(
			&publisher, keys, values, ctx
		);
        
		display::update_version(&mut display);
        
		transfer::public_transfer(publisher, sender(ctx));
		transfer::public_transfer(display, sender(ctx));
	}
    
}

让NFT发挥实际价值

在上面的例子中,我们为NFT分配了真实世界的消费价值(一杯咖啡),并创建了一种机制来显示NFT是否已被兑换为其分配的价值。这种用法超越了区块链上典型的NFT交易,展示了真实世界的使用案例。此外,我们还在系统中内置了库存或存货的概念,认识到现实世界中物品数量有限的现实。

作为一个更广泛的概念,上述示例说明了Sui上的NFT如何成为具有互动功能的数字门票,其功能远远超出了目前用于电影院、音乐会和飞机等场所的数字门票或通行证。我们的NFT门票具有内置安全性、可控转让性和可编程显示功能。

有关其他Sui/Move 学习资源,请查看以下链接!