使用 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sCrypt Web3应用开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值