├── assets ├── arguments.js └── whitelist.json ├── .env ├── contracts ├── Jour2 │ └── ContratTest.sol ├── Jour3 │ └── HelloWorld.sol ├── solutions │ ├── HelloWorldSolution.sol │ ├── Day4Solution.sol │ ├── NekrTokenIsERC20.sol │ ├── BlogSolution.sol │ ├── Staking.sol │ └── NekrIsERC721.sol ├── Jour10 │ └── NekrIsERC721_Jour10.sol ├── Jour4 │ └── Day4.sol ├── Jour20 │ └── Staking_Jour20.sol ├── Jour6 │ └── Blog.sol ├── Jour11 │ └── NekrIsERC721_Jour11.sol ├── Jour21 │ └── Staking_Jour21.sol ├── Jour12 │ └── NekrIsERC721_Jour12.sol ├── Jour13 │ └── NekrIsERC721_Jour13.sol ├── Jour22 │ └── Staking_Jour22.sol ├── Jour14 │ └── NekrIsERC721_Jour14.sol └── Jour23 │ └── Staking_Jour23.sol ├── .gitignore ├── tsconfig.json ├── scripts ├── NekrTokenIsERC20.deploy.ts ├── GetMerkleRoot.ts ├── HelloWorld.deploy.ts ├── GenerateMerkle.ts └── FullContract.deploy.ts ├── test ├── Jour3 │ └── HelloWorld.test.ts ├── Jour4 │ └── Day4.test.ts └── Jour6 │ └── Blog.test.ts ├── hardhat.config.ts ├── README.md └── package.json /assets/arguments.js: -------------------------------------------------------------------------------- 1 | module.exports = [] 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY=YOUR_WALLET_PRIVATE_KEY 2 | MUMBAI_PRIVATE_KEY=YOUR_MUMBAI_PRIVATE_KEY 3 | MUMBAI_URL=https://matic-mumbai.chainstacklabs.com 4 | -------------------------------------------------------------------------------- /contracts/Jour2/ContratTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.23; 3 | 4 | contract ContratTest { 5 | 6 | constructor() {} 7 | 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | coverage.json 4 | typechain 5 | 6 | #Hardhat files 7 | cache 8 | artifacts 9 | .idea 10 | src/ 11 | 12 | src/contracts/NekrIsERC721.sol 13 | yarn-error.log 14 | -------------------------------------------------------------------------------- /assets/whitelist.json: -------------------------------------------------------------------------------- 1 | [{"address": "0xBcD09dfEc85285eE39D7CbF195fc973CdE2213c2"}, {"address": "0xF096D4e0C02E4115aec303C656BA4b33880aB0e9"}, {"address": "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8"}, {"address": "0x1b3cb81e51011b549d78bf720b0d924ac763a7c2"}] 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "sourceMap": true, 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ], 11 | "include": [ 12 | "test", "src", "scripts" 13 | ], 14 | "files": ["hardhat.config.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /contracts/Jour3/HelloWorld.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | contract HelloWorld { 5 | 6 | constructor() {} 7 | 8 | // Créez une fonction qui renvoie "Hello World !" lorsque vous appelez la fonction 9 | function helloWorld() public pure returns (string memory) { 10 | // CODE HERE 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/solutions/HelloWorldSolution.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | contract HelloWorldSolution { 5 | 6 | constructor() {} 7 | 8 | // Créez une fonction qui renvoie "Hello World !" lorsque vous appelez la fonction 9 | function helloWorld() public pure returns (string memory) { 10 | return "Hello World !"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scripts/NekrTokenIsERC20.deploy.ts: -------------------------------------------------------------------------------- 1 | 2 | async function main() { 3 | const contract = await ethers.getContractFactory("NekrTokenIsERC20"); 4 | const Contract = await contract.deploy(); 5 | console.log("Deploying contract..."); 6 | await Contract.deployed(); 7 | console.log("Contract deployed to:", Contract.address); 8 | console.log("yarn hardhat verify --network polygonMumbai", Contract.address); // verify the contract 9 | } 10 | 11 | main().then(); 12 | -------------------------------------------------------------------------------- /contracts/Jour10/NekrIsERC721_Jour10.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 8 | 9 | contract NekrIsERC721_Jour10 is ERC721, Ownable { 10 | 11 | constructor() ERC721 ('', '') Ownable(msg.sender) {} 12 | 13 | } 14 | -------------------------------------------------------------------------------- /scripts/GetMerkleRoot.ts: -------------------------------------------------------------------------------- 1 | const { MerkleTree } = require('merkletreejs'); 2 | const keccak256 = require('keccak256'); 3 | const whitelist = require('../assets/whitelist.json'); 4 | 5 | export function GetMerkleRoot() { 6 | let wlTab = []; 7 | whitelist.map(a => { 8 | wlTab.push(a.address); 9 | }) 10 | const leaves = wlTab.map(a => keccak256(a)); 11 | const tree = new MerkleTree(leaves, keccak256, { sort: true }); 12 | return tree.getHexRoot(); 13 | } 14 | 15 | GetMerkleRoot(); 16 | -------------------------------------------------------------------------------- /test/Jour3/HelloWorld.test.ts: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | 3 | describe("Test HelloWorld", async () => { 4 | 5 | let contract; 6 | 7 | before(async () => { 8 | const Contract = await ethers.getContractFactory("HelloWorld"); 9 | contract = await Contract.deploy(); 10 | }) 11 | 12 | // @dev Test de la fonction 13 | it("La fonction 'helloWorld()' doit renvoyer 'Hello World !'", async () => { 14 | const helloWorld = await contract.helloWorld(); 15 | expect(helloWorld).to.equal("Hello World !"); 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /contracts/Jour4/Day4.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | // IMPORTER OWNABLE DE OPENZEPPELIN 5 | 6 | /* 7 | contract Day4 is Ownable { 8 | 9 | constructor() {} 10 | 11 | function retournerLeNumeroDuJour() public pure returns (COMPLETE HERE) { 12 | // CODE HERE 13 | } 14 | 15 | function retournerLeNomDuJour() public pure returns (COMPLETE HERE) { 16 | // CODE HERE 17 | } 18 | 19 | function retounerLadresseDuContrat () public view returns (COMPLETE HERE) { 20 | // CODE HERE 21 | } 22 | } 23 | */ 24 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import {HardhatUserConfig} from "hardhat/config"; 3 | import "@nomicfoundation/hardhat-toolbox"; 4 | 5 | dotenv.config(); 6 | 7 | const config: HardhatUserConfig = { 8 | solidity: "0.8.23", 9 | networks: { 10 | polygonMumbai: { 11 | url: process.env.MUMBAI_URL!, 12 | accounts: [process.env.WALLET_PRIVATE_KEY!], 13 | } 14 | }, 15 | etherscan: { 16 | apiKey: { 17 | polygonMumbai: process.env.MUMBAI_PRIVATE_KEY!, 18 | }, 19 | },paths: { 20 | artifacts: "./artifacts" 21 | } 22 | }; 23 | 24 | export default config; 25 | -------------------------------------------------------------------------------- /contracts/solutions/Day4Solution.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | 6 | contract Day4Solution is Ownable { 7 | 8 | constructor() Ownable(msg.sender) { 9 | 10 | } 11 | 12 | function retournerLeNumeroDuJour() public pure returns (uint) { 13 | return 4; 14 | } 15 | 16 | function retournerLeNomDuJour() public pure returns (string memory) { 17 | return "Dimanche"; 18 | } 19 | 20 | function retounerLadresseDuContrat () public view returns (address) { 21 | return address(this); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /scripts/HelloWorld.deploy.ts: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | async function HelloWorldDeploy() { 4 | // COMMANDE À LANCER : yarn hardhat run .\scripts\HelloWorld.deploy.ts --network polygonMumbai 5 | 6 | // On récupère le contrat via son nom 7 | const contract = await ethers.getContractFactory("HelloWorld"); 8 | // On le déploie 9 | const Contract = await contract.deploy(); 10 | console.log("Deploying contract..."); 11 | // On attend que le contrat soit déployé 12 | await Contract.deployed(); 13 | console.log("Contract deployed to:", Contract.address); 14 | // Commande pour vérifier le contrat déployé sur l'explorateur de blocs 15 | console.log("hardhat verify --network polygonMumbai", Contract.address); // verify the contract 16 | } 17 | 18 | HelloWorldDeploy().then(); 19 | -------------------------------------------------------------------------------- /contracts/Jour20/Staking_Jour20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "../solutions/NekrIsERC721.sol"; 5 | import "../solutions/NekrTokenIsERC20.sol"; 6 | 7 | contract Staking_Jour20 { 8 | 9 | NekrTokenIsERC20 token; 10 | NekrIsERC721 nft; 11 | 12 | uint public totalStaked; 13 | 14 | struct StakeStruct { 15 | uint tokenId; 16 | uint stakingStartTime; 17 | address owner; 18 | } 19 | 20 | mapping (uint => StakeStruct) public StructByID; 21 | 22 | uint rewardsPerHour = 500000000000000000; 23 | 24 | event Staked(address indexed owner, uint tokenId, uint value); 25 | event UnStaked(address indexed owner, uint tokenId, uint value); 26 | event Claimed(address indexed owner, uint amount); 27 | 28 | constructor(NekrTokenIsERC20 _token, NekrIsERC721 _nft) { 29 | token = _token; 30 | nft = _nft; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Calendrier de l'avent 2022 3 | 4 | **Bienvenue** dans mon calendrier de l'avent **Solidity** ! 5 | 6 | Vous pouvez retrouver mes threads sur mon Twitter : [@0xNekr](https://twitter.com/0xNekr) 7 | 8 | Le but est de vous initier au développement de contrat intelligent, nous allons donc apprendre les bases tous ensemble. 9 | 10 | En plus des tweets, je vous redirigerai vers des ressources que j'estime importantes ! 11 | 12 | --- 13 | 14 | Bien sûr, vous allez être acteur de ce calendrier, car la pratique est la meilleure façon d'apprendre selon moi. 15 | 16 | À la fin de ces 25 jours, vous aurez créé un contrat de NFT, un Token basique et un contrat qui permet de staker votre NFT pour récupérer des tokens ! 17 | 18 | Sympa, non ? 19 | 20 | --- 21 | 22 | ### Disclaimer : 23 | 24 | Après ces 25 jours, il vous restera énormément de choses à apprendre, mais vous aurez fait une belle première expérience dans le développement qui vous permettra de continuer sur des sujets plus poussés ! 25 | -------------------------------------------------------------------------------- /contracts/Jour6/Blog.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | contract Blog { 5 | 6 | // @dev : Une structure contenant les infos de l'article avec comme noms (id, titre, texte, auteur et timestamp); 7 | 8 | // @dev : Un mapping contenant les articles par id avec comme nom (articles) 9 | 10 | // @dev : Une variable (id) qui peut être récupérée 11 | 12 | // @dev : Une variable (auteur) qui peut être récupérée 13 | 14 | // @dev : Un événement (NouvelArticle) avec toutes les informations de l'article 15 | 16 | // @dev : Le créateur du contrat doit devenir auteur à la création 17 | constructor() { 18 | 19 | } 20 | 21 | // @dev : Une fonction (modifierAuteur) qui permet de modifier l'auteur du blog, seulement par l'auteur actuel 22 | 23 | // @dev : Une fonction (creerArticle) qui permet d'ajouter un article, seulement par l'auteur actuel 24 | // elle doit émettre un événement NouvelArticle avec toutes les informations de l'article 25 | } 26 | -------------------------------------------------------------------------------- /contracts/solutions/NekrTokenIsERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | contract NekrTokenIsERC20 is ERC20, Ownable { 8 | 9 | mapping(address => bool) admins; 10 | uint256 public maxSupply = 1000000000 * 10**18; 11 | 12 | constructor() ERC20("Nekr Token", "NKTK") Ownable(msg.sender) { 13 | admins[msg.sender] = true; 14 | } 15 | 16 | function mint(address _to, uint _amount) external { 17 | require(admins[msg.sender], "You can't mint, you are not admin"); 18 | require(totalSupply() + _amount <= maxSupply, "Max supply exceeded"); 19 | _mint(_to, _amount); 20 | } 21 | 22 | function addAdmin(address _admin) external onlyOwner { 23 | admins[_admin] = true; 24 | } 25 | 26 | function removeAdmin(address _admin) external onlyOwner { 27 | admins[_admin] = false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/Jour11/NekrIsERC721_Jour11.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 8 | 9 | 10 | contract NekrIsERC721_Jour11 is ERC721, Ownable { 11 | 12 | using Strings for uint; 13 | 14 | uint256 private _tokenIds; 15 | 16 | enum Step { 17 | SaleNotStarted, 18 | WhitelistSale, 19 | PublicSale, 20 | SoldOut 21 | } 22 | 23 | Step public currentStep; 24 | 25 | bytes32 public merkleRoot; 26 | 27 | uint public constant maxSupply = 20; 28 | uint public constant maxWhitelist = 10; 29 | 30 | uint public whitelistPrice = 0.5 ether; 31 | uint public publicPrice = 1 ether; 32 | 33 | mapping(address => uint) public amountMintedByAddress; 34 | 35 | string public baseTokenURI; 36 | 37 | constructor() ERC721("", "") Ownable(msg.sender) { 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /contracts/solutions/BlogSolution.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | contract BlogSolution { 5 | 6 | struct Article { 7 | uint id; 8 | string titre; 9 | string texte; 10 | address auteur; 11 | uint timestamp; 12 | } 13 | 14 | mapping(uint => Article) public articles; 15 | 16 | uint public id = 0; 17 | 18 | address public auteur; 19 | 20 | event NouvelArticle(uint id, string titre, string texte, address auteur, uint timestamp); 21 | 22 | constructor() { 23 | auteur = msg.sender; 24 | } 25 | 26 | function modifierAuteur(address _auteur) public { 27 | require(msg.sender == auteur, "Vous n'etes pas l'auteur"); 28 | auteur = _auteur; 29 | } 30 | 31 | function creerArticle(string memory _titre, string memory _texte) public { 32 | id++; 33 | require(msg.sender == auteur, "Vous n'etes pas l'auteur"); 34 | articles[id] = Article(id, _titre, _texte, msg.sender, block.timestamp); 35 | emit NouvelArticle(id, _titre, _texte, msg.sender, block.timestamp); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /scripts/GenerateMerkle.ts: -------------------------------------------------------------------------------- 1 | const { MerkleTree } = require('merkletreejs'); 2 | const keccak256 = require('keccak256'); 3 | const whitelist = require('../assets/whitelist.json'); 4 | 5 | function GenerateMerkle() { 6 | let wlTab = []; // Tableau des adresses de la whitelist 7 | 8 | // Itération sur toutes les adresses de la whitelist pour les ajouter au tableau 9 | whitelist.map(a => { 10 | wlTab.push(a.address); 11 | }) 12 | 13 | // Création des feuilles de l'arbre de merkle 14 | const leaves = wlTab.map(a => keccak256(a)); 15 | 16 | // Création de l'arbre de merkle 17 | const tree = new MerkleTree(leaves, keccak256, { sort: true }); 18 | 19 | // Récupération de la racine de l'arbre de merkle 20 | const root = tree.getHexRoot(); 21 | console.log("Whitelist root :", root); 22 | 23 | const addressToCheck = "0xBcD09dfEc85285eE39D7CbF195fc973CdE2213c2"; 24 | // Vérification de l'existence d'une adresse dans l'arbre de merkle 25 | const proof = tree.getHexProof(keccak256(addressToCheck)); 26 | console.log('Merkle proof for', addressToCheck, ':', proof); 27 | } 28 | 29 | GenerateMerkle(); 30 | 31 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@chainlink/contracts": "^0.4.1", 4 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.4", 5 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 6 | "@openzeppelin/contracts": "^5.0.1", 7 | "hardhat": "^2.9.9", 8 | "hardhat-deploy-ethers": "^0.3.0-beta.13", 9 | "keccak": "^3.0.2", 10 | "keccak256": "^1.0.6", 11 | "merkletreejs": "^0.2.32", 12 | "tslint": "^6.1.3" 13 | }, 14 | "devDependencies": { 15 | "@nomiclabs/hardhat-ethers": "^2.0.0", 16 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 17 | "@typechain/ethers-v5": "^7.0.1", 18 | "@typechain/hardhat": "^2.3.0", 19 | "@types/chai": "^4.2.21", 20 | "@types/mocha": "^9.0.0", 21 | "@types/node": "^12.0.0", 22 | "@typescript-eslint/eslint-plugin": "^4.29.1", 23 | "@typescript-eslint/parser": "^4.29.1", 24 | "chai": "^4.3.6", 25 | "dotenv": "^16.0.0", 26 | "eslint": "^7.29.0", 27 | "eslint-config-prettier": "^8.3.0", 28 | "eslint-config-standard": "^16.0.3", 29 | "eslint-plugin-import": "^2.23.4", 30 | "eslint-plugin-node": "^11.1.0", 31 | "eslint-plugin-prettier": "^3.4.0", 32 | "eslint-plugin-promise": "^5.1.0", 33 | "ethers": "^5.6.9", 34 | "hardhat-gas-reporter": "^1.0.4", 35 | "prettier": "^2.3.2", 36 | "prettier-plugin-solidity": "^1.0.0-beta.13", 37 | "solhint": "^3.3.6", 38 | "solidity-coverage": "^0.7.16", 39 | "ts-node": "^10.1.0", 40 | "typechain": "^5.1.2", 41 | "typescript": "^4.7.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/Jour4/Day4.test.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai"; 2 | import {ethers} from "hardhat"; 3 | 4 | describe("Test Day4", async () => { 5 | 6 | let contract; 7 | let deployer; 8 | 9 | before(async () => { 10 | [deployer] = await ethers.getSigners(); 11 | const Contract = await ethers.getContractFactory("Day4"); 12 | contract = await Contract.deploy(); 13 | }) 14 | 15 | // @dev Test de l'import de Ownable 16 | it("'owner' doit retourner une adresse si Ownable est correctement importé.", async () => { 17 | expect(await contract.owner()).to.equal(deployer.address); 18 | }) 19 | 20 | // @dev Test de la fonction retournerLeNumeroDuJour() 21 | it('retournerLeNumeroDuJour() doit renvoyer le numéro du jour (4)', async () => { 22 | const numeroDuJour = await contract.retournerLeNumeroDuJour(); 23 | expect(numeroDuJour.toNumber()).to.equal(4); 24 | }); 25 | 26 | // @dev Test de la fonction retournerLeNomDuJour() 27 | it('retournerLeNomDuJour() doit renvoyer le nom du jour (Dimanche)', async () => { 28 | const nomDuJour = await contract.retournerLeNomDuJour(); 29 | expect(nomDuJour).to.equal("Dimanche"); 30 | }); 31 | 32 | // @dev Test de la fonction retounerLadresseDuContrat() 33 | it('retounerLadresseDuContrat() doit renvoyer l\'adresse du contract', async () => { 34 | const adresseDuContract = await contract.retounerLadresseDuContrat(); 35 | expect(adresseDuContract).to.equal(contract.address); 36 | }); 37 | }) 38 | -------------------------------------------------------------------------------- /contracts/Jour21/Staking_Jour21.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "../solutions/NekrIsERC721.sol"; 5 | import "../solutions/NekrTokenIsERC20.sol"; 6 | 7 | contract Staking_Jour21 { 8 | 9 | NekrTokenIsERC20 token; 10 | NekrIsERC721 nft; 11 | 12 | uint public totalStaked; 13 | 14 | struct StakeStruct { 15 | uint tokenId; 16 | uint stakingStartTime; 17 | address owner; 18 | } 19 | 20 | mapping (uint => StakeStruct) public StructByID; 21 | 22 | uint rewardsPerHour = 500000000000000000; 23 | 24 | event Staked(address indexed owner, uint tokenId, uint value); 25 | event UnStaked(address indexed owner, uint tokenId, uint value); 26 | event Claimed(address indexed owner, uint amount); 27 | 28 | constructor(NekrTokenIsERC20 _token, NekrIsERC721 _nft) { 29 | token = _token; 30 | nft = _nft; 31 | } 32 | 33 | function stake(uint[] calldata tokenIds) external { 34 | for (uint i = 0 ; i < tokenIds.length ; i++) { 35 | require(nft.ownerOf(tokenIds[i]) == msg.sender, "Not the owner"); 36 | require(StructByID[tokenIds[i]].stakingStartTime == 0, "Already staked"); 37 | 38 | nft.transferFrom(msg.sender, address(this), tokenIds[i]); 39 | emit Staked(msg.sender, tokenIds[i], block.timestamp); 40 | 41 | StructByID[tokenIds[i]] = StakeStruct({ 42 | tokenId: tokenIds[i], 43 | stakingStartTime: block.timestamp, 44 | owner: msg.sender 45 | }); 46 | } 47 | 48 | totalStaked += tokenIds.length; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /contracts/Jour12/NekrIsERC721_Jour12.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 8 | 9 | 10 | contract NekrIsERC721_Jour12 is ERC721, Ownable { 11 | 12 | using Strings for uint; 13 | 14 | uint256 private _tokenIds; 15 | 16 | enum Step { 17 | SaleNotStarted, 18 | WhitelistSale, 19 | PublicSale, 20 | SoldOut 21 | } 22 | 23 | Step public currentStep; 24 | 25 | bytes32 public merkleRoot; 26 | 27 | uint public constant maxSupply = 20; 28 | uint public constant maxWhitelist = 10; 29 | 30 | uint public whitelistPrice = 0.5 ether; 31 | uint public publicPrice = 1 ether; 32 | 33 | mapping(address => uint) public amountMintedByAddress; 34 | 35 | string public baseTokenURI; 36 | 37 | event newMint(address indexed sender, uint256 amount); 38 | event stepUpdated(Step currentStep); 39 | 40 | constructor(string memory _baseTokenURI, bytes32 _merkleRoot) 41 | ERC721("Calendar Collection", "CALCO") 42 | Ownable(msg.sender) 43 | { 44 | baseTokenURI = _baseTokenURI; 45 | merkleRoot = _merkleRoot; 46 | } 47 | 48 | function withdraw() external onlyOwner { 49 | require(address(this).balance > 0, "Nothing to withdraw"); 50 | payable(msg.sender).transfer(address(this).balance); 51 | } 52 | 53 | function updatePublicPrice(uint _newPrice) external onlyOwner { 54 | publicPrice = _newPrice; 55 | } 56 | 57 | function updateWhitelistPrice(uint _newPrice) external onlyOwner { 58 | whitelistPrice = _newPrice; 59 | } 60 | 61 | function getCurrentPrice() public view returns (uint) { 62 | if (currentStep == Step.WhitelistSale) { 63 | return whitelistPrice; 64 | } else { 65 | return publicPrice; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /contracts/Jour13/NekrIsERC721_Jour13.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 8 | 9 | 10 | contract NekrIsERC721_Jour13 is ERC721, Ownable { 11 | 12 | using Strings for uint; 13 | 14 | uint256 private _tokenIds; 15 | 16 | enum Step { 17 | SaleNotStarted, 18 | WhitelistSale, 19 | PublicSale, 20 | SoldOut 21 | } 22 | 23 | Step public currentStep; 24 | 25 | bytes32 public merkleRoot; 26 | 27 | uint public constant maxSupply = 20; 28 | uint public constant maxWhitelist = 10; 29 | 30 | uint public whitelistPrice = 0.5 ether; 31 | uint public publicPrice = 1 ether; 32 | 33 | mapping(address => uint) public amountMintedByAddress; 34 | 35 | string public baseTokenURI; 36 | 37 | event newMint(address indexed sender, uint256 amount); 38 | event stepUpdated(Step currentStep); 39 | 40 | constructor(string memory _baseTokenURI, bytes32 _merkleRoot) 41 | ERC721("Calendar Collection", "CALCO") 42 | Ownable(msg.sender) 43 | { 44 | baseTokenURI = _baseTokenURI; 45 | merkleRoot = _merkleRoot; 46 | } 47 | 48 | function withdraw() external onlyOwner { 49 | require(address(this).balance > 0, "Nothing to withdraw"); 50 | payable(msg.sender).transfer(address(this).balance); 51 | } 52 | 53 | function updatePublicPrice(uint _newPrice) external onlyOwner { 54 | publicPrice = _newPrice; 55 | } 56 | 57 | function updateWhitelistPrice(uint _newPrice) external onlyOwner { 58 | whitelistPrice = _newPrice; 59 | } 60 | 61 | function getCurrentPrice() public view returns (uint) { 62 | if (currentStep == Step.WhitelistSale) { 63 | return whitelistPrice; 64 | } else { 65 | return publicPrice; 66 | } 67 | } 68 | 69 | function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner { 70 | merkleRoot = _merkleRoot; 71 | } 72 | 73 | function isWhitelisted(address _account, bytes32[] calldata proof) public view returns(bool) { 74 | return _verify(_leaf(_account), proof); 75 | } 76 | 77 | function _leaf(address _account) internal pure returns(bytes32) { 78 | return keccak256(abi.encodePacked(_account)); 79 | } 80 | 81 | function _verify(bytes32 leaf, bytes32[] memory proof) internal view returns(bool) { 82 | return MerkleProof.verify(proof, merkleRoot, leaf); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /contracts/Jour22/Staking_Jour22.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "../solutions/NekrIsERC721.sol"; 5 | import "../solutions/NekrTokenIsERC20.sol"; 6 | 7 | contract Staking_Jour22 { 8 | 9 | 10 | NekrTokenIsERC20 token; 11 | NekrIsERC721 nft; 12 | 13 | uint public totalStaked; 14 | 15 | struct StakeStruct { 16 | uint tokenId; 17 | uint stakingStartTime; 18 | address owner; 19 | } 20 | 21 | mapping (uint => StakeStruct) public StructByID; 22 | 23 | uint rewardsPerHour = 500000000000000000; 24 | 25 | event Staked(address indexed owner, uint tokenId, uint value); 26 | event UnStaked(address indexed owner, uint tokenId, uint value); 27 | event Claimed(address indexed owner, uint amount); 28 | 29 | constructor(NekrTokenIsERC20 _token, NekrIsERC721 _nft) { 30 | token = _token; 31 | nft = _nft; 32 | } 33 | 34 | function stake(uint[] calldata tokenIds) external { 35 | for (uint i = 0 ; i < tokenIds.length ; i++) { 36 | require(nft.ownerOf(tokenIds[i]) == msg.sender, "Not the owner"); 37 | require(StructByID[tokenIds[i]].stakingStartTime == 0, "Already staked"); 38 | 39 | nft.transferFrom(msg.sender, address(this), tokenIds[i]); 40 | emit Staked(msg.sender, tokenIds[i], block.timestamp); 41 | 42 | StructByID[tokenIds[i]] = StakeStruct({ 43 | tokenId: tokenIds[i], 44 | stakingStartTime: block.timestamp, 45 | owner: msg.sender 46 | }); 47 | } 48 | 49 | totalStaked += tokenIds.length; 50 | } 51 | 52 | function getRewards(address owner, uint[] calldata tokenIds) external view returns(uint) { 53 | uint totalEarned; 54 | 55 | for (uint i = 0 ; i < tokenIds.length ; i++) { 56 | require(StructByID[tokenIds[i]].owner == owner, "All owners are not the same"); 57 | 58 | uint stakingStartTime = StructByID[tokenIds[i]].stakingStartTime; 59 | totalEarned += (block.timestamp - stakingStartTime) * rewardsPerHour / 3600; 60 | } 61 | 62 | return totalEarned; 63 | } 64 | 65 | function claim(uint[] calldata tokenIds) external { 66 | _claim(msg.sender, tokenIds, false); 67 | } 68 | 69 | function _claim(address _owner, uint[] calldata _tokenIds, bool _unstake) internal { 70 | uint totalEarned; 71 | 72 | for (uint i = 0 ; i < _tokenIds.length ; i++) { 73 | require(StructByID[_tokenIds[i]].owner == _owner, "Not the owner, you cannot claim the awards"); 74 | 75 | uint stakingStartTime = StructByID[_tokenIds[i]].stakingStartTime; 76 | totalEarned += (block.timestamp - stakingStartTime) * rewardsPerHour / 3600; 77 | 78 | StructByID[_tokenIds[i]] = StakeStruct({ 79 | tokenId: _tokenIds[i], 80 | stakingStartTime: block.timestamp, 81 | owner: _owner 82 | }); 83 | } 84 | 85 | if (totalEarned > 0) { 86 | token.mint(_owner, totalEarned); 87 | } 88 | 89 | if (_unstake) { 90 | // _unstakeNFT(_owner, _tokenIds); 91 | } 92 | 93 | emit Claimed(_owner, totalEarned); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /test/Jour6/Blog.test.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai"; 2 | import {ethers} from "hardhat"; 3 | 4 | describe("Test Blog", async () => { 5 | 6 | let contract; 7 | let deployer; 8 | let nouvelAuteur 9 | 10 | before(async () => { 11 | [deployer, nouvelAuteur] = await ethers.getSigners(); 12 | const Contract = await ethers.getContractFactory("Blog"); 13 | contract = await Contract.deploy(); 14 | }) 15 | 16 | // @dev A la création du contrat, votre adresse doit être enregistrer en tant qu’auteur 17 | it("L'adresse qui déploie le contrat doit être enregistrée en tant qu'auteur", async () => { 18 | const auteur = await contract.auteur(); 19 | expect(auteur).to.equal(deployer.address); 20 | }); 21 | 22 | // @dev Il n’y a que l’auteur qui peut ajouter un article 23 | it("Seul l'auteur peut ajouter un article", async () => { 24 | const article = "Mon premier article"; 25 | const texte = "Mon premier texte"; 26 | await expect(contract.connect(nouvelAuteur).creerArticle(article, texte)).to.be.reverted; 27 | }); 28 | 29 | // @dev L'auteur peut ajouter un article 30 | it("L'auteur peut ajouter un article et un event est envoyé", async () => { 31 | const article = "Mon premier article"; 32 | const texte = "Mon premier texte"; 33 | await expect(contract.creerArticle(article, texte)).to.emit(contract, "NouvelArticle"); 34 | }); 35 | 36 | // @dev Quelqu'un qui n'est pas auteur ne doit pas pouvoir modifier l'auteur actuel 37 | it("Seul l'auteur peut modifier l'auteur actuel", async () => { 38 | await expect(contract.connect(nouvelAuteur).modifierAuteur(nouvelAuteur.address)).to.be.reverted; 39 | await contract.connect(deployer).modifierAuteur(nouvelAuteur.address); 40 | const auteur = await contract.auteur(); 41 | expect(auteur).to.equal(nouvelAuteur.address); 42 | }); 43 | 44 | // @dev Il est possible de récupérer un article via son ID 45 | it("Il est possible de récupérer un article via son ID", async () => { 46 | const premierArticle = await contract.articles(1); 47 | expect(premierArticle.id).to.equal(1); 48 | expect(premierArticle.titre).to.equal("Mon premier article"); 49 | expect(premierArticle.texte).to.equal("Mon premier texte"); 50 | }); 51 | 52 | // @dev Il est possible de récupérer la liste des articles via une boucle off-chain grâce à l'id. 53 | it("Il est possible de récupérer la liste des articles via une boucle off-chain grâce à l'id", async () => { 54 | for (let i = 2; i <= 4; i++) { 55 | await contract.connect(nouvelAuteur).creerArticle("Mon article " + i, "Mon texte " + i); 56 | } 57 | 58 | const NombreArticles = await contract.id(); 59 | for (let i = 1; i <= NombreArticles.toNumber(); i++) { 60 | const article = await contract.articles(i); 61 | if (i === 1) { 62 | expect(article.titre).to.equal("Mon premier article"); 63 | expect(article.texte).to.equal("Mon premier texte"); 64 | } else { 65 | expect(article.id).to.equal(i); 66 | expect(article.titre).to.equal("Mon article " + i); 67 | expect(article.texte).to.equal("Mon texte " + i); 68 | } 69 | } 70 | }); 71 | }) 72 | -------------------------------------------------------------------------------- /contracts/Jour14/NekrIsERC721_Jour14.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 8 | 9 | 10 | contract NekrIsERC721_Jour14 is ERC721, Ownable { 11 | 12 | using Strings for uint; 13 | 14 | uint256 private _tokenIds; 15 | 16 | enum Step { 17 | SaleNotStarted, 18 | WhitelistSale, 19 | PublicSale, 20 | SoldOut 21 | } 22 | 23 | Step public currentStep; 24 | 25 | bytes32 public merkleRoot; 26 | 27 | uint public constant maxSupply = 20; 28 | uint public constant maxWhitelist = 10; 29 | 30 | uint public whitelistPrice = 0.5 ether; 31 | uint public publicPrice = 1 ether; 32 | 33 | mapping(address => uint) public amountMintedByAddress; 34 | 35 | string public baseTokenURI; 36 | 37 | bool public metadataFrozen = false; 38 | 39 | event newMint(address indexed sender, uint256 amount); 40 | event stepUpdated(Step currentStep); 41 | 42 | constructor(string memory _baseTokenURI, bytes32 _merkleRoot) 43 | ERC721("Calendar Collection", "CALCO") 44 | Ownable(msg.sender) 45 | { 46 | baseTokenURI = _baseTokenURI; 47 | merkleRoot = _merkleRoot; 48 | } 49 | 50 | function setBaseURI(string memory _baseTokenURI) public onlyOwner { 51 | require(!metadataFrozen, "Metadata are frozen"); 52 | baseTokenURI = _baseTokenURI; 53 | } 54 | 55 | function freezeMetadata() public onlyOwner { 56 | metadataFrozen = true; 57 | } 58 | 59 | function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) { 60 | _requireOwned(_tokenId); 61 | return string(abi.encodePacked(baseTokenURI, _tokenId.toString())); 62 | } 63 | 64 | function withdraw() external onlyOwner { 65 | require(address(this).balance > 0, "Nothing to withdraw"); 66 | payable(msg.sender).transfer(address(this).balance); 67 | } 68 | 69 | function updatePublicPrice(uint _newPrice) external onlyOwner { 70 | publicPrice = _newPrice; 71 | } 72 | 73 | function updateWhitelistPrice(uint _newPrice) external onlyOwner { 74 | whitelistPrice = _newPrice; 75 | } 76 | 77 | function getCurrentPrice() public view returns (uint) { 78 | if (currentStep == Step.WhitelistSale) { 79 | return whitelistPrice; 80 | } else { 81 | return publicPrice; 82 | } 83 | } 84 | 85 | function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner { 86 | merkleRoot = _merkleRoot; 87 | } 88 | 89 | function isWhitelisted(address _account, bytes32[] calldata proof) public view returns(bool) { 90 | return _verify(_leaf(_account), proof); 91 | } 92 | 93 | function _leaf(address _account) internal pure returns(bytes32) { 94 | return keccak256(abi.encodePacked(_account)); 95 | } 96 | 97 | function _verify(bytes32 leaf, bytes32[] memory proof) internal view returns(bool) { 98 | return MerkleProof.verify(proof, merkleRoot, leaf); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /scripts/FullContract.deploy.ts: -------------------------------------------------------------------------------- 1 | import {run} from "hardhat"; 2 | import {GetMerkleRoot} from "./GetMerkleRoot"; 3 | 4 | async function FullContract() { 5 | 6 | // Récupération de tous les contrats 7 | const token = await ethers.getContractFactory("NekrTokenIsERC20"); 8 | const nft = await ethers.getContractFactory("NekrIsERC721"); 9 | const staking = await ethers.getContractFactory("Staking"); 10 | 11 | // Définition de l'URI IPFS : 12 | const uri = 'ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/'; 13 | 14 | /* 15 | * Voici comment doit se dérouler le déploiement : 16 | * 1 - Déploiement du token ERC20 & ERC721 17 | * 2 - Déploiement du staking avec les adresses des contrats précédents 18 | * 3 - Ajout des droits de minter sur le token ERC20 pour le staking (addAdmin) 19 | */ 20 | 21 | console.log("Deploying token..."); 22 | const Token = await token.deploy(); 23 | await Token.deployed(); 24 | console.log("Token deployed to:", Token.address); 25 | console.log("Waiting 5 seconds before verifying the token... (to avoid errors)"); 26 | await delay(5000); 27 | console.log("Verify token contract...") 28 | try { 29 | await run(`verify:verify`, { 30 | address: Token.address, 31 | constructorArguments: [], 32 | }) 33 | console.log("Token verified !") 34 | } catch (error) { 35 | console.log("Already verified"); 36 | } 37 | 38 | console.log("Get merkle root..."); 39 | const merkleRoot = GetMerkleRoot(); 40 | console.log("Whitelist root :", merkleRoot); 41 | 42 | console.log("Deploying NFT..."); 43 | const NFT = await nft.deploy( 44 | uri, 45 | merkleRoot 46 | ); 47 | await NFT.deployed(); 48 | console.log("NFT deployed to:", NFT.address); 49 | console.log("Waiting 5 seconds before verifying the NFT... (to avoid errors)"); 50 | await delay(5000); 51 | console.log("Verify NFT contract...") 52 | try { 53 | await run(`verify:verify`, { 54 | address: NFT.address, 55 | constructorArguments: [ 56 | uri, 57 | merkleRoot 58 | ], 59 | }) 60 | console.log("NFT verified !") 61 | } catch (error) { 62 | console.log("Already verified"); 63 | } 64 | 65 | console.log("Deploying staking..."); 66 | const Staking = await staking.deploy(Token.address, NFT.address); 67 | await Staking.deployed(); 68 | console.log("Staking deployed to:", Staking.address); 69 | 70 | console.log("Waiting 5 seconds before verifying the staking... (to avoid errors)"); 71 | await delay(5000); 72 | console.log("Verify staking contract...") 73 | try { 74 | await run(`verify:verify`, { 75 | address: Staking.address, 76 | constructorArguments: [ 77 | Token.address, 78 | NFT.address 79 | ], 80 | }) 81 | console.log("Staking verified !") 82 | } catch (error) { 83 | console.log("Already verified"); 84 | } 85 | 86 | console.log("Adding admin rights to staking for token..."); 87 | await Token.addAdmin(Staking.address); 88 | console.log("Admin rights added to staking for token."); 89 | 90 | console.log("Resume : ") 91 | console.log("Token address :", Token.address); 92 | console.log("NFT address :", NFT.address); 93 | console.log("Staking address :", Staking.address); 94 | } 95 | 96 | FullContract().then(); 97 | 98 | const delay = ms => new Promise(res => setTimeout(res, ms)); 99 | -------------------------------------------------------------------------------- /contracts/Jour23/Staking_Jour23.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "../solutions/NekrIsERC721.sol"; 5 | import "../solutions/NekrTokenIsERC20.sol"; 6 | 7 | contract Staking_Jour23 { 8 | 9 | NekrTokenIsERC20 token; 10 | NekrIsERC721 nft; 11 | 12 | uint public totalStaked; 13 | 14 | struct StakeStruct { 15 | uint tokenId; 16 | uint stakingStartTime; 17 | address owner; 18 | } 19 | 20 | mapping (uint => StakeStruct) public StructByID; 21 | 22 | uint rewardsPerHour = 500000000000000000; 23 | 24 | event Staked(address indexed owner, uint tokenId, uint value); 25 | event UnStaked(address indexed owner, uint tokenId, uint value); 26 | event Claimed(address indexed owner, uint amount); 27 | 28 | constructor(NekrTokenIsERC20 _token, NekrIsERC721 _nft) { 29 | token = _token; 30 | nft = _nft; 31 | } 32 | 33 | function stake(uint[] calldata tokenIds) external { 34 | for (uint i = 0 ; i < tokenIds.length ; i++) { 35 | require(nft.ownerOf(tokenIds[i]) == msg.sender, "Not the owner"); 36 | require(StructByID[tokenIds[i]].stakingStartTime == 0, "Already staked"); 37 | 38 | nft.transferFrom(msg.sender, address(this), tokenIds[i]); 39 | emit Staked(msg.sender, tokenIds[i], block.timestamp); 40 | 41 | StructByID[tokenIds[i]] = StakeStruct({ 42 | tokenId: tokenIds[i], 43 | stakingStartTime: block.timestamp, 44 | owner: msg.sender 45 | }); 46 | } 47 | 48 | totalStaked += tokenIds.length; 49 | } 50 | 51 | function getRewards(address owner, uint[] calldata tokenIds) external view returns(uint) { 52 | uint totalEarned; 53 | 54 | for (uint i = 0 ; i < tokenIds.length ; i++) { 55 | require(StructByID[tokenIds[i]].owner == owner, "All owners are not the same"); 56 | 57 | uint stakingStartTime = StructByID[tokenIds[i]].stakingStartTime; 58 | totalEarned += (block.timestamp - stakingStartTime) * rewardsPerHour / 3600; 59 | } 60 | 61 | return totalEarned; 62 | } 63 | 64 | function claim(uint[] calldata tokenIds) external { 65 | _claim(msg.sender, tokenIds, false); 66 | } 67 | 68 | function _claim(address _owner, uint[] calldata _tokenIds, bool _unstake) internal { 69 | uint totalEarned; 70 | 71 | for (uint i = 0 ; i < _tokenIds.length ; i++) { 72 | require(StructByID[_tokenIds[i]].owner == _owner, "Not the owner, you cannot claim the awards"); 73 | 74 | uint stakingStartTime = StructByID[_tokenIds[i]].stakingStartTime; 75 | totalEarned += (block.timestamp - stakingStartTime) * rewardsPerHour / 3600; 76 | 77 | StructByID[_tokenIds[i]] = StakeStruct({ 78 | tokenId: _tokenIds[i], 79 | stakingStartTime: block.timestamp, 80 | owner: _owner 81 | }); 82 | } 83 | 84 | if (totalEarned > 0) { 85 | token.mint(_owner, totalEarned); 86 | } 87 | 88 | if (_unstake) { 89 | _unstakeNFT(_owner, _tokenIds); 90 | } 91 | 92 | emit Claimed(_owner, totalEarned); 93 | } 94 | 95 | function unstakeNFT(uint[] calldata tokenIds) external { 96 | _claim(msg.sender, tokenIds, true); 97 | } 98 | 99 | function _unstakeNFT(address _owner, uint[] calldata _tokenIds) internal { 100 | 101 | for (uint i = 0 ; i < _tokenIds.length ; i++) { 102 | require(StructByID[_tokenIds[i]].owner == msg.sender, "Not the owner"); 103 | delete StructByID[_tokenIds[i]]; 104 | nft.transferFrom(address(this), _owner, _tokenIds[i]); 105 | emit UnStaked(msg.sender, _tokenIds[i], block.timestamp); 106 | } 107 | 108 | totalStaked -= _tokenIds.length; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /contracts/solutions/Staking.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "./NekrIsERC721.sol"; 5 | import "./NekrTokenIsERC20.sol"; 6 | 7 | contract Staking { 8 | 9 | NekrTokenIsERC20 token; 10 | NekrIsERC721 nft; 11 | 12 | uint public totalStaked; 13 | 14 | struct StakeStruct { 15 | uint tokenId; 16 | uint stakingStartTime; 17 | address owner; 18 | } 19 | 20 | mapping (uint => StakeStruct) public StructByID; 21 | 22 | uint rewardsPerHour = 500000000000000000; 23 | 24 | event Staked(address indexed owner, uint tokenId, uint value); 25 | event UnStaked(address indexed owner, uint tokenId, uint value); 26 | event Claimed(address indexed owner, uint amount); 27 | 28 | constructor(NekrTokenIsERC20 _token, NekrIsERC721 _nft) { 29 | token = _token; 30 | nft = _nft; 31 | } 32 | 33 | function stake(uint[] calldata tokenIds) external { 34 | for (uint i = 0 ; i < tokenIds.length ; i++) { 35 | require(nft.ownerOf(tokenIds[i]) == msg.sender, "Not the owner"); 36 | require(StructByID[tokenIds[i]].stakingStartTime == 0, "Already staked"); 37 | 38 | nft.transferFrom(msg.sender, address(this), tokenIds[i]); 39 | emit Staked(msg.sender, tokenIds[i], block.timestamp); 40 | 41 | StructByID[tokenIds[i]] = StakeStruct({ 42 | tokenId: tokenIds[i], 43 | stakingStartTime: block.timestamp, 44 | owner: msg.sender 45 | }); 46 | } 47 | 48 | totalStaked += tokenIds.length; 49 | } 50 | 51 | function getRewards(address owner, uint[] calldata tokenIds) external view returns(uint) { 52 | uint totalEarned; 53 | 54 | for (uint i = 0 ; i < tokenIds.length ; i++) { 55 | require(StructByID[tokenIds[i]].owner == owner, "All owners are not the same"); 56 | 57 | uint stakingStartTime = StructByID[tokenIds[i]].stakingStartTime; 58 | totalEarned += (block.timestamp - stakingStartTime) * rewardsPerHour / 3600; 59 | } 60 | 61 | return totalEarned; 62 | } 63 | 64 | function claim(uint[] calldata tokenIds) external { 65 | _claim(msg.sender, tokenIds, false); 66 | } 67 | 68 | function _claim(address _owner, uint[] calldata _tokenIds, bool _unstake) internal { 69 | uint totalEarned; 70 | 71 | for (uint i = 0 ; i < _tokenIds.length ; i++) { 72 | require(StructByID[_tokenIds[i]].owner == _owner, "Not the owner, you cannot claim the awards"); 73 | 74 | uint stakingStartTime = StructByID[_tokenIds[i]].stakingStartTime; 75 | totalEarned += (block.timestamp - stakingStartTime) * rewardsPerHour / 3600; 76 | 77 | StructByID[_tokenIds[i]] = StakeStruct({ 78 | tokenId: _tokenIds[i], 79 | stakingStartTime: block.timestamp, 80 | owner: _owner 81 | }); 82 | } 83 | 84 | if (totalEarned > 0) { 85 | token.mint(_owner, totalEarned); 86 | } 87 | 88 | if (_unstake) { 89 | _unstakeNFT(_owner, _tokenIds); 90 | } 91 | 92 | emit Claimed(_owner, totalEarned); 93 | } 94 | 95 | function unstakeNFT(uint[] calldata tokenIds) external { 96 | _claim(msg.sender, tokenIds, true); 97 | } 98 | 99 | function _unstakeNFT(address _owner, uint[] calldata _tokenIds) internal { 100 | 101 | for (uint i = 0 ; i < _tokenIds.length ; i++) { 102 | require(StructByID[_tokenIds[i]].owner == msg.sender, "Not the owner"); 103 | delete StructByID[_tokenIds[i]]; 104 | nft.transferFrom(address(this), _owner, _tokenIds[i]); 105 | emit UnStaked(msg.sender, _tokenIds[i], block.timestamp); 106 | } 107 | 108 | totalStaked -= _tokenIds.length; 109 | } 110 | 111 | function tokensByOwner(address owner) external view returns(uint[] memory) { 112 | uint supply = nft.totalSupply(); 113 | uint[] memory tmpList = new uint[](supply); 114 | uint stakedCount = 0; 115 | 116 | for(uint i = 0; i < supply; i++) { 117 | if (StructByID[i].owner == owner) { 118 | tmpList[stakedCount] = i; 119 | stakedCount++; 120 | } 121 | } 122 | 123 | uint[] memory tokensList = new uint[](stakedCount); 124 | for(uint i = 0; i < stakedCount; i++) { 125 | tokensList[i] = tmpList[i]; 126 | } 127 | 128 | return tokensList; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /contracts/solutions/NekrIsERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 8 | 9 | 10 | contract NekrIsERC721 is ERC721, Ownable { 11 | 12 | using Strings for uint; 13 | 14 | uint256 private _tokenIds; 15 | 16 | enum Step { 17 | SaleNotStarted, 18 | WhitelistSale, 19 | PublicSale, 20 | SoldOut 21 | } 22 | 23 | Step public currentStep; 24 | 25 | bytes32 public merkleRoot; 26 | 27 | uint public constant maxSupply = 20; 28 | uint public constant maxWhitelist = 10; 29 | 30 | uint public whitelistPrice = 0.5 ether; 31 | uint public publicPrice = 1 ether; 32 | 33 | mapping(address => uint) public amountMintedByAddress; 34 | 35 | string public baseTokenURI; 36 | 37 | bool public metadataFrozen = false; 38 | 39 | event newMint(address indexed sender, uint256 amount); 40 | event stepUpdated(Step currentStep); 41 | 42 | constructor(string memory _baseTokenURI, bytes32 _merkleRoot) 43 | ERC721("Calendar Collection", "CALCO") 44 | Ownable(msg.sender) 45 | { 46 | baseTokenURI = _baseTokenURI; 47 | merkleRoot = _merkleRoot; 48 | } 49 | 50 | function totalSupply() public view returns (uint) { 51 | return _tokenIds; 52 | } 53 | 54 | function mint(uint _count, bytes32[] calldata _proof) external payable { 55 | require(currentStep == Step.WhitelistSale || currentStep == Step.PublicSale, "The sale is not open or closed "); 56 | uint current_price = getCurrentPrice(); 57 | uint totalMinted = _tokenIds; 58 | 59 | if (currentStep == Step.WhitelistSale) { 60 | require(isWhitelisted(msg.sender, _proof), "Not whitelisted"); 61 | require(amountMintedByAddress[msg.sender] + _count <= 1, "You can mint only 1 NFT per address"); 62 | require(totalMinted + _count <= maxWhitelist, "Max supply exceeded"); 63 | } 64 | 65 | require(totalMinted + _count <= maxSupply, "The total supply has been reached."); 66 | require(msg.value >= current_price * _count, "Not enough funds to purchase."); 67 | 68 | for (uint i = 0; i < _count; i++) { 69 | uint newTokenID = _tokenIds; 70 | _mint(msg.sender, newTokenID); 71 | _tokenIds += 1; 72 | } 73 | 74 | emit newMint(msg.sender, _count); 75 | } 76 | 77 | function setBaseURI(string memory _baseTokenURI) public onlyOwner { 78 | require(!metadataFrozen, "Metadata are frozen"); 79 | baseTokenURI = _baseTokenURI; 80 | } 81 | 82 | function freezeMetadata() public onlyOwner { 83 | metadataFrozen = true; 84 | } 85 | 86 | function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) { 87 | _requireOwned(_tokenId); 88 | return string(abi.encodePacked(baseTokenURI, _tokenId.toString())); 89 | } 90 | 91 | function withdraw() external onlyOwner { 92 | require(address(this).balance > 0, "Nothing to withdraw"); 93 | payable(msg.sender).transfer(address(this).balance); 94 | } 95 | 96 | function updatePublicPrice(uint _newPrice) external onlyOwner { 97 | publicPrice = _newPrice; 98 | } 99 | 100 | function updateWhitelistPrice(uint _newPrice) external onlyOwner { 101 | whitelistPrice = _newPrice; 102 | } 103 | 104 | function getCurrentPrice() public view returns (uint) { 105 | if (currentStep == Step.WhitelistSale) { 106 | return whitelistPrice; 107 | } else { 108 | return publicPrice; 109 | } 110 | } 111 | 112 | function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner { 113 | merkleRoot = _merkleRoot; 114 | } 115 | 116 | function setStep(Step _step) external onlyOwner { 117 | currentStep = _step; 118 | emit stepUpdated(_step); 119 | } 120 | 121 | function isWhitelisted(address _account, bytes32[] calldata proof) public view returns(bool) { 122 | return _verify(_leaf(_account), proof); 123 | } 124 | 125 | function _leaf(address _account) internal pure returns(bytes32) { 126 | return keccak256(abi.encodePacked(_account)); 127 | } 128 | 129 | function _verify(bytes32 leaf, bytes32[] memory proof) internal view returns(bool) { 130 | return MerkleProof.verify(proof, merkleRoot, leaf); 131 | } 132 | } 133 | --------------------------------------------------------------------------------