├── .env ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── contracts ├── DungeonsAndDragonsCharacter.sol └── Migrations.sol ├── metadata ├── mainnet-chainlink-elf.json ├── mainnet-chainlink-knight.json ├── mainnet-chainlink-orc.json ├── mainnet-chainlink-wizard.json ├── the-chainlink-elf.json ├── the-chainlink-knight.json ├── the-chainlink-orc.json └── the-chainlink-wizard.json ├── migrations ├── 1_initial_migration.js └── 2_mycontract_migration.js ├── package.json ├── scripts ├── create-metadata.js ├── environmentvar-test.js ├── fund-contract.js ├── generate-character.js ├── get-character.js └── set-token-uri.js ├── test └── DungeonsAndDragonsCharactors.test.js └── truffle-config.js /.env: -------------------------------------------------------------------------------- 1 | # This is where you add your environment variables 2 | MNEMONIC='cat dog frog....' 3 | RINKEBY_RPC_URL='www.infura.io/asdfadsfafdadf' 4 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC='cat dog frog....' 2 | RINKEBY_RPC_URL='www.infura.io/asdfadsfafdadf' 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | yarn.lock 10 | package-lock.json 11 | node_modules 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Patrick Collins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chainlink Random Character Creation 2 | 3 | This repo is a starting point for creating: 4 | 1. NFTs built with verifiable RNG using the [Chainlink VRF](https://docs.chain.link/docs/get-a-random-number) 5 | 2. Create dynamic NFTs that change based on real world data. [By using decentralized oracles to get data.](https://docs.chain.link/docs/make-a-http-get-request) 6 | 3. Adding your randomized NFTs to the [OpenSea Marketplace](https://opensea.io/) 7 | 8 | Skip down to [deploy To Opensea](#deploy-to-opensea) - to see how to add a tokenURI 9 | 10 | We will easily create our own NFT on the Rinkeby Chain. We can edit the name of the character in the [`generate-character.js`](./scripts/generate-character.js) script. 11 | 12 | This will create a character with 6 attributes from 0 - 99: 13 | - uint256 strength; 14 | - uint256 dexterity; 15 | - uint256 constitution; 16 | - uint256 intelligence; 17 | - uint256 wisdom; 18 | - uint256 charisma; 19 | 20 | And then: 21 | - uint256 experience; 22 | - string name; 23 | 24 | ## Quickstart 25 | 26 | Right now this repo only works with rinkeby. Run the following. 27 | 28 | ### Setup Environment Variables 29 | You'll need a `MNEMONIC` and a rinkeby `RINKEBY_RPC_URL` environment variable. Your `MNEMONIC` is your seed phrase of your wallet. You can find an `RINKEBY_RPC_URL` from node provider services like [Infura](https://infura.io/) 30 | 31 | Then, you can create a `.env` file with the following. 32 | 33 | ```bash 34 | MNEMONIC='cat dog frog....' 35 | RINKEBY_RPC_URL='www.infura.io/asdfadsfafdadf' 36 | ``` 37 | 38 | Or, set them in a `bash_profile` file or export them directly into your terminal. You can learn more about [environment variables here](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html). 39 | 40 | To run them directly in your terminal, run: 41 | ```bash 42 | export MNEMONIC='cat dog frog....' 43 | export RINKEBY_RPC_URL='www.infura.io/asdfadsfafdadf' 44 | ``` 45 | 46 | Then you can get started with: 47 | 48 | ### Clone The Repo and migrate 49 | ``` 50 | git clone https://github.com/PatrickAlphaC/dungeons-and-dragons-nft 51 | cd dungeons-and-dragons-nft 52 | yarn 53 | truffle migrate --reset --network rinkeby 54 | ``` 55 | 56 | This will deploy your D&D NFT! 57 | 58 | ### Generate a character 59 | You can now try it out: 60 | ```bash 61 | truffle exec scripts/fund-contract.js --network rinkeby 62 | truffle exec scripts/generate-character.js --network rinkeby 63 | truffle exec scripts/get-character.js --network rinkeby 64 | ``` 65 | 66 | This will create a new character with random stats! 67 | Depending how often you deploy, you can pick which character by changing the [`dnd.getCharacterOverView(1)`](contracts/DungeonsAndDragonsCharacter.sol) command in `get-character.js` to swap the `0` out with whatever `tokenId` of the character you like. 68 | 69 | This will give you the overview of your NFT. You'll see `BN` since the call returns big numbers, you can cast them to ints to see what they are.... Or you could go one step farther 70 | 71 | ### See it on etherscan or oneclickdapp 72 | 73 | You can get an [Etherscan API key](https://etherscan.io/apis) for free and interact with the NFTs on chain. Then set `ETHERSCAN_API_KEY ` as an environment variable. 74 | 75 | ```bash 76 | yarn add truffle-plugin-verify 77 | truffle run verify DungeonsAndDragonsCharacter --network rinkeby --license MIT 78 | ``` 79 | 80 | This will verify and publish your contract, and you can go to the `Read Contract` section of etherscan that it gives you. 81 | 82 | Otherwise, you can use [oneclickdapp](https://oneclickdapp.com/) and just add the contract address and ABI. You can find the ABI in the `build/contracts` folder. Just remember it's not the whole file that is the ABI, just the section that says `ABI`. 83 | 84 | 85 | # Deploy to Opensea 86 | 87 | Once we have our NFTs created, we need to give them a `tokenURI`. TokenURIs are the standard for showing the data of NFTs to the world. This makes it easier to store things like images since we don't have to waste the gas of adding them on-chain. 88 | 89 | The [TokenURI](https://eips.ethereum.org/EIPS/eip-721) represents a URL or other unique identifier, and it is an `.json` file with a few parameters. 90 | 91 | ``` 92 | { 93 | "name": "Name for it ", 94 | "description": "Anything you want", 95 | "image": "https://ipfs.io/ipfs/HASH_HERE?file.png", 96 | "attributes": [...] 97 | } 98 | ``` 99 | 100 | We are going to be storing these images and meta data in IPFS. You'll need both: 101 | 1. [IPFS](https://ipfs.io/) 102 | 2. [IPFS companion](https://chrome.google.com/webstore/detail/ipfs-companion/nibjojkomfdiaoajekhjakgkdhaomnch?hl=en) 103 | 3. [Pinata](https://pinata.cloud/pinataupload) 104 | 105 | IPFS is a peer to peer network for storing files. It's free and open sourced, and we can use it to host our tokenURI. The IPFS companion let's us view IPFS data nativly in our browsers like Brave or Chrome. And Pinata allows us to keep our IPFS files up even when our node is down (don't worry about that for now) 106 | 107 | Once our IPFS node is up, we can start adding files to it. We first want to upload the image of our NFT. What does this D&D character look like? Add it to your IPFS node and then "Pin" it. Once pinned, you can get the CID of the pinned file, and make sure it stays pinned by pinning it on your Pinata account. Don't worry, it's free! This will just help keep the data up even when our IPFS node is down. 108 | 109 | Once we have the image pinned and up, we can get the link for that image. It'll look a little something like this: 110 | 111 | `https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png` 112 | 113 | This is a real link, and if you click it and nothing renders, your IPFS companion might not be working, or your IPFS node is down. 114 | 115 | Once we have our image, we can add it to our metadata `.json` file, and add our stats in there. You can see some samples in the `metadata` folder. We want to use the values of our characters that we got off-chain, so be sure to verify what the random numbers you got on etherscan! Once we have the .json metadata file, we want to add that to IPFS as well, and pin it too! 116 | 117 | This metadata json file is going to be our `tokenURI`, so we will modify our `set-token-uri.js` with the `tokenId` of the NFT we are giving a picture to, and adding the ipfs tokenURI. 118 | 119 | Then we just run it like: 120 | 121 | ``` 122 | truffle exec scripts/set-token-uri.js --network rinkeby 123 | ``` 124 | 125 | Now, we can get the address of our NFT and head on over to the opensea testnet marketplace to see if we did it correctly. If done correctly, it'll look [something like this](https://testnets.opensea.io/assets/dungeonsanddragonscharacter-v9). 126 | 127 | [Here is the link for adding your testnet NFT contract to be viewed on opensea.](https://testnets.opensea.io/get-listed/step-two) 128 | -------------------------------------------------------------------------------- /contracts/DungeonsAndDragonsCharacter.sol: -------------------------------------------------------------------------------- 1 | // contracts/DungeonsAndDragonsCharacter.sol 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.6.6; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | import "@openzeppelin/contracts/utils/Strings.sol"; 9 | 10 | contract DungeonsAndDragonsCharacter is ERC721, VRFConsumerBase, Ownable { 11 | using SafeMath for uint256; 12 | using Strings for string; 13 | 14 | bytes32 internal keyHash; 15 | uint256 internal fee; 16 | uint256 public randomResult; 17 | address public VRFCoordinator; 18 | // rinkeby: 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B 19 | address public LinkToken; 20 | // rinkeby: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709a 21 | 22 | struct Character { 23 | uint256 strength; 24 | uint256 dexterity; 25 | uint256 constitution; 26 | uint256 intelligence; 27 | uint256 wisdom; 28 | uint256 charisma; 29 | uint256 experience; 30 | string name; 31 | } 32 | 33 | Character[] public characters; 34 | 35 | mapping(bytes32 => string) requestToCharacterName; 36 | mapping(bytes32 => address) requestToSender; 37 | mapping(bytes32 => uint256) requestToTokenId; 38 | 39 | /** 40 | * Constructor inherits VRFConsumerBase 41 | * 42 | * Network: Rinkeby 43 | * Chainlink VRF Coordinator address: 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B 44 | * LINK token address: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 45 | * Key Hash: 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311 46 | */ 47 | constructor(address _VRFCoordinator, address _LinkToken, bytes32 _keyhash) 48 | public 49 | VRFConsumerBase(_VRFCoordinator, _LinkToken) 50 | ERC721("DungeonsAndDragonsCharacter", "D&D") 51 | { 52 | VRFCoordinator = _VRFCoordinator; 53 | LinkToken = _LinkToken; 54 | keyHash = _keyhash; 55 | fee = 0.1 * 10**18; // 0.1 LINK 56 | } 57 | 58 | function requestNewRandomCharacter( 59 | string memory name 60 | ) public returns (bytes32) { 61 | require( 62 | LINK.balanceOf(address(this)) >= fee, 63 | "Not enough LINK - fill contract with faucet" 64 | ); 65 | bytes32 requestId = requestRandomness(keyHash, fee); 66 | requestToCharacterName[requestId] = name; 67 | requestToSender[requestId] = msg.sender; 68 | return requestId; 69 | } 70 | 71 | function getTokenURI(uint256 tokenId) public view returns (string memory) { 72 | return tokenURI(tokenId); 73 | } 74 | 75 | function setTokenURI(uint256 tokenId, string memory _tokenURI) public { 76 | require( 77 | _isApprovedOrOwner(_msgSender(), tokenId), 78 | "ERC721: transfer caller is not owner nor approved" 79 | ); 80 | _setTokenURI(tokenId, _tokenURI); 81 | } 82 | 83 | function fulfillRandomness(bytes32 requestId, uint256 randomNumber) 84 | internal 85 | override 86 | { 87 | uint256 newId = characters.length; 88 | uint256 strength = (randomNumber % 100); 89 | uint256 dexterity = ((randomNumber % 10000) / 100 ); 90 | uint256 constitution = ((randomNumber % 1000000) / 10000 ); 91 | uint256 intelligence = ((randomNumber % 100000000) / 1000000 ); 92 | uint256 wisdom = ((randomNumber % 10000000000) / 100000000 ); 93 | uint256 charisma = ((randomNumber % 1000000000000) / 10000000000); 94 | uint256 experience = 0; 95 | 96 | characters.push( 97 | Character( 98 | strength, 99 | dexterity, 100 | constitution, 101 | intelligence, 102 | wisdom, 103 | charisma, 104 | experience, 105 | requestToCharacterName[requestId] 106 | ) 107 | ); 108 | _safeMint(requestToSender[requestId], newId); 109 | } 110 | 111 | function getLevel(uint256 tokenId) public view returns (uint256) { 112 | return sqrt(characters[tokenId].experience); 113 | } 114 | 115 | function getNumberOfCharacters() public view returns (uint256) { 116 | return characters.length; 117 | } 118 | 119 | function getCharacterOverView(uint256 tokenId) 120 | public 121 | view 122 | returns ( 123 | string memory, 124 | uint256, 125 | uint256, 126 | uint256 127 | ) 128 | { 129 | return ( 130 | characters[tokenId].name, 131 | characters[tokenId].strength + characters[tokenId].dexterity + characters[tokenId].constitution + characters[tokenId].intelligence + characters[tokenId].wisdom + characters[tokenId].charisma, 132 | getLevel(tokenId), 133 | characters[tokenId].experience 134 | ); 135 | } 136 | 137 | function getCharacterStats(uint256 tokenId) 138 | public 139 | view 140 | returns ( 141 | uint256, 142 | uint256, 143 | uint256, 144 | uint256, 145 | uint256, 146 | uint256, 147 | uint256 148 | ) 149 | { 150 | return ( 151 | characters[tokenId].strength, 152 | characters[tokenId].dexterity, 153 | characters[tokenId].constitution, 154 | characters[tokenId].intelligence, 155 | characters[tokenId].wisdom, 156 | characters[tokenId].charisma, 157 | characters[tokenId].experience 158 | ); 159 | } 160 | 161 | function sqrt(uint256 x) internal view returns (uint256 y) { 162 | uint256 z = (x + 1) / 2; 163 | y = x; 164 | while (z < y) { 165 | y = z; 166 | z = (x / z + z) / 2; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.24 <0.7.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint256 public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint256 completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /metadata/mainnet-chainlink-elf.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chainlink Elf", 3 | "description": "Inspiring, Based, Mythical, Oracle loving creature. Leading the new world and helping teach about superior digital agreements. Also is good with a bow!", 4 | "image": "https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 59 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 29 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 18 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 46 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 66 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 83 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /metadata/mainnet-chainlink-knight.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chainlink Knight", 3 | "description": "Defender against Flash Loan attacks, protector of #Defi protocols. Helping keep the smart contract kingdom secure, reliable, and safe. ", 4 | "image": "https://ipfs.io/ipfs/QmZGQA92ri1jfzSu61JRaNQXYg1bLuM7p8YT83DzFA2KLH?filename=Chainlink_Knight.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 52 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 55 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 18 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 95 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 81 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 93 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /metadata/mainnet-chainlink-orc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chainlink Orc", 3 | "description": "This green tweeting machine is one of the most exciting and excited creatures on the planet. Don't let his looks fool you, he loves to love new technology and help others get involved. Loves watching 'JSON Parser'", 4 | "image": "https://ipfs.io/ipfs/QmW1toapYs7M29rzLXTENn3pbvwe8ioikX1PwzACzjfdHP?filename=Chainlink_Orc.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 47 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 77 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 16 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 47 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 95 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 85 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /metadata/mainnet-chainlink-wizard.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chainlink Wizard", 3 | "description": "Brilliant spell-slinger and magical with cryptography. Often uses Jewles in her h-index potions. ", 4 | "image": "https://ipfs.io/ipfs/QmPMwQtFpEdKrUjpQJfoTeZS1aVSeuJT6Mof7uV29AcUpF?filename=Chainlink_Witch.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 52 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 11 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 17 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 85 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 24 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 54 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /metadata/the-chainlink-elf.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "The Chainlink Elf", 3 | "description": "Inspiring, Based, Mythical, Oracle loving creature. Leading the new world and helping teach about superior digital agreements. Also is good with a bow!", 4 | "image": "https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 84 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 86 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 19 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 79 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 93 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 53 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /metadata/the-chainlink-knight.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "The Chainlink Knight", 3 | "description": "Defender against Flash Loan attacks, protector of #Defi protocols. Helping keep the smart contract kingdom secure, reliable, and safe. ", 4 | "image": "https://ipfs.io/ipfs/QmZGQA92ri1jfzSu61JRaNQXYg1bLuM7p8YT83DzFA2KLH?filename=Chainlink_Knight.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 53 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 54 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 2 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 96 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 94 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 99 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /metadata/the-chainlink-orc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "The Chainlink Orc", 3 | "description": "This green tweeting machine is one of the most exciting and excited creatures on the planet. Don't let his looks fool you, he loves to love new technology and help others get involved. Loves watching 'JSON Parser'", 4 | "image": "https://ipfs.io/ipfs/QmW1toapYs7M29rzLXTENn3pbvwe8ioikX1PwzACzjfdHP?filename=Chainlink_Orc.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 46 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 55 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 54 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 61 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 93 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 21 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /metadata/the-chainlink-wizard.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "The Chainlink Wizard", 3 | "description": "Brilliant spell-slinger and magical with cryptography. Often uses Jewles in her h-index potions. ", 4 | "image": "https://ipfs.io/ipfs/QmPMwQtFpEdKrUjpQJfoTeZS1aVSeuJT6Mof7uV29AcUpF?filename=Chainlink_Witch.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Strength", 8 | "value": 90 9 | }, 10 | { 11 | "trait_type": "Dexterity", 12 | "value": 74 13 | }, 14 | { 15 | "trait_type": "Constitution", 16 | "value": 96 17 | }, 18 | { 19 | "trait_type": "Intelligence", 20 | "value": 90 21 | }, 22 | { 23 | "trait_type": "Wisdom", 24 | "value": 2 25 | }, 26 | { 27 | "trait_type": "Charisma", 28 | "value": 96 29 | }, 30 | { 31 | "trait_type": "Experience", 32 | "value": 0 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require('Migrations') 2 | 3 | module.exports = deployer => { 4 | deployer.deploy(Migrations) 5 | } 6 | -------------------------------------------------------------------------------- /migrations/2_mycontract_migration.js: -------------------------------------------------------------------------------- 1 | const DungeonsAndDragonsCharacter = artifacts.require('DungeonsAndDragonsCharacter') 2 | const { LinkToken } = require('@chainlink/contracts/truffle/v0.4/LinkToken') 3 | const RINKEBY_VRF_COORDINATOR = '0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B' 4 | const RINKEBY_LINKTOKEN = '0x01be23585060835e02b77ef475b0cc51aa1e0709' 5 | const RINKEBY_KEYHASH = '0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311' 6 | 7 | module.exports = async (deployer, network, [defaultAccount]) => { 8 | // hard coded for rinkeby 9 | LinkToken.setProvider(deployer.provider) 10 | DungeonsAndDragonsCharacter.setProvider(deployer.provider) 11 | if (network.startsWith('rinkeby')) { 12 | await deployer.deploy(DungeonsAndDragonsCharacter, RINKEBY_VRF_COORDINATOR, RINKEBY_LINKTOKEN, RINKEBY_KEYHASH) 13 | let dnd = await DungeonsAndDragonsCharacter.deployed() 14 | } else if (network.startsWith('mainnet')) { 15 | console.log("If you're interested in early access to Chainlink VRF on mainnet, please email vrf@chain.link") 16 | } else { 17 | console.log("Right now only rinkeby works! Please change your network to Rinkeby") 18 | // await deployer.deploy(DungeonsAndDragonsCharacter) 19 | // let dnd = await DungeonsAndDragonsCharacter.deployed() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainlink/box", 3 | "version": "0.6.0", 4 | "description": "A Chainlink example in a Truffle box", 5 | "scripts": { 6 | "compile": "npx truffle compile", 7 | "console:dev": "npx truffle console --network cldev", 8 | "console:live": "npx truffle console --network live", 9 | "depcheck": "echo '@chainlink/box' && depcheck --ignore-dirs=build/contracts || true", 10 | "solhint": "solhint ./contracts/**/*.sol", 11 | "lint": "yarn solhint", 12 | "migrate:dev": "npx truffle migrate --reset --network cldev", 13 | "migrate:live": "npx truffle migrate --network live", 14 | "test": "NODE_ENV=test npx truffle test" 15 | }, 16 | "license": "MIT", 17 | "dependencies": { 18 | "@chainlink/contracts": "0.1.9", 19 | "@openzeppelin/contracts": "^3.1.0", 20 | "@truffle/hdwallet-provider": "^1.4.1", 21 | "dotenv": "^8.2.0" 22 | }, 23 | "devDependencies": { 24 | "@chainlink/belt": "^0.0.1", 25 | "@chainlink/test-helpers": "0.0.5", 26 | "@openzeppelin/test-helpers": "^0.5.6", 27 | "chai": "^4.2.0", 28 | "depcheck": "^0.9.1", 29 | "solhint": "^2.1.0", 30 | "truffle": "^5.1.5", 31 | "truffle-plugin-verify": "^0.5.7" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/create-metadata.js: -------------------------------------------------------------------------------- 1 | const DungeonsAndDragons = artifacts.require('DungeonsAndDragonsCharacter') 2 | const fs = require('fs') 3 | 4 | const metadataTemple = { 5 | "name": "", 6 | "description": "", 7 | "image": "", 8 | "attributes": [ 9 | { 10 | "trait_type": "Strength", 11 | "value": 0 12 | }, 13 | { 14 | "trait_type": "Dexterity", 15 | "value": 0 16 | }, 17 | { 18 | "trait_type": "Constitution", 19 | "value": 0 20 | }, 21 | { 22 | "trait_type": "Intelligence", 23 | "value": 0 24 | }, 25 | { 26 | "trait_type": "Wisdom", 27 | "value": 0 28 | }, 29 | { 30 | "trait_type": "Charisma", 31 | "value": 0 32 | }, 33 | { 34 | "trait_type": "Experience", 35 | "value": 0 36 | } 37 | ] 38 | } 39 | module.exports = async callback => { 40 | const dnd = await DungeonsAndDragons.deployed() 41 | length = await dnd.getNumberOfCharacters() 42 | index = 0 43 | while (index < length) { 44 | console.log('Let\'s get the overview of your character ' + index + ' of ' + length) 45 | let characterMetadata = metadataTemple 46 | let characterOverview = await dnd.characters(index) 47 | index++ 48 | characterMetadata['name'] = characterOverview['name'] 49 | if (fs.existsSync('metadata/' + characterMetadata['name'].toLowerCase().replace(/\s/g, '-') + '.json')) { 50 | console.log('test') 51 | continue 52 | } 53 | console.log(characterMetadata['name']) 54 | characterMetadata['attributes'][0]['value'] = characterOverview['strength']['words'][0] 55 | characterMetadata['attributes'][1]['value'] = characterOverview['dexterity']['words'][0] 56 | characterMetadata['attributes'][2]['value'] = characterOverview['constitution']['words'][0] 57 | characterMetadata['attributes'][3]['value'] = characterOverview['intelligence']['words'][0] 58 | characterMetadata['attributes'][4]['value'] = characterOverview['wisdom']['words'][0] 59 | characterMetadata['attributes'][5]['value'] = characterOverview['charisma']['words'][0] 60 | filename = 'metadata/' + characterMetadata['name'].toLowerCase().replace(/\s/g, '-') 61 | let data = JSON.stringify(characterMetadata) 62 | fs.writeFileSync(filename + '.json', data) 63 | } 64 | callback(dnd) 65 | } 66 | -------------------------------------------------------------------------------- /scripts/environmentvar-test.js: -------------------------------------------------------------------------------- 1 | const DungeonsAndDragons = artifacts.require('DungeonsAndDragonsCharacter') 2 | 3 | module.exports = async callback => { 4 | console.log(process.env.DOG) 5 | callback('sup') 6 | } 7 | -------------------------------------------------------------------------------- /scripts/fund-contract.js: -------------------------------------------------------------------------------- 1 | const DungeonsAndDragonsCharacter = artifacts.require('DungeonsAndDragonsCharacter') 2 | const LinkTokenInterface = artifacts.require('LinkTokenInterface') 3 | 4 | /* 5 | This script is meant to assist with funding the requesting 6 | contract with LINK. It will send 1 LINK to the requesting 7 | contract for ease-of-use. Any extra LINK present on the contract 8 | can be retrieved by calling the withdrawLink() function. 9 | */ 10 | 11 | const payment = process.env.TRUFFLE_CL_BOX_PAYMENT || '3000000000000000000' 12 | 13 | module.exports = async callback => { 14 | try { 15 | const dnd = await DungeonsAndDragonsCharacter.deployed() 16 | 17 | const tokenAddress = await dnd.LinkToken() 18 | console.log("Chainlink Token Address: ", tokenAddress) 19 | const token = await LinkTokenInterface.at(tokenAddress) 20 | console.log('Funding contract:', dnd.address) 21 | const tx = await token.transfer(dnd.address, payment) 22 | callback(tx.tx) 23 | } catch (err) { 24 | callback(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/generate-character.js: -------------------------------------------------------------------------------- 1 | const DungeonsAndDragons = artifacts.require('DungeonsAndDragonsCharacter') 2 | 3 | module.exports = async callback => { 4 | const dnd = await DungeonsAndDragons.deployed() 5 | console.log('Creating requests on contract:', dnd.address) 6 | const tx = await dnd.requestNewRandomCharacter("The Chainlink Knight") 7 | const tx2 = await dnd.requestNewRandomCharacter("The Chainlink Elf") 8 | const tx3 = await dnd.requestNewRandomCharacter("The Chainlink Wizard") 9 | const tx4 = await dnd.requestNewRandomCharacter("The Chainlink Orc") 10 | callback(tx.tx) 11 | } 12 | -------------------------------------------------------------------------------- /scripts/get-character.js: -------------------------------------------------------------------------------- 1 | const DungeonsAndDragons = artifacts.require('DungeonsAndDragonsCharacter') 2 | 3 | module.exports = async callback => { 4 | const dnd = await DungeonsAndDragons.deployed() 5 | console.log('Let\'s get the overview of your character') 6 | const overview = await dnd.characters(0) 7 | console.log(overview) 8 | callback(overview.tx) 9 | } 10 | -------------------------------------------------------------------------------- /scripts/set-token-uri.js: -------------------------------------------------------------------------------- 1 | const DungeonsAndDragons = artifacts.require('DungeonsAndDragonsCharacter') 2 | const TOKENID = 0 3 | module.exports = async callback => { 4 | const dnd = await DungeonsAndDragons.deployed() 5 | console.log('Let\'s set the tokenURI of your characters') 6 | const tx = await dnd.setTokenURI(0, "https://ipfs.io/ipfs/QmaSED9ZSbdGts5UZqueFJjrJ4oHH3GnmGJdSDrkzpYqRS?filename=the-chainlink-knight.json") 7 | const tx1 = await dnd.setTokenURI(1, "https://ipfs.io/ipfs/QmTvsVaaHTuMNmwXgbfgkrztFEazAPyzmrb4VSS2PbqLjA?filename=the-chainlink-elf.json") 8 | const tx2 = await dnd.setTokenURI(2, "https://ipfs.io/ipfs/QmPZQhiBB6pwcxRgwZe2mx6ZizCPYgq8i4FBMETyWK1V2z?filename=the-chainlink-wizard.json") 9 | const tx3 = await dnd.setTokenURI(3, "https://ipfs.io/ipfs/QmS6aznzxshLMcECPQZjCR94UF22WHu6FMM5HLQvaYL9NP?filename=the-chainlink-orc.json") 10 | console.log(tx) 11 | callback(tx.tx) 12 | } 13 | -------------------------------------------------------------------------------- /test/DungeonsAndDragonsCharactors.test.js: -------------------------------------------------------------------------------- 1 | 2 | const { expectRevert } = require('@openzeppelin/test-helpers') 3 | 4 | const CHARACTER_NAME = "Shrek" 5 | 6 | contract('DungeonsAndDragonsCharacter', accounts => { 7 | const { LinkToken } = require('@chainlink/contracts/truffle/v0.4/LinkToken') 8 | const DungeonsAndDragonsCharacter = artifacts.require('DungeonsAndDragonsCharacter.sol') 9 | const defaultAccount = accounts[0] 10 | 11 | let link, dnd 12 | 13 | beforeEach(async () => { 14 | link = await LinkToken.new({ from: defaultAccount }) 15 | dnd = await DungeonsAndDragonsCharacter.new({ from: defaultAccount }) 16 | }) 17 | // TODO 18 | }) 19 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('@truffle/hdwallet-provider') 2 | require('dotenv').config() 3 | 4 | module.exports = { 5 | networks: { 6 | rinkeby: { 7 | provider: () => { 8 | return new HDWalletProvider(process.env.MNEMONIC, process.env.RINKEBY_RPC_URL) 9 | }, 10 | network_id: '4', 11 | skipDryRun: true, 12 | }, 13 | mainnet: { 14 | provider: () => { 15 | return new HDWalletProvider(process.env.MAINNET_MNEMONIC, process.env.MAINNET_RPC_URL) 16 | }, 17 | network_id: '1', 18 | skipDryRun: true, 19 | }, 20 | }, 21 | compilers: { 22 | solc: { 23 | version: '0.6.6', 24 | }, 25 | }, 26 | api_keys: { 27 | etherscan: process.env.ETHERSCAN_API_KEY 28 | }, 29 | plugins: [ 30 | 'truffle-plugin-verify' 31 | ] 32 | } 33 | --------------------------------------------------------------------------------