# 用 Python 制作 NFT 区块链作品（下）

By [quantbang](https://paragraph.com/@quantbang) · 2022-03-24

---

![](https://storage.googleapis.com/papyrus_images/eb9796ada62204690b081f5cb9be1b4c959505a746d8c006c368fdf18131763c.jpg)

在本文中，我们将学习如何使用 Brownie、Python 和 Chainlink 来制作非同质化的 NFT 作品，并在 OpenSea NFT 市场上展示和销售我们的成果。

**动态和高级 NFT**

动态 NFT 是可以随时间变化的 NFT，或者具有我们可以用来相互交互的链上功能。这些 NFT 可以无限定制，让我们可以制作整个游戏、元宇宙（metaverse）或某种互动艺术。下面我们进入高级部分。

**高级快速入门**

确保您的metamask中有足够的测试网 ETH 和 LINK，然后运行以下命令：

    brownie run scripts/advanced_collectible/deploy_advanced.py --network rinkeby
    brownie run scripts/advanced_collectible/create_collectible.py --network rinkeby
    

我们的收藏品是从 Chainlink VRF 返回的随机犬种。Chainlink VRF 是一种获得可证明随机数的方法，因此我们的 NFT 真正稀缺。然后我们想要创建它的元数据。

    brownie run scripts/advanced_collectible/create_metadata.py --network rinkeby
    

然后我们可以选择将此数据上传到 IPFS，以便我们可以拥有一个 `tokenURI`。稍后我会告诉你如何做到这一点。现在，我们将仅使用以下示例 `tokenURI`：

    https://ipfs.io/ipfs/Qmd9MCGtdVz2miNumBHDbvj8bigSgTwnr4SbyH6DNnpWdt?filename=1-PUG.json
    

如果您将 IPFS Companion 下载到您的浏览器中，您可以使用该 URL 来查看 URI 返回的内容。它看起来像这样：

    {
        "name": "PUG",
        "description": "An adorable PUG pup!",
        "image": "https://ipfs.io/ipfs/QmSsYRx3LpDAb1GZQm7zZ1AuHZjfbPkD6J7s9r41xu1mf8?filename=pug.png",
        "attributes": [
            {
                "trait_type": "cuteness",
                "value": 100
            }
        ]
    }
    

然后我们可以运行我们的 `set_tokenuri.py` 脚本：

    brownie run scripts/advanced_collectible/set_tokenuri.py --network rinkeby
    

我们会得到这样的输出：

    Running 'scripts/advanced_collectible/set_tokenuri.py::main'...
    Working on rinkeby
    Transaction sent: 0x8a83a446c306d6255952880c0ca35fa420248a84ba7484c3798d8bbad421f88e
      Gas price: 1.0 gwei   Gas limit: 44601   Nonce: 354
      AdvancedCollectible.setTokenURI confirmed - Block: 8331653   Gas used: 40547 (90.91%)
    Awesome! You can view your NFT at https://testnets.opensea.io/assets/0x679c5f9adC630663a6e63Fa27153B215fe021b34/0
    Please give up to 20 minutes, and hit the "refresh metadata" button
    

我们可以点击给出的链接，看看它在 Opensea 上的样子！您可能需要点击刷新元数据按钮并等待几分钟。

![](https://storage.googleapis.com/papyrus_images/77be9b2f699a79c6e6697c79857908191cf5a5bef0a28178d668a1a9ef82145d.png)

**随机品种**

然我们看一下刚刚做了什么。这是我们的 `AdvancedCollectible.sol`：

    pragma solidity 0.6.6;
    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
    contract AdvancedCollectible is ERC721, VRFConsumerBase {
        uint256 public tokenCounter;
        enum Breed{PUG, SHIBA_INU, BRENARD}
        // add other things
        mapping(bytes32 => address) public requestIdToSender;
        mapping(bytes32 => string) public requestIdToTokenURI;
        mapping(uint256 => Breed) public tokenIdToBreed;
        mapping(bytes32 => uint256) public requestIdToTokenId;
        event requestedCollectible(bytes32 indexed requestId); 
        bytes32 internal keyHash;
        uint256 internal fee;
        uint256 public randomResult;
        constructor(address _VRFCoordinator, address _LinkToken, bytes32 _keyhash)
        public 
        VRFConsumerBase(_VRFCoordinator, _LinkToken)
        ERC721("Dogie", "DOG")
        {
            tokenCounter = 0;
            keyHash = _keyhash;
            fee = 0.1 * 10 ** 18;
        }
        function createCollectible(string memory tokenURI, uint256 userProvidedSeed) 
            public returns (bytes32){
                bytes32 requestId = requestRandomness(keyHash, fee, userProvidedSeed);
                requestIdToSender[requestId] = msg.sender;
                requestIdToTokenURI[requestId] = tokenURI;
                emit requestedCollectible(requestId);
        }
        function fulfillRandomness(bytes32 requestId, uint256 randomNumber) internal override {
            address dogOwner = requestIdToSender[requestId];
            string memory tokenURI = requestIdToTokenURI[requestId];
            uint256 newItemId = tokenCounter;
            _safeMint(dogOwner, newItemId);
            _setTokenURI(newItemId, tokenURI);
            Breed breed = Breed(randomNumber % 3); 
            tokenIdToBreed[newItemId] = breed;
            requestIdToTokenId[requestId] = newItemId;
            tokenCounter = tokenCounter + 1;
        }
        function setTokenURI(uint256 tokenId, string memory _tokenURI) public {
            require(
                _isApprovedOrOwner(_msgSender(), tokenId),
                "ERC721: transfer caller is not owner nor approved"
            );
            _setTokenURI(tokenId, _tokenURI);
        }
    }
    

我们使用 Chainlink VRF 从 PUG、SHIBA\_INU、BRENARD 列表中创建一个随机品种。当我们这次调用 `createCollectible` 时，我们实际上向链下的 Chainlink VRF 节点发起了一个请求，并返回一个随机数，以使用这 3 个品种之一创建 NFT。

在你的 NFT 中使用真正的随机性是创造真正稀缺性的好方法，使用 Chainlink oracle 随机数意味着你的数字可以证明是随机的，并且不会受到矿工的影响。

您可以在文档中了解有关 Chainlink VRF 的更多信息。

[https://docs.chain.link/docs/chainlink-vrf/](https://docs.chain.link/docs/chainlink-vrf/)

Chainlink 节点通过调用 `fulfillRandomness` 函数进行响应，并根据随机数创建收藏品。然后我们仍然需要调用 `_setTokenURI` 来为我们的 NFT 提供它需要的外观。

我们没有在这里给出我们的 NFT 属性，但属性是让我们的 NFT 进行交互的好方法。您可以在此 `龙与地下城`示例中看到具有属性的 NFT 的一个很好的示例。

[https://github.com/PatrickAlphaC/dungeons-and-dragons-nft](https://github.com/PatrickAlphaC/dungeons-and-dragons-nft)

**来自 IPFS 的元数据**

我们使用 IPFS 来存储两个文件：

*   NFT 的形象（哈巴狗形象）
    
*   `tokenURI` 文件（JSON 文件，其中还包含图像的链接）
    

我们使用 IPFS 是因为它是一个免费的去中心化平台。我们可以通过下载 IPFS 桌面并点击导入按钮将我们的 tokenURI 和图像添加到 IPFS。

![](https://storage.googleapis.com/papyrus_images/2ea30a486da345fc0079e0c55d637892e1205d7bc27a0521baa3bc7ee7d8d1ff.png)

然后，我们可以通过点击要共享的文件旁边的 3 个点、点击共享链接并复制给定的链接来共享 URI。然后我们可以将此链接添加到我们的 `set_tokenuri.py` 文件中以更改我们想要使用的 `tokenURI`。

**持久性**

但是，如果 tokenURI 仅在我们的节点上，这意味着当我们的节点关闭时，没有其他人可以查看它。所以我们希望其他人 `pin`我们的 NFT。我们可以使用 `Pinata` 之类的 `pin`服务来帮助我们的数据保持活动状态，即使我们的 IPFS 节点已关闭。

我想未来会有越来越多的元数据存储在 IPFS 和去中心化存储平台上。集中式服务器可能会宕机，这意味着这些 NFT 上的艺术将永远丢失。请务必检查您使用的 NFT 的 tokenURI 所在的位置！

我也希望更多的人会使用像 Filecoin 这样的 dStorage 平台，因为使用 `pin`服务也没有像它应该的那样去中心化。

现在，您已经具备了制作漂亮有趣、可定制、交互式 NFT 的技能，并让它们在市场上呈现。

NFT 是一种有趣、强大的方式，可以补偿艺术家们所做的辛勤工作。

---

*Originally published on [quantbang](https://paragraph.com/@quantbang/python-nft-2)*
