2022-10-27 11:13

教学丨如何 “整” 套属于你的数字藏品?

36.6万


前言


数字藏品作为近期区块链领域的热点之一备受大众关注,因其利用区块链技术,拥有唯一不可篡改的凭证,在保护其数字版权的基础上,实现真实可信的数字化发行、购买、收藏和使用。但数字藏品是如何产生的?如何流转的?又是如何与区块链结合实现版权保护的?这些问题却不被大众了解。

《看完这篇!新手也能写NFT合约!》中,我们学习了NFT合约标准ERC-721和ERC-1155的合约设计理念,并在趣链Baas平台内的Web IDE上,以开源标准ERC-721合约作为基础模板进行进一步的合约开发。本篇我们从底链角度出发,学习如何使用HVM合约发布和流转数字藏品,从这一系列的流程中来揭开数字藏品这个藏在区块链技术之后的新潮流的神秘面纱。


数字藏品协议


工欲善其事必先利其器,在对数字藏品进行深入解剖前,需要先了解下数字藏品协议。

数字藏品协议是一种约定当前数字藏品项目规范的方式,因为不同的数字藏品项目的需求都不同,其对外提供的接口服务也会因为其项目自身的特殊性而不同。但是没有统一的标准就不适合一个行业的产生,一旦每个数字藏品项目都有其自己的对外提供服务接口,那么对于区块链浏览器、数字钱包和买卖交易双方都将造成很大的困扰,需要为每个数字藏品项目都进行适配,那显然是不合适的。

因此需要有数字藏品项目的规范来约定一个数字藏品项目应该提供怎么样的功能以及其接口的出入参格式,方便第三方进行适配统一才能让数字藏品变得流通,所以一个具备标准通用的数字藏品协议就变得十分迫切。

类似以太坊中的ERC721协议,我们在HVM中也提供了相同功能的HPC721协议,让我们来看看协议中的具体内容:

