# 百亿项目Alchemy Road to Web3 第五周NFT获取教程

By [tantan](https://paragraph.com/@tantan) · 2022-09-20

---

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计划将新资金用于推广Web3采用，这方面的一些举措包括推出Web3 University，就是现在的 Road to Web3 活动，活动为期10周，每周一个NFT。看了下nft数量极少，估计由于任务难度大，很多小伙伴直接放弃，这样的项目若是空投，绝对是大毛。

手把手第五周教程开始：使用 Chainlink 预言机构建基于市场数据变化的动态 NFT
--------------------------------------------

![](https://storage.googleapis.com/papyrus_images/6818d6c1b37077dcc6675e6c67163be20573037af039f2f011a9130415f9bc8c.png)

什么是动态NFT？这一课很有趣。

NFT会变化，就像下面一样，我们开始吧！

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

### step1 领取测试币

1.小狐狸钱包调整到rinkey测试网，进 [faucets.chain.link](https://faucets.chain.link/) ，领取测试币。

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

### step2 编译合约

1.进入[remix.ethereum.org](https://remix.ethereum.org/) ,如图新建一个工作台，随意命名。

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

2.右键删除contract下面的三个文件。

![](https://storage.googleapis.com/papyrus_images/79b5cdb3d7cdc57a5683530837493803e7239ad280f60bafce57702014f6306c.png)

3.在contracts下面新建名为Bull&Bear.sol 的文件夹。

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

4.将下面的代码复制粘贴进来。

    // 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;
        }
    }
    

5.点击左侧的编译器菜单，版本选0.8.4，然后点击编译。

![](https://storage.googleapis.com/papyrus_images/0aa2b7905c2ef651134b4180b880017ad829e2f58e1e04c67c6a4e9b3496227e.png)

6.然后点击“Deploy and Run Transactions”菜单，此时确保小狐狸的网络是rinkey测试网，其他选项如图中红框所示。

![](https://storage.googleapis.com/papyrus_images/3c96f7d6a551bbd1568e1485c90d680df12f7ea05351d89523094935b9c0d107.png)

### step3 获取预言机订阅号

1.进入 [vrf.chain.link](https://vrf.chain.link/rinkeby) 并连接小狐狸。

![](https://storage.googleapis.com/papyrus_images/9baa831b55e9b48f14394c91f8fc237f4a0ae62709beb6edb300beecb62e19a7.png)

2.点击Create Subscription。

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

3.继续点击Create Subscription，并在跳出的小狐狸对话框点击确认。

![](https://storage.googleapis.com/papyrus_images/9f6a7ee64ca94b0ba118a2d1cad029b8e4d2403bb16d586baa07800258b10b6e.png)

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

4.再点击Add funds。

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

4.再输入任意数字，点击Add funds，并在小狐狸确认。

![](https://storage.googleapis.com/papyrus_images/551a1b7a9f4f326c1dc06e4d06829ed973ba39791f2775b47ce02b593da30e3f.png)

![](https://storage.googleapis.com/papyrus_images/051c0b9215a48e31b382331563d9e8e583dec0954d54c846233aa9a6765275a6.png)

5.等待十几秒，再点击Add consumer。

![](https://storage.googleapis.com/papyrus_images/31bb779422d472daca6f85ae71694fd446c05e50c27d96352250a33cf0b6df92.png)

6.到这个页面，先空着，等会儿补上，直接点I’ll do it later。

![](https://storage.googleapis.com/papyrus_images/9679bd4ee3b6e70fce32719b9cef95d84f3027844507b827121d8619ba50d6f5.png)

7.好了，如图，我这里获取的编号为11513，大家的这个不一样哈。

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

### step4 部署合约

1.回到remix界面。

第一行输入10；

第二行输入0xECe365B379E1dD183B20fc5f022230C044d51404 ；

第三行输入刚才获取的编号，我的是11513。

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

2.然后点击transact部署合约，出现小狐狸点击确认。

![](https://storage.googleapis.com/papyrus_images/29d1d8cb3788850185e0051edcc7a54385b1417307fabf6cdf98d2eeeb116784.png)

3.出现这样的信息就对了。

![](https://storage.googleapis.com/papyrus_images/05971f336f379d0c5c3e771a2247ced7408da100684e828fddfa1fa58b932e51.png)

4.回到step3的预言机界面，点击Add consumer。

![](https://storage.googleapis.com/papyrus_images/899f0f130ba77c963ef55f20366588737d317af15052f74b6f21b0b4616d7233.png)

5.将刚刚生成的合约地址复制下来，粘贴进预言机界面，点击Add consumer，然后在小狐狸中确认。

![](https://storage.googleapis.com/papyrus_images/9e0e5e589c7bf88688bc40d5bcc6cf65ed73244edcf301c8ef84099067d2dc0d.png)

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

![](https://storage.googleapis.com/papyrus_images/3aca27ecb1c78f21017630587247cc754d808e633c7a8a55e39492d5ecd57198.png)

6.点击close关闭，即可。

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

### step5 mint NFT

1.点击下箭头出现合约详情，点击requestRandomWords，弹出小狐狸钱包点击确认。

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

出现这个勾就继续。

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

2.在s\_randomWords中输入0，点击call。

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

3.然后在safemint里面填写自己的ETH地址，点击transact，弹出小狐狸钱包后点击确认。

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

出现如图所示就成功了。

![](https://storage.googleapis.com/papyrus_images/33e762745d31a25668ef04c21324abf1f7b5537e7dff16082bbe85562956489d.png)

4.此时进入[openseatest](https://testnets.opensea.io/) 查看，就可以看到刚才mint的NFT了。

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

### step6 提交项目

提交合约地址就可以了！

### step7 NFT领取

很多人问NFT怎么没有领取链接哈哈，其实都在[这里](https://mintkudos.xyz/)呀。

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

还有人私信说看不到前面的课程。。。。。估计是新关注的小伙伴吧，都贴下面了。

---

*Originally published on [tantan](https://paragraph.com/@tantan/alchemy-road-to-web3-nft-3)*
