使用 sCrypt 实现一个简单的 NFT 合约

Posted freedomhero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 sCrypt 实现一个简单的 NFT 合约相关的知识,希望对你有一定的参考价值。

我们之前的token方案针对的是可替换(fungible)的 token。这里来看看另一种方案如何实现 NFT(non-fungible token)合约。这类 token 可以代表独一无二的和不可分割的资产,比如房地产和收藏品。

概览

与可替换 token 类似,在每个 UTXO 中,token 合约的数据部分也有两个部分与 NFT 有关:

  1. 一个公钥:用于控制 token 的发行或转让。
  2. 一个整数:唯一标识一个 token。

同样,通过使用与公钥对应的私钥进行签名来对 UTXO 中的 token 进行转账。不同之处在于,在 NFT 的方案中每个UTXO只存储一个 token,而不象可替换 token 方案那样可以存储多个 token。

发行

token 发行 UTXO 是一个特殊的 UTXO 合约,只有发行者才可以运行该合约。发行者通过把该 UTXO 拆分成多个 UTXO 的方式来发行新的 token。下图是把 token 发行 UTXO 拆分成两个,这样就发行了一个新的 token i 给Alice。

下面是相关的代码:

 public function issue(Sig issuerSig, PubKey receiver, int satoshiAmount0, int satoshiAmount1, SigHashPreimage txPreimage) 
        // this ensures the preimage is for the current tx
        require(Tx.checkPreimage(txPreimage));

        // read previous locking script: codePart + OP_RETURN + currTokenId + issuer + matchAction
        bytes lockingScript = SigHash.scriptCode(txPreimage);
        int scriptLen = len(lockingScript);

        // constant part of locking script: upto op_return
        int constStart = scriptLen - DataLen - Constants.PubKeyLen - 1;
        bytes constPart = lockingScript[: constStart];

        bytes matchAction = lockingScript[scriptLen - 1 :];
        // action must be issue
        require(matchAction == NonFungibleToken.ISSUE);

        PubKey issuer = PubKey(lockingScript[constStart + DataLen : scriptLen - 1]);
        // authorize: only the issuer can mint new tokens
        require(checkSig(issuerSig, issuer));

        int currTokenId = unpack(lockingScript[constStart : constStart + DataLen]);

        // increment token ID to mint a new token
        bytes outputScript0 = constPart + num2bin(currTokenId + 1, DataLen) + issuer + NonFungibleToken.ISSUE;
        bytes output0 = Utils.buildOutput(outputScript0, satoshiAmount0);

        // transfer previous token to another receiver
        bytes outputScript1 = constPart + num2bin(currTokenId, DataLen) + receiver + NonFungibleToken.TRANSFER;
        bytes output1 = Utils.buildOutput(outputScript1, satoshiAmount1);

        // check outputs
        Sha256 hashOutputs = hash256(output0 + output1);
        require(hashOutputs == SigHash.hashOutputs(txPreimage));
    

转移

UTXO 中的 token 可以如下图所示进行转移:

如下是相关代码:

    public function transfer(Sig senderSig, PubKey receiver, int satoshiAmount, SigHashPreimage txPreimage) 
        require(Tx.checkPreimage(txPreimage));

        // read previous locking script: codePart + OP_RETURN + tokenID + ownerPublicKey + matchAction
        bytes lockingScript = SigHash.scriptCode(txPreimage);
        int scriptLen = len(lockingScript);

        // constant part of locking script: upto tokenID
        int constStart = scriptLen - Constants.PubKeyLen - 1;
        bytes constPart = lockingScript[: constStart];

        bytes matchAction = lockingScript[scriptLen - 1 :];
        // action must be transfer
        require(matchAction == NonFungibleToken.TRANSFER);

        PubKey sender = PubKey(lockingScript[constStart : scriptLen - 1]);
        // authorize
        require(checkSig(senderSig, sender));

        // transfer
        bytes outputScript = constPart + receiver + NonFungibleToken.TRANSFER;

        bytes output = Utils.buildOutput(outputScript, satoshiAmount);
        require(hash256(output) == SigHash.hashOutputs(txPreimage));
    

组合

下图展示了 NFT 的发行及转移的操作流程。

这里有完整的合约代码和使用示例

讨论

我们只展示了 NFT 的基本方案。它扩展起来也很简单,例如:

  • 在一个 Tx 中发行多个新 token
  • 在一个 Tx 中转移多个 token
  • 限制 token总量
  • 销毁 token

以上是关于使用 sCrypt 实现一个简单的 NFT 合约的主要内容,如果未能解决你的问题,请参考以下文章

在 sCrypt 合约中使用 HashedMap 数据结构

如何在 sCrypt 合约中实现浮点数运算

从 sCrypt 智能合约中访问区块链数据

编写和发布 scrypt-ts 库合约

编写和发布 scrypt-ts 库合约

sCrypt 合约中如何使用优化版 OP_PUSH_TX