public interface HPC721 {
// 声明发生数字藏品转移时触发的event事件 void eventTransfer(String from, String to, long id);
// 声明发生某个数字藏品授权时触发的event事件 void eventApproval(String owner, String approved, long id);
// 声明发生某个账户授权其所有数字藏品时触发的event事件 void eventApprovalForAll(String owner, String operator, boolean approved);
// 查询一个账户拥有的数字藏品数量 long balanceOf(String owner);
// 通过数字藏品凭证查询其所有者 String ownerOf(long id);
// 转移数字藏品 void transferFrom(String from, String to, long id);
// 转移数字藏品,并记录额外调用信息 void transferFrom(String from, String to, long id, byte[] calldata);
// 授权数字藏品给某个账户 void approve(String to, long id);
// 获取某个数字藏品的授权人 String getApproved(long id);
// 设置是否将所有数字藏品给予某个账户权限 void setApprovalForAll(String operator, boolean approved);
// 查询某个账户是否具有另一账户的授权 boolean isApprovedForAll(String owner, String operator);}

同时还有HPC721的扩展协议,用来定义数字藏品项目的扩展信息(可按需实现):

public interface HPC721Metadata extends HPC721 {
// 获取数字藏品项目名称 String name();
// 获取数字藏品项目的含义 String symbol();
// 获取某个数字藏品的uri String uri(long id);}

简单来讲数字藏品项目就是一个数字藏品合约,而数字藏品协议就是一个合约接口,指定了合约所提供的功能。


实现数字藏品合约


了解了数字藏品协议后,我们可以基于上述接口来实现一个数字藏品合约。首先是构建一个HVM合约项目,将合约主类名称之为PropertyContract并实现HPC721协议接口。

public class PropertyContract extends BaseContract implements HPC721 {    // ...}

定义好合约主类后,我们便可以填充合约主类的内容了,首先是定义存储合约数据的账本数据结构:

// 记录账户地址下的数字藏品数量@StoreField(hvmType = StoreField.TypeNestedMap)private NestedMapLong> balances;
// 记录某个数字藏品是否授权给某个账户地址@StoreField(hvmType = StoreField.TypeNestedMap)private NestedMap<Long, String> propertyApprovals;
// 记录某个账户是否给另一个账户全部数字藏品的授权@StoreField(hvmType = StoreField.TypeNestedMap)private NestedMapBoolean>> operatorApprovals;

分别定义了三个NestedMap结构来存储以下信息:账户地址下的数字藏品数量、某个数字藏品是否授权给某个账户地址和某个账户是否给另一个账户全部数字藏品的授权,用于满足协议中的方法所定义的功能。

此处可能会发现与一般的数字藏品合约不同,没有一个存储数字藏品的数据结构,也没有存储数字藏品ID到所有者的对应关系,这是因为我们在HVM中引入了PropertyAccount资产账户的概念,资产账户是一种将数字藏品映射为区块链底层账户模型的一种方式,用一个区块链底层账户来存储数字藏品,既能提高执行效率又能提高数据的安全性。

在对合约方法进行正式编写前,还需要定义好合约事件,在某些情况下触发合约事件,外部可以通过合约事件来解析在智能合约中发生的行为,以此一些第三方在获取合约数据时,便有了一个统一方便的方式。合约中需要定义的事件如下:

@Overridepublic void eventTransfer(String from, String to, long id) {    event(null,  "Transfer", from, to, String.valueOf(id));}
@Overridepublic void eventApproval(String owner, String approved, long id) { event(null, "Approval", owner, approved, String.valueOf(id));}
@Overridepublic void eventApprovalForAll(String owner, String operator, boolean approved) { event(null, "ApprovalForAll", owner, operator, String.valueOf(approved));}

三个事件分别在发生数字藏品转移(包括铸造)、授权某个数字藏品和对某个账户的全部数字藏品授权这三个场景下。定义好了事件内容后,便可以完善合约方法的部分了,同时将上述提到的账本数据结构、资产账户和合约事件融入到合约方法中。

首先是进行数字藏品铸造。由于铸造数字藏品并不属于协议内容,因此对数字藏品的铸造逻辑的自由度就会大一些:

public void mintProperty(long id, String owner, String meta) {    // 检查参数合法性    if (StringUtil.checkEmpty(owner) || meta == null) {        throw new RuntimeException("the emit param is illegal");    }    // 通过emit0方法铸造资产账户    byte[] identity = ByteUtil.longToBytes(id);    emit0(identity, owner, meta.getBytes());    // 对owner拥有的藏品数量进行更新    if (balances.get(owner) == null) {        balances.put(owner, 1L);    } else {        balances.put(owner, balances.get(owner)+1);    }    // 发送铸造成功事件    eventTransfer("", owner, id);}

在示例中我们通过了内置的emit0方法铸造生产了一个资产账户,在资产账户铸造时要求传入资产账户的ID、所有者和meta元数据信息,随后更新藏品数量和发送铸造合约事件,因此完成对一个数字藏品的铸造,将来可通过ID来查询到当前合约中的数字藏品资产账户。

有了铸造数字藏品的方法后就可以为合约定义转移功能了:

public void transferFrom(String from, String to, long id) {    // 判断藏品转移操作发起者是否为藏品的拥有者或者授权人    if (!isApprovedOrOwner(getSender(), id)) {        throw new RuntimeException("caller is not token owner or approved");    }    // 获取到资产账户,判断参数from是否为藏品的拥有者    PropertyV1 property = getPropertyNotNull(id);    if (!from.equals(property.getOwner())) {        throw new RuntimeException("transfer from incorrect owner");    }    if (StringUtil.checkEmpty(to)) {        throw new RuntimeException("transfer to the zero address");    }    // 移除藏品ID的授权信息以及更新拥有者的藏品数量    propertyApprovals.remove(id);    balances.put(from, balances.get(from)-1);    if (balances.get(to) == null) {        balances.put(to, 1L);    } else {        balances.put(to, balances.get(to)+1);    }    // 更新藏品的拥有者信息    property.setOwner(to);    // 发送转移藏品事件    eventTransfer(from, to, id);}
public PropertyV1 getPropertyNotNull(long id) { // 通过getProperty0传入ID读取资产账户信息 PropertyV1 property = getProperty0(ByteUtil.longToBytes(id)); if (property == null) { throw new RuntimeException("the property is not exist"); } return property;}

由此我们便拥有了一个数字藏品合约项目所需要主要功能,最后可将查询数字藏品信息的功能和查询数字藏品meta信息的功能补充上,例如查询账户下的藏品数量和查询藏品拥有者信息:

public long balanceOf(String owner) {    Long l = balances.get(owner);    if (l == null) {        return 0;    }    return l;}
public String ownerOf(long id) { PropertyV1 property = getPropertyNotNull(id); return property.getOwner();}

查询数字藏品meta信息的功能的合约方法如下:

public String metaOf(long id) {    PropertyV1 property = getPropertyNotNull(id);    return property.getMetaData();}


调用数字藏品合约


最后,我们便可以通过SDK来发起对合约的调用了:String contractAddress = deployContract();
// 铸造ID为1的数字藏品,在藏品meta元信息中填入数字藏品源文件在ipfs上的链接{ InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("mintProperty") .addlong(1) .addString(account.getAddress()) .addString("https://ipfs.io/ipfs/QmPTtdDthdzPM6gWiLhsBUs79LdMmXgTVTcz978c5JsDJF?filename=Image.png") .build(); invokeContract(account, invokeDirectlyParams, String.class, contractAddress); System.out.println("mint id 1 success");}
// 将数字藏品转移{ Account a1 = accountService.genAccount(Algo.SMRAW); InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("transferFrom") .addString(account.getAddress()) .addString(a1.getAddress()) .addlong(1) .build(); invokeContract(account, invokeDirectlyParams, String.class, contractAddress); System.out.println("transfer id 1 from " + account.getAddress() + " to " + a1.getAddress() + " successfully");}
// 查询账户下当前拥有的数字藏品数量,应变为0{ InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("balanceOf").addString(account.getAddress()).build(); String ret = invokeContract(account, invokeDirectlyParams, String.class, contractAddress); System.out.println("balanceOf " + account.getAddress() + " is " + ret);}
// 查询ID为1的数字藏品的地址,应为藏品被转入的地址{ InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("ownerOf").addlong(1).build(); String ret = invokeContract(account, invokeDirectlyParams, String.class, contractAddress); System.out.println("ownerOf id 1 is " + ret);}

该调用逻辑模拟了账号A铸造了一个ID为1的数字藏品,并在数字藏品的meta元数据中记录了源文件所在的ipfs地址,随后将数字藏品转移给账户B,之后向合约进行查询发现账户A下的数字藏品数量变为0,且ID为1的数字藏品的拥有者变为了账户B。进行完整的合约调用后将获得如下输出:


总结


至此我们完成了通过HVM来编写一个数字藏品合约项目,了解了数字藏品协议的内容和作用,并基于数字藏品协议补充了数字藏品合约的内容,最后通过SDK模拟场景调用了编写的合约方法,进行了数字藏品铸造、转移和查询。

从这一系列过程中我们发现数字藏品其实也没有那么难理解,其本质上是通过区块链技术进行背书的数字凭证,通过区块链来明确数字资产的所有权和保障数据安全。

本文链接:https://www.8btc.com/media/6784480
转载请注明文章出处

评论
登录 账号发表你的看法,还没有账号?立即免费 注册
下载
分享
收藏
阅读
评论
点赞
上一篇
下一篇