Alchemy的the Road to Web3第五周文本教程- 使用Chainlink构建一个动态的NFT

Alchemy是什么项目?

2019年12月,Alchemy完成1500万美元A轮融资,资方为Pantera Capital,斯坦福大学,Coinbase,三星等。

2021年4月,Alchemy以5.05亿美元估值完成8000万美元B轮融资,Coatue和Addition领投,DFJ Growth、K5 Global、Chainsmokers(烟鬼组合)、演员Jared Leto和Glazer家族参投。

2021年10月,Alchemy以35亿美元估值完成2.5亿美元C轮融资,由a16z领投的。

2022年2月,Alchemy以102亿美元估值完成2亿美元融资,Lightspeed与Silver Lake领投。

Alchemy是一个背景强大、经费充足、踏实做事、没有发币的团队,这样的项目不刷,难道去刷土狗吗?最近帝哥已经陆陆续续把任务快做完了,现在在陆续给大家产出教程,帝哥也关注了下现在获取到的Alchemy的NFT的价格,大家可以去OpenSea上面查询下:

话不多说,来开始我们今天的教程,我是帝哥(@CoinmanLabs),今天帝哥带大家一起来看看第五周的任务。

什么是动态 NFT?

一个动态 NFT是一种不可替代的代币,可以根据特定情况进行更改。

例如,目前有八种不同的LaMelo Ball NFT,每个 NFT 都会记录一组不同的 LaMelo 球员统计数据,从篮板和助攻到得分,并根据这些数据进行更改(10 次助攻?不同颜色 - 得分 1 分,不同球)。

动态 NFT 持有者可以根据 LaMelo 的持续表现获得抽奖和其他 NFT 特定福利的特殊访问权限。它变得更加凉爽;这八个 NFT 之一,Gold Evolve NFT,带来了一个独特的承诺:

如果拉梅洛·鲍尔(LaMelo Ball)赢得了 2021 年 NBA 赛季的年度最佳新秀,NFT 本身就会发展以反映新的形象。LaMelo 赢得了奖项,NFT 得到了发展。

我们能学到什么?

在本教程中,将学会使用Chainlink 的去中心化和加密保护的预言机网络获取和跟踪资产价格数据,将学会使用来自Chainlink 守护者网络自动化NFT 智能合约,以根据正在跟踪的资产价格数据更新 NFT。如果市场价格上涨,智能合约将随机选择 NFT 的 URI 指向这三个看涨图像之一,并且 NFT 将动态更新。

需要的工具和条件

1.获取测试币

进入 https://faucets.chain.link/ 连接钱包,点击Send Request获取测试以太坊和link 代币。

如果你的钱包没有link这个代币显示,点击导入代币,输入合约地址(

0x01BE23585060835E02B77ef475b0Cc51aA1e0709)即可:

当你成功获取到测试币结束后,进入 https://vrf.chain.link/rinkeby 连接你的钱包,点击Create Subscription,来获取一个Link 预言机的订阅,订阅需要消耗少量gas费。

等待完成后别急着关页面,我们给他提供一些资金。点击add funds。

2.初始化项目

进入 https://remix.ethereum.org/ ,开始我们今天的项目开发,新建一个sol文件,结构和代码如下:

代码如下:

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

// Chainlink Imports
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
// This import includes functions from both ./KeeperBase.sol and
// ./interfaces/KeeperCompatibleInterface.sol
import "@chainlink/contracts/src/v0.8/KeeperCompatible.sol";

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

// Dev imports. This only works on a local dev network
// and will not work on any test or main livenets.
import "hardhat/console.sol";

contract BullBear is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable, VRFConsumerBaseV2, KeeperCompatibleInterface  {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;
    uint public interval;
    uint public lastTimeStamp;

    AggregatorV3Interface public priceFeed;
    int256 public currentPrice;

    // IPFS URIs for the dynamic nft graphics/metadata.
    // NOTE: These connect to my IPFS Companion node.
    // You should upload the contents of the /ipfs folder to your own node for development.
    string[] bullUrisIpfs = [
        "https://ipfs.io/ipfs/QmRXyfi3oNZCubDxiVFre3kLZ8XeGt6pQsnAQRZ7akhSNs?filename=gamer_bull.json",
        "https://ipfs.io/ipfs/QmRJVFeMrtYS2CUVUM2cHJpBV5aX2xurpnsfZxLTTQbiD3?filename=party_bull.json",
        "https://ipfs.io/ipfs/QmdcURmN1kEEtKgnbkVJJ8hrmsSWHpZvLkRgsKKoiWvW9g?filename=simple_bull.json"
    ];
    string[] bearUrisIpfs = [
        "https://ipfs.io/ipfs/Qmdx9Hx7FCDZGExyjLR6vYcnutUR8KhBZBnZfAPHiUommN?filename=beanie_bear.json",
        "https://ipfs.io/ipfs/QmTVLyTSuiKGUEmb88BgXG3qNC8YgpHZiFbjHrXKH3QHEu?filename=coolio_bear.json",
        "https://ipfs.io/ipfs/QmbKhBXVWmwrYsTPFYfroR2N7NAekAMxHUVg2CWks7i9qj?filename=simple_bear.json"
    ];


    // random
    VRFCoordinatorV2Interface COORDINATOR;

    // Your subscription ID.
    uint64 s_subscriptionId;

    // Goerli coordinator. For other networks,
    // see https://docs.chain.link/docs/vrf-contracts/#configurations
    address vrfCoordinator = 0x6168499c0cFfCaCD319c818142124B7A15E857ab;

    // The gas lane to use, which specifies the maximum gas price to bump to.
    // For a list of available gas lanes on each network,
    // see https://docs.chain.link/docs/vrf-contracts/#configurations
    bytes32 keyHash = 0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc;

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. Storing each word costs about 20,000 gas,
    // so 100,000 is a safe default for this example contract. Test and adjust
    // this limit based on the network that you select, the size of the request,
    // and the processing of the callback request in the fulfillRandomWords()
    // function.
    uint32 callbackGasLimit = 100000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFCoordinatorV2.MAX_NUM_WORDS.
    uint32 numWords =  2;
    uint256[] public s_randomWords;
    uint256 public s_requestId;

    event TokensUpdated(string marketTrend);

    constructor(uint updateInterval, address _priceFeed, uint64 subscriptionId) ERC721("Bull&Bear", "BBTK") VRFConsumerBaseV2(vrfCoordinator) {
        interval = updateInterval;
        lastTimeStamp = block.timestamp;

        // https://rinkeby.etherscan.io/address/0xECe365B379E1dD183B20fc5f022230C044d51404
        priceFeed = AggregatorV3Interface(_priceFeed);
        currentPrice = getLatestPrice();

        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
    }

    function safeMint(address to) public {
        // Current counter value will be the minted token's token ID.
        uint256 tokenId = _tokenIdCounter.current();

        // Increment it so next time it's correct when we call .current()
        _tokenIdCounter.increment();

        // Mint the token
        _safeMint(to, tokenId);

        // Default to a bull NFT
        string memory defaultUri = bullUrisIpfs[s_randomWords[0]%3];
        _setTokenURI(tokenId, defaultUri);

        console.log(
            "DONE!!! minted token ",
            tokenId,
            " and assigned token url: ",
            defaultUri
        );
    }

    function checkUpkeep(bytes calldata) external view override returns (bool upkeepNeeded, bytes memory /*performData*/){
        upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
    }

    function performUpkeep(bytes calldata) external override{
        if((block.timestamp - lastTimeStamp) > interval){
            lastTimeStamp = block.timestamp;
            int latestPrice = getLatestPrice();

            if(latestPrice == currentPrice){
                return;
            }else if(latestPrice < currentPrice){
                updateAllTokenUris("bears");
            }else{
                updateAllTokenUris("bull");
            }

            currentPrice = latestPrice;
        }
    }

    function getLatestPrice() public view returns(int256){
        (,
        int price,
        ,
        ,) = priceFeed.latestRoundData();
        return price;
    }

    function updateAllTokenUris(string memory trend) internal{
        if(compareStrings("bears", trend)){
            for(uint i=0; i< _tokenIdCounter.current(); i++){
                _setTokenURI(i,bearUrisIpfs[s_randomWords[0]%3]);
            }
        }else {
            for(uint i=0; i< _tokenIdCounter.current(); i++){
                _setTokenURI(i,bullUrisIpfs[s_randomWords[0]%3]);
            }
        }

        emit TokensUpdated(trend);
    }

    function setInterval(uint256 newInterval) public onlyOwner{
        interval = newInterval;
    }

    function setPriceFeed(address newFeed) public onlyOwner{
        priceFeed = AggregatorV3Interface(newFeed);
    }

    function compareStrings(string memory a, string memory b) internal pure returns (bool){
        return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
    }

    // The following functions are overrides required by Solidity.
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function _burn(uint256 tokenId)
        internal
        override(ERC721, ERC721URIStorage)
    {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

        // Assumes the subscription is funded sufficiently.
    function requestRandomWords() external onlyOwner {
        // Will revert if subscription is not set and funded.
        s_requestId = COORDINATOR.requestRandomWords(
        keyHash,
        s_subscriptionId,
        requestConfirmations,
        callbackGasLimit,
        numWords
        );
    }

    function fulfillRandomWords(
        uint256, /* requestId */
        uint256[] memory randomWords
    ) internal override {
        s_randomWords = randomWords;
    }
}

3.部署合约

订阅的id
订阅的id

id我们将需要部署的合约选择 Bull&Bear 合约,账号选择我们刚才领取测试币的账号,部署的参数UPDATEINTERVAL填写 10,_PRICEFEED 为 Link 上面测试ILV合约的预言机地址 0x48731cF7e84dc94C5f84577882c14Be11a5B7456(合约地址来源https://docs.chain.link/docs/ethereum-addresses/),SUBSCRIPTIONID 为你自己申请的Link预言机订阅号。等待部署完成获取合约的地址。比如帝哥部署合约的地址:0xF0666b8ac53406aE3f630669Df6172BA0876C97b

3.1给预言机添加消费者

将我们的合约地址添加到预言机的消费者中。如下面所示。

当添加成功后,我们在使用合约里面的函数来获取随机数。首先点击requestRandomWords函数,等待交易确认后在s_randomWords函数后输入0获取第一个数,我们可以稍微留意下现在这个数,过了一分钟,我们在重复现在做的事:首先点击requestRandomWords然后在点击s_randomWords函数后输入0。

第一次结果
第一次结果

3.2mint给自己NFT

我们在afemint里面填写自己的ETH地址,给自己mint一个NFT,等NFT mint成功以后,我们在tokenURI 这边输入0,NFT的元数据信息。

我们可以看到我们的元数据已经返回了。

我们在setPriceFeed方法里输入Link预言机以太坊价格的合约地址 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e,将把原来的LIV价格的预言机改ETH的,以点击一下 getLatestPrice方法,可以看到int256后面就是最新的以太坊价格,其中前4位是个位数,后面8位为小数,我们在performUpkeep方法的参数里输入“[]”,点击call,手动来触发价格更新方法,这一步方法里也会更新我们的NFT元数据我们再次点击获取Token Id 为0的NFT的元数据,可以看到元数据会变成这三个中的一个。

最后我们去 https://testnets.opensea.io/ 刷新元数据是否不一样了,下面的是帝哥做的哦。

以上就是第五周的任务了,大家别忘记了填写表格完成哦,表格页面在下方:

我是懂币帝,用技术带你领略区块链魅力,第一时间获取行业最新资讯:

推特:@CoinmanLabs

微信:CoinmanLabs(社群客服微信进群)

Subscribe to Coinman.eth
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.