├── .solhintignore ├── .npmignore ├── .eslintignore ├── .mocharc.js ├── .solcover.js ├── .prettierignore ├── scripts ├── verifications │ ├── revealManager.js │ ├── nftFactory.js │ ├── vrfManager.js │ ├── wallet.js │ └── nftBeaconProxy.js ├── interactions │ ├── exchange │ │ └── changeExRate.js │ ├── nft │ │ ├── initFilterRegistry.js │ │ ├── sendEther.js │ │ ├── setReveal.js │ │ ├── changeBaseURI.js │ │ ├── transferBalance.js │ │ ├── publicMint.js │ │ ├── ticketMint.js │ │ └── transferNFT.js │ ├── mintManager │ │ ├── setMinFee.js │ │ ├── airDrop.js │ │ └── setPublicMintSchedule.js │ ├── wallet │ │ ├── sendEther.js │ │ ├── approve.js │ │ ├── revoke.js │ │ ├── approvalRequest.js │ │ └── payment.js │ ├── caManager │ │ ├── removeManager.js │ │ ├── registerManager.js │ │ ├── roleAddition.js │ │ ├── roleRemoval.js │ │ └── registerNFT.js │ ├── ticketManager │ │ └── setEndDate.js │ ├── interactionHelpers.js │ └── revealManager │ │ └── vrfRequest.js ├── upgrades │ ├── forceImport.js │ ├── upgradeBeacon.js │ └── upgradeProxy.js └── deployments │ ├── deployWallet.js │ ├── deployConstants.js │ ├── runDeploy.js │ ├── deployNFTBeaconProxy.js │ ├── deployments.js │ └── index.js ├── audit └── Quantstamp - Omnuum - Final Report.pdf ├── .solhint.json ├── .prettierrc ├── .gitignore ├── contracts ├── mock │ ├── MockExchange.sol │ ├── MockVrfCoords.sol │ ├── MockMintManager.sol │ ├── MockVrfRequester.sol │ ├── MockLink.sol │ ├── GasNFT.sol │ └── MockNFT.sol ├── V1 │ ├── OperatorFilterRegistry │ │ ├── DefaultOperatorFiltererUpgradeable.sol │ │ ├── IOperatorFilterRegistry.sol │ │ └── OperatorFiltererUpgradeable.sol │ ├── RevealManager.sol │ ├── SenderVerifier.sol │ ├── NftFactory.sol │ ├── OmnuumExchange.sol │ ├── TicketManager.sol │ ├── OmnuumCAManager.sol │ └── OmnuumVRFManager.sol ├── library │ └── RevertMessage.sol └── utils │ ├── Ownable.sol │ └── OwnableUpgradeable.sol ├── deploy_history ├── mainnet_5-20-2022_17:57:30.json ├── mainnet_5-19-2022_14:00:00.json └── mainnet_4-15-2022_14:49:04.json ├── utils ├── hardhat.js ├── s3.js ├── walletFromMnemonic.js ├── wallet.js ├── transaction.js └── constants.js ├── .eslintrc.js ├── README.md ├── test ├── revealManager.test.js ├── etc │ ├── util.js │ └── mock.js ├── senderVerifier.test.js ├── exchange.test.js └── nftFactory.test.js ├── hardhat.config.js ├── package.json └── resources └── OmnuumNFT1155.sol /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.js 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | timeout: 1000 * 100, 3 | }; 4 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['library', 'mock'], 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /scripts/verifications/revealManager.js: -------------------------------------------------------------------------------- 1 | module.exports = ['0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab']; 2 | -------------------------------------------------------------------------------- /audit/Quantstamp - Omnuum - Final Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciety-xyz/omnuum-contracts/HEAD/audit/Quantstamp - Omnuum - Final Report.pdf -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 140, 5 | "bracketSpacing": true, 6 | "formatOnSave": true, 7 | "semi": true, 8 | "explicitTypes": "always" 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /scripts/deployments/deployResults/ 2 | /scripts/deployments/deployNFTResults/ 3 | node_modules 4 | 5 | build 6 | 7 | .env 8 | .env.example 9 | .idea 10 | cache 11 | 12 | .npmrc 13 | 14 | /scripts/scratch 15 | .DS_Store 16 | data/ 17 | coverage/ 18 | coverage.json 19 | artifacts/ 20 | -------------------------------------------------------------------------------- /scripts/verifications/nftFactory.js: -------------------------------------------------------------------------------- 1 | // constructor( 2 | // address _caManager, 3 | // address _nftContractBeacon, 4 | // address _omnuumSigner 5 | 6 | module.exports = [ 7 | '0x4E80cABF3Ad2a4d1abD1Bbd2e511A7eA8e8cCdf9', 8 | '0x1FBfb9545B8167d027a2D84590b59f81cA1A2483', 9 | '0x6F75DDD866B9E390f9f668235C3219432EAa0eE3', 10 | ]; 11 | -------------------------------------------------------------------------------- /scripts/verifications/vrfManager.js: -------------------------------------------------------------------------------- 1 | // address _LINK, 2 | // address _vrf_coord, 3 | // bytes32 _key_hash, 4 | // uint256 _fee, 5 | // address _omnuumCA (CA manager) 6 | 7 | const DEP_CONSTANTS = require('../deployments/deployConstants'); 8 | 9 | module.exports = [...Object.values(DEP_CONSTANTS.vrfManager.chainlink.mainnet), '0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab']; 10 | -------------------------------------------------------------------------------- /contracts/mock/MockExchange.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '../V1/OmnuumExchange.sol'; 5 | 6 | contract MockExchange { 7 | function exchangeToken( 8 | address _exchangeCA, 9 | address _token, 10 | uint256 _amount, 11 | address _to 12 | ) public payable { 13 | OmnuumExchange(_exchangeCA).exchangeToken{ value: msg.value }(_token, _amount, _to); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/verifications/wallet.js: -------------------------------------------------------------------------------- 1 | const { createWalletOwnerAccounts } = require('../deployments/deployHelper'); 2 | const DEP_CONSTANTS = require('../deployments/deployConstants'); 3 | 4 | const walletOwnerAccounts = createWalletOwnerAccounts(DEP_CONSTANTS.wallet.ownerAddresses, DEP_CONSTANTS.wallet.ownerLevels); 5 | 6 | console.log(walletOwnerAccounts); 7 | 8 | module.exports = [DEP_CONSTANTS.wallet.consensusRatio, DEP_CONSTANTS.wallet.minLimitForConsensus, walletOwnerAccounts]; 9 | -------------------------------------------------------------------------------- /contracts/mock/MockVrfCoords.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@chainlink/contracts/src/v0.8/VRFConsumerBase.sol'; 5 | 6 | contract MockVrfCoords { 7 | constructor() {} 8 | 9 | function sendRandom( 10 | address _vrfManager, 11 | bytes32 requestId, 12 | uint256 randomness 13 | ) public { 14 | VRFConsumerBase(_vrfManager).rawFulfillRandomness(requestId, randomness); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/mock/MockMintManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import './GasNFT.sol'; 5 | 6 | contract MockMintManager { 7 | function airdrop( 8 | address _target, 9 | address[] calldata _tos, 10 | uint32[] calldata _quantitys 11 | ) public { 12 | uint256 len = _tos.length; 13 | for (uint32 i = 0; i < len; i++) { 14 | GasNFT(_target).mint(_tos[i], _quantitys[i]); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /deploy_history/mainnet_5-20-2022_17:57:30.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "mainnet", 3 | "deployStartAt": "5:57:30 PM", 4 | "deployer": "0x84803BBd4269f596D94e88fA4E697b21C39370d2", 5 | "mintManager": { 6 | "proxy": "0x2D24c1C8677870d320C10aE7b840A279A0CAF492", 7 | "impl": "0xf8a9b89e29ceb504f33c83a24b9d0cb312677788", 8 | "admin": "0x6d82f1895bdAe7B6FEA3c57F44fc281AbB6BB649", 9 | "gasUsed": 2186325, 10 | "blockNumber": 14810165, 11 | "address": "0x2D24c1C8677870d320C10aE7b840A279A0CAF492" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/mock/MockVrfRequester.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '../V1/OmnuumVRFManager.sol'; 5 | 6 | contract MockVrfRequester { 7 | constructor() {} 8 | 9 | function requestVRF(address _vrfManager) public payable { 10 | OmnuumVRFManager(_vrfManager).requestVRF{ value: msg.value }('REVEAL_PFP'); 11 | } 12 | 13 | function requestVRFOnce(address _vrfManager, address _targetAddress) public payable { 14 | OmnuumVRFManager(_vrfManager).requestVRFOnce{ value: msg.value }(_targetAddress, 'REVEAL_PFP'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/hardhat.js: -------------------------------------------------------------------------------- 1 | const { ethers, upgrades, config, run } = require('hardhat'); 2 | const { rm } = require('fs/promises'); 3 | const path = require('path'); 4 | const { mapC, go } = require('fxjs'); 5 | 6 | const clean = async () => { 7 | const targets = ['artifacts', 'cache', '.openzeppelin', 'data']; 8 | await go( 9 | targets, 10 | mapC((target) => rm(path.join(__dirname, `../${target}`), { recursive: true, force: true })), 11 | ); 12 | }; 13 | 14 | module.exports.compile = async ({ quiet, force }) => { 15 | if (force) { 16 | await clean(); 17 | } 18 | 19 | await run('compile', { force, quiet }); 20 | }; 21 | -------------------------------------------------------------------------------- /contracts/mock/MockLink.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | contract MockLink { 5 | uint256 bal = 10 ether; 6 | 7 | function changeBalance(uint256 _bal) public { 8 | bal = _bal; 9 | } 10 | 11 | function balanceOf(address) public view returns (uint256) { 12 | return bal; 13 | } 14 | 15 | function transfer(address _to, uint256 value) public returns (bool) { 16 | return true; 17 | } 18 | 19 | function transferAndCall( 20 | address to, 21 | uint256 value, 22 | bytes calldata data 23 | ) external returns (bool success) { 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/verifications/nftBeaconProxy.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | 3 | const iFace = new ethers.utils.Interface(['function initialize(address,address,uint32,string,address) public']); 4 | 5 | const data = iFace.encodeFunctionData(iFace.getFunction('initialize'), [ 6 | '0x4E80cABF3Ad2a4d1abD1Bbd2e511A7eA8e8cCdf9', // caManager 7 | '0x6F75DDD866B9E390f9f668235C3219432EAa0eE3', // OmnuumSigner 8 | '99', // MaxSupply 9 | 'ipfs://QmTrfNt151At8XkSxn7GaABhURdHwRDDSX3zbu4f8eikgo/', // coverUri 10 | '0x6F75DDD866B9E390f9f668235C3219432EAa0eE3', // Project Owner 11 | ]); 12 | 13 | module.exports = ['0x1FBfb9545B8167d027a2D84590b59f81cA1A2483', data]; // Beacon Address 14 | -------------------------------------------------------------------------------- /contracts/V1/OperatorFilterRegistry/DefaultOperatorFiltererUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { OperatorFiltererUpgradeable } from './OperatorFiltererUpgradeable.sol'; 5 | 6 | abstract contract DefaultOperatorFiltererUpgradeable is OperatorFiltererUpgradeable { 7 | address constant DEFAULT_SUBSCRIPTION = address(0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6); 8 | 9 | function __DefaultOperatorFilterer_init() internal onlyInitializing { 10 | OperatorFiltererUpgradeable.__OperatorFilterer_init(DEFAULT_SUBSCRIPTION, true); 11 | } 12 | 13 | function __DefaultOperatorFilterer() internal { 14 | OperatorFiltererUpgradeable.__OperatorFilterer(DEFAULT_SUBSCRIPTION, true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/library/RevertMessage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | // https://ethereum.stackexchange.com/questions/83528/how-can-i-get-the-revert-reason-of-a-call-in-solidity-so-that-i-can-use-it-in-th 5 | library RevertMessage { 6 | function parse(bytes memory _returnData) internal pure returns (string memory) { 7 | // If the _res length is less than 68, then the transaction failed silently (without a revert message) 8 | if (_returnData.length < 68) return 'Transaction reverted silently'; 9 | 10 | assembly { 11 | // Slice the sighash. 12 | _returnData := add(_returnData, 0x04) 13 | } 14 | return abi.decode(_returnData, (string)); // All that remains is the revert string 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/interactions/exchange/changeExRate.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers'); 2 | const { readFile } = require('fs/promises'); 3 | const deployed = require('../../../artifacts/contracts/V1/OmnuumExchange.sol/OmnuumExchange.json'); 4 | 5 | async function main() { 6 | const provider = new ethers.providers.InfuraProvider('homestead', {}); 7 | 8 | const signer = await new ethers.Wallet('', provider); 9 | 10 | console.log('signer address', signer.address); 11 | 12 | const contract = new ethers.ContractFactory(deployed.abi, deployed.bytecode).attach('0x680756B2794B4d4Ad265040B18539f19f90F13CC'); 13 | 14 | const tx = await contract.connect(signer).updateTmpExchangeRate('7333333333000000'); 15 | 16 | const receipt = await tx.wait(); 17 | 18 | console.log(receipt); 19 | } 20 | 21 | main(); 22 | -------------------------------------------------------------------------------- /utils/s3.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const chalk = require('chalk'); 3 | 4 | const s3Upload = async ({ bucketName, keyName, fileBuffer, region = 'ap-northeast-2' }) => { 5 | const s3 = new AWS.S3({ region }); 6 | await s3 7 | .putObject({ 8 | Bucket: bucketName, 9 | Key: keyName, 10 | Body: fileBuffer, 11 | }) 12 | .promise(); 13 | console.log(`Successfully uploaded data to\n${chalk.yellow(`https://s3.console.aws.amazon.com/s3/object/${bucketName}/${keyName}`)}`); 14 | }; 15 | 16 | const s3Get = ({ bucketName, keyName, region = 'ap-northeast-2' }) => { 17 | const s3 = new AWS.S3({ region }); 18 | return s3 19 | .getObject({ 20 | Bucket: bucketName, 21 | Key: keyName, 22 | }) 23 | .promise(); 24 | }; 25 | 26 | module.exports = { s3Upload, s3Get }; 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | extends: ['airbnb', 'prettier'], 9 | plugins: ['prettier'], 10 | parserOptions: { 11 | ecmaVersion: 12, 12 | }, 13 | overrides: [ 14 | { 15 | files: ['hardhat.config.js'], 16 | globals: { task: true }, 17 | }, 18 | ], 19 | globals: { 20 | artifacts: true, 21 | }, 22 | rules: { 23 | quotes: ['error', 'single'], 24 | 'no-restricted-syntax': 0, 25 | 'import/extensions': 0, 26 | 'no-underscore-dangle': 0, 27 | 'max-len': 0, 28 | camelcase: 0, 29 | 'no-multi-str': 'off', 30 | eqeqeq: 0, 31 | 'no-unused-expressions': 0, 32 | 'comma-dangle': 'off', 33 | 'no-console': 0, 34 | 'no-unused-vars': 0, 35 | 'no-nested-ternary': 0, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | There are a lot of NFT projects and PFP projects. But, there are limited tools and services for trying new project. Artists or creator whoever want to launch a new project needs several resources. FE developer, BE developer, DApp Developer, Artists, NFT image maker etc. To lowering entry barrier and encourage NFT projects for everyone, we are building those tools. 5 | 6 | Core contracts are NFT and several supportive contracts. NFT contract is based on ERC1155 with little changes. These are similar to previous NFT, PFP projects except reusability. Our goal is to build tool, so NFT contracts have to be deployed multiple times for each projects. This repository is our efforts to reach that goal. 7 | 8 | 9 | ## Project Setting 10 | 11 | ```shell 12 | npm install 13 | ``` 14 | 15 | ## Testing 16 | 17 | 18 | - Test commands 19 | 20 | ```shell 21 | npx hardhat node 22 | npx hardhat test 23 | ``` 24 | - Code Coverage 25 | ```shell 26 | npx hardhat coverage --network hardhat 27 | ``` 28 | 29 | 30 | ## References 31 | 32 | - [Omnuum.io](https://omnuum.io/) 33 | - [Docs](https://www.omnuum.page/) 34 | -------------------------------------------------------------------------------- /contracts/V1/RevealManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '../utils/Ownable.sol'; 5 | import './OmnuumVRFManager.sol'; 6 | import './OmnuumCAManager.sol'; 7 | 8 | /// @title RevealManager - simple proxy for reveal call 9 | /// @author Omnuum Dev Team - 10 | /// @notice prevent direct call to VRF manager. separate concern from NFT contract and VRF contract 11 | contract RevealManager { 12 | OmnuumCAManager private caManager; 13 | 14 | constructor(OmnuumCAManager _caManager) { 15 | caManager = _caManager; 16 | } 17 | 18 | /// @notice Request Chainlink VRF through the OmnuumVRFManager contract for revealing all NFT items 19 | /// @dev check that msg.sender is owner of nft contract and nft is revealed or not 20 | /// @param _nftContract nft contract address 21 | function vrfRequest(Ownable _nftContract) external payable { 22 | /// @custom:error (OO1) - Ownable: Caller is not the collection owner 23 | require(_nftContract.owner() == msg.sender, 'OO1'); 24 | 25 | OmnuumVRFManager(caManager.getContract('VRF')).requestVRFOnce{ value: msg.value }(address(_nftContract), 'REVEAL_PFP'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scripts/upgrades/forceImport.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { upgrades, ethers } = require('hardhat'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { nullCheck } = require('../deployments/deployHelper'); 6 | 7 | const inquirerParams = { 8 | contractName: 'contractName', 9 | contractAddress: 'contractAddress', 10 | }; 11 | 12 | const getSolidityFileList = fs 13 | .readdirSync(path.resolve(__dirname, '../../contracts/V1')) 14 | .map((filename) => filename.substr(0, filename.indexOf('.'))); 15 | 16 | const questions = [ 17 | { 18 | name: inquirerParams.contractName, 19 | type: 'list', 20 | message: '🤔 Choose contract you want to import is ...', 21 | choices: getSolidityFileList, 22 | }, 23 | { 24 | name: inquirerParams.contractAddress, 25 | type: 'input', 26 | message: '🤔 Proxy or Beacon contract address is ...', 27 | validate: nullCheck, 28 | }, 29 | ]; 30 | 31 | (async () => { 32 | inquirer.prompt(questions).then(async (ans) => { 33 | try { 34 | const Contract = await ethers.getContractFactory(ans.contractName); 35 | await upgrades.forceImport(ans.contractAddress, Contract); 36 | console.log('💋 Import is done!'); 37 | } catch (e) { 38 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 39 | } 40 | }); 41 | })(); 42 | -------------------------------------------------------------------------------- /test/revealManager.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { ethers, upgrades } = require('hardhat'); 3 | 4 | const { isLocalNetwork } = require('./etc/util.js'); 5 | const Constants = require('../utils/constants.js'); 6 | require('chai').should(); 7 | 8 | const { testDeploy, prepareDeploy, prepareMockDeploy } = require('./etc/mock.js'); 9 | 10 | upgrades.silenceWarnings(); 11 | 12 | // *Ticket Manager Contract will be removed and this feature will be replaced by off-chain lazy minting method. 13 | describe('RevealManager', () => { 14 | before(async () => { 15 | await prepareDeploy.call(this); 16 | await prepareMockDeploy.call(this); 17 | }); 18 | 19 | beforeEach(async () => { 20 | this.accounts = await ethers.getSigners(); 21 | await testDeploy.call(this, this.accounts); 22 | }); 23 | 24 | describe('[Method] vrfRequest', () => { 25 | // success case is tested on vRFManager.test.js 26 | it('[Revert] only project owner', async () => { 27 | const { 28 | omnuumNFT721, 29 | revealManager, 30 | accounts: [, maliciousAC], 31 | } = this; 32 | 33 | if (await isLocalNetwork(ethers.provider)) return; 34 | 35 | // check link is enough 36 | await expect(revealManager.connect(maliciousAC).vrfRequest(omnuumNFT721.address)).to.be.revertedWith(Constants.reasons.code.OO1); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /scripts/interactions/nft/initFilterRegistry.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | nftContractAddress: 'nftContractAddress', 7 | ownerPrivateKey: 'ownerPrivateKey', 8 | }; 9 | 10 | const questions = [ 11 | { 12 | name: inquirerParams.nftContractAddress, 13 | type: 'input', 14 | message: '🤔 nft contract address is...', 15 | validate: nullCheck, 16 | }, 17 | { 18 | name: inquirerParams.ownerPrivateKey, 19 | type: 'input', 20 | message: '🤔 NFT project owner private key is ...', 21 | validate: nullCheck, 22 | }, 23 | ]; 24 | 25 | (async () => { 26 | inquirer.prompt(questions).then(async (ans) => { 27 | try { 28 | const provider = await getRPCProvider(ethers.provider); 29 | const nftOwnerSigner = new ethers.Wallet(ans.ownerPrivateKey, provider); 30 | 31 | const nftContract = (await ethers.getContractFactory('OmnuumNFT721')).attach(ans.nftContractAddress); 32 | const txResponse = await nftContract.connect(nftOwnerSigner).initFilterRegistryAfterDeploy(); 33 | const txReceipt = await txResponse.wait(); 34 | 35 | console.log(txReceipt); 36 | console.log(`💋 Init Filter Registry is set.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 37 | } catch (e) { 38 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 39 | } 40 | }); 41 | })(); 42 | -------------------------------------------------------------------------------- /scripts/interactions/mintManager/setMinFee.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | const { testValues } = require('../../../utils/constants'); 5 | 6 | const questions = [ 7 | { 8 | name: 'deployer_private_key', 9 | type: 'input', 10 | message: '🤔 MintManager deployer private key is ...', 11 | validate: nullCheck, 12 | }, 13 | { 14 | name: 'mint_manager_address', 15 | type: 'input', 16 | message: '🤔 MintManager contract address is ...', 17 | validate: nullCheck, 18 | }, 19 | { 20 | name: 'minFee', 21 | type: 'input', 22 | message: '🤔 MinFee value ... (in ether)', 23 | validate: nullCheck, 24 | }, 25 | ]; 26 | 27 | (async () => { 28 | inquirer.prompt(questions).then(async (ans) => { 29 | try { 30 | const provider = await getRPCProvider(ethers.provider); 31 | const deployerSigner = new ethers.Wallet(ans.deployer_private_key, provider); 32 | const mintManager = (await ethers.getContractFactory('OmnuumMintManager')).attach(ans.mint_manager_address); 33 | 34 | const txResponse = await mintManager.connect(deployerSigner).setMinFee(ethers.utils.parseEther(ans.minFee)); 35 | 36 | const txReceipt = await txResponse.wait(); 37 | 38 | console.log(txReceipt); 39 | 40 | console.log(`\n\nUpdated minFee: ${await mintManager.minFee()}`); 41 | } catch (e) { 42 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 43 | } 44 | }); 45 | })(); 46 | -------------------------------------------------------------------------------- /utils/walletFromMnemonic.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { go, range, map } = require('fxjs'); 3 | const ethers = require('ethers'); 4 | const { nullCheck } = require('../scripts/deployments/deployHelper'); 5 | 6 | const question = [ 7 | { 8 | name: 'wordLength', 9 | type: 'list', 10 | message: '🤔 Choose 12 or 24 word list ?...', 11 | choices: ['12', '24'], 12 | validate: nullCheck, 13 | }, 14 | ]; 15 | 16 | const validateWord = (word) => { 17 | const isWordInList = ethers.wordlists.en.getWordIndex(word) >= 0; 18 | if (isWordInList) { 19 | return true; 20 | } 21 | return '🚨 Word is not in the list (BIP39)'; 22 | }; 23 | 24 | const createWordQuestions = async (len) => 25 | go( 26 | range(len), 27 | map((idx) => { 28 | const word_index = idx + 1; 29 | return { name: `word_${word_index}`, type: 'password', mask: '*', message: `Input word [${word_index}]`, validate: validateWord }; 30 | }), 31 | ); 32 | 33 | const createWalletPath = (i) => `m/44'/60'/0'/0/${i}`; 34 | 35 | const getWalletFromMnemonic = async (provider) => { 36 | const { wordLength } = await inquirer.prompt(question); 37 | const wordQuestions = await createWordQuestions(Number(wordLength)); 38 | const wordList = await inquirer.prompt(wordQuestions); 39 | const mnemonic = Object.values(wordList).join(' '); 40 | const wallet = ethers.Wallet.fromMnemonic(mnemonic, createWalletPath(0), ethers.wordlists.en); 41 | if (provider) { 42 | return wallet.connect(provider); 43 | } 44 | return wallet; 45 | }; 46 | 47 | module.exports = { getWalletFromMnemonic, validateWord }; 48 | -------------------------------------------------------------------------------- /contracts/utils/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts/utils/Context.sol'; 5 | 6 | contract Ownable is Context { 7 | address private _owner; 8 | 9 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 10 | 11 | /** 12 | * @dev Initializes the contract setting the deployer as the initial owner. 13 | */ 14 | constructor() { 15 | _transferOwnership(_msgSender()); 16 | } 17 | 18 | /** 19 | * @dev Returns the address of the current owner. 20 | */ 21 | function owner() public view virtual returns (address) { 22 | return _owner; 23 | } 24 | 25 | /** 26 | * @dev Throws if called by any account other than the owner. 27 | */ 28 | modifier onlyOwner() { 29 | require(owner() == _msgSender(), 'Ownable: caller is not the owner'); 30 | _; 31 | } 32 | 33 | /** 34 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 35 | * Can only be called by the current owner. 36 | */ 37 | function transferOwnership(address newOwner) public virtual onlyOwner { 38 | require(newOwner != address(0), 'Ownable: new owner is the zero address'); 39 | _transferOwnership(newOwner); 40 | } 41 | 42 | /** 43 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 44 | * Internal function without access restriction. 45 | */ 46 | function _transferOwnership(address newOwner) internal virtual { 47 | address oldOwner = _owner; 48 | _owner = newOwner; 49 | emit OwnershipTransferred(oldOwner, newOwner); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scripts/interactions/wallet/sendEther.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | wallet_address: 'wallet_address', 7 | ether_sender_private_key: 'ether_sender_private_key', 8 | send_value: 'send_value', 9 | }; 10 | 11 | const questions = [ 12 | { 13 | name: inquirerParams.wallet_address, 14 | type: 'input', 15 | message: '🤔 Wallet contract address is ...', 16 | validate: nullCheck, 17 | }, 18 | { 19 | name: inquirerParams.ether_sender_private_key, 20 | type: 'input', 21 | message: '🤔 Ether sender private key is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.send_value, 26 | type: 'input', 27 | message: '🤔 Amount of Ether you want to send is (in ether)...', 28 | validate: nullCheck, 29 | }, 30 | ]; 31 | 32 | (async () => { 33 | inquirer.prompt(questions).then(async (ans) => { 34 | try { 35 | const provider = await getRPCProvider(ethers.provider); 36 | const senderSigner = new ethers.Wallet(ans.ether_sender_private_key, provider); 37 | 38 | const txResponse = await senderSigner.sendTransaction({ 39 | to: ans.wallet_address, 40 | value: ethers.utils.parseEther(ans.send_value), 41 | }); 42 | const txReceipt = await txResponse.wait(); 43 | 44 | console.log(txReceipt); 45 | console.log(`💋 Send ether to Wallet. \nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 46 | } catch (e) { 47 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 48 | } 49 | }); 50 | })(); 51 | -------------------------------------------------------------------------------- /scripts/interactions/wallet/approve.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | approver_private_key: 'approver_private_key', 7 | wallet_address: 'wallet_address', 8 | request_id: 'request_id', 9 | }; 10 | 11 | const questions = [ 12 | { 13 | name: inquirerParams.approver_private_key, 14 | type: 'input', 15 | message: '🤔 Approver private key is ...', 16 | validate: nullCheck, 17 | }, 18 | { 19 | name: inquirerParams.wallet_address, 20 | type: 'input', 21 | message: '🤔 Wallet contract address is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.request_id, 26 | type: 'input', 27 | message: '🤔 Request id you want to approve ...', 28 | validate: nullCheck, 29 | }, 30 | ]; 31 | 32 | (async () => { 33 | inquirer.prompt(questions).then(async (ans) => { 34 | try { 35 | const provider = await getRPCProvider(ethers.provider); 36 | const approverSigner = new ethers.Wallet(ans.approver_private_key, provider); 37 | 38 | const wallet = (await ethers.getContractFactory('OmnuumWallet')).attach(ans.wallet_address); 39 | 40 | const txResponse = await wallet.connect(approverSigner).approve(ans.request_id, { gasLimit: 10000000 }); 41 | const txReceipt = await txResponse.wait(); 42 | 43 | console.log(txReceipt); 44 | console.log(`💋 Approve is on the way..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 45 | } catch (e) { 46 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /scripts/interactions/wallet/revoke.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | approver_private_key: 'approver_private_key', 7 | wallet_address: 'wallet_address', 8 | request_id: 'request_id', 9 | }; 10 | 11 | const questions = [ 12 | { 13 | name: inquirerParams.approver_private_key, 14 | type: 'input', 15 | message: '🤔 Approver private key is ...', 16 | validate: nullCheck, 17 | }, 18 | { 19 | name: inquirerParams.wallet_address, 20 | type: 'input', 21 | message: '🤔 Wallet contract address is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.request_id, 26 | type: 'input', 27 | message: '🤔 Request id you want to revoke ...', 28 | validate: nullCheck, 29 | }, 30 | ]; 31 | 32 | (async () => { 33 | inquirer.prompt(questions).then(async (ans) => { 34 | try { 35 | const provider = await getRPCProvider(ethers.provider); 36 | const approverSigner = new ethers.Wallet(ans.approver_private_key, provider); 37 | 38 | const wallet = (await ethers.getContractFactory('OmnuumWallet')).attach(ans.wallet_address); 39 | 40 | const txResponse = await wallet.connect(approverSigner).revokeApproval(ans.request_id, { gasLimit: 10000000 }); 41 | const txReceipt = await txResponse.wait(); 42 | 43 | console.log(txReceipt); 44 | console.log(`💋 Revoke is on the way..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 45 | } catch (e) { 46 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /contracts/mock/GasNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts/token/ERC1155/ERC1155.sol'; 5 | import '@openzeppelin/contracts/utils/Counters.sol'; 6 | 7 | contract GasNFT is ERC1155 { 8 | using Counters for Counters.Counter; 9 | Counters.Counter private _tokenIdCounter; 10 | 11 | event Airdrop(address indexed nft, address indexed to, uint32 quantity); 12 | 13 | constructor() ERC1155('') {} 14 | 15 | function mint(address _to, uint32 _quantity) public { 16 | for (uint32 i = 0; i < _quantity; i++) { 17 | _tokenIdCounter.increment(); 18 | _mint(_to, _tokenIdCounter.current(), 1, ''); 19 | } 20 | } 21 | 22 | function mintMultiple(address[] calldata _tos) public { 23 | uint256 len = _tos.length; 24 | for (uint32 i = 0; i < len; i++) { 25 | _tokenIdCounter.increment(); 26 | _mint(_tos[i], _tokenIdCounter.current(), 1, ''); 27 | } 28 | } 29 | 30 | function mintMultiple2(address[] calldata _tos, uint32[] calldata _quantitys) public { 31 | uint256 len = _tos.length; 32 | uint256 totalQuantity; 33 | for (uint256 i = 0; i < len; i++) { 34 | totalQuantity += _quantitys[i]; 35 | } 36 | 37 | for (uint32 i = 0; i < len; i++) { 38 | address to = _tos[i]; 39 | uint32 quantity = _quantitys[i]; 40 | 41 | emit Airdrop(address(this), to, quantity); 42 | 43 | for (uint32 j = 0; j < quantity; j++) { 44 | _tokenIdCounter.increment(); 45 | _mint(to, _tokenIdCounter.current(), 1, ''); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /scripts/interactions/nft/sendEther.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | nft_contract_address: 'nft_contract_address', 7 | ether_sender_private_key: 'ether_sender_private_key', 8 | send_value: 'send_value', 9 | }; 10 | 11 | const questions = [ 12 | { 13 | name: inquirerParams.nft_contract_address, 14 | type: 'input', 15 | message: '🤔 NFT contract address is ...', 16 | validate: nullCheck, 17 | }, 18 | { 19 | name: inquirerParams.ether_sender_private_key, 20 | type: 'input', 21 | message: '🤔 Ether sender private key is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.send_value, 26 | type: 'input', 27 | message: '🤔 Amount of Ether you want to send is (in ether)...', 28 | validate: nullCheck, 29 | }, 30 | ]; 31 | 32 | (async () => { 33 | inquirer.prompt(questions).then(async (ans) => { 34 | try { 35 | const provider = await getRPCProvider(ethers.provider); 36 | const senderSigner = new ethers.Wallet(ans.ether_sender_private_key, provider); 37 | 38 | const txResponse = await senderSigner.sendTransaction({ 39 | to: ans.nft_contract_address, 40 | value: ethers.utils.parseEther(ans.send_value), 41 | }); 42 | const txReceipt = await txResponse.wait(); 43 | 44 | console.log(txReceipt); 45 | console.log(`💋 Send ether to NFT Contract. \nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 46 | } catch (e) { 47 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 48 | } 49 | }); 50 | })(); 51 | -------------------------------------------------------------------------------- /scripts/interactions/nft/setReveal.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | nftContractAddress: 'nftContractAddress', 7 | nft_owner_private_key: 'nft_owner_private_key', 8 | nft_address: 'nft_address', 9 | base_uri: 'base_uri', 10 | }; 11 | 12 | const questions = [ 13 | { 14 | name: inquirerParams.nftContractAddress, 15 | type: 'input', 16 | message: '🤔 nft contract address is...', 17 | validate: nullCheck, 18 | }, 19 | { 20 | name: inquirerParams.nft_owner_private_key, 21 | type: 'input', 22 | message: '🤔 NFT project owner private key is ...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.base_uri, 27 | type: 'input', 28 | message: '🤔 Base Uri (for reveal) to be changed is ...', 29 | validate: nullCheck, 30 | }, 31 | ]; 32 | 33 | (async () => { 34 | inquirer.prompt(questions).then(async (ans) => { 35 | try { 36 | const provider = await getRPCProvider(ethers.provider); 37 | const nftOwnerSigner = new ethers.Wallet(ans.nft_owner_private_key, provider); 38 | 39 | const nftContract = (await ethers.getContractFactory('OmnuumNFT721')).attach(ans.nftContractAddress); 40 | const txResponse = await nftContract.connect(nftOwnerSigner).setRevealed(ans.base_uri); 41 | const txReceipt = await txResponse.wait(); 42 | 43 | console.log(txReceipt); 44 | console.log(`💋 Reveal is set.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 45 | } catch (e) { 46 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /scripts/interactions/nft/changeBaseURI.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | nftContractAddress: 'nftContractAddress', 7 | nft_owner_private_key: 'nft_owner_private_key', 8 | nft_address: 'nft_address', 9 | base_uri: 'base_uri', 10 | }; 11 | 12 | const questions = [ 13 | { 14 | name: inquirerParams.nftContractAddress, 15 | type: 'input', 16 | message: '🤔 nft contract address is...', 17 | validate: nullCheck, 18 | }, 19 | { 20 | name: inquirerParams.nft_owner_private_key, 21 | type: 'input', 22 | message: '🤔 NFT project owner private key is ...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.base_uri, 27 | type: 'input', 28 | message: '🤔 Base Uri to be changed is ...', 29 | validate: nullCheck, 30 | }, 31 | ]; 32 | 33 | (async () => { 34 | inquirer.prompt(questions).then(async (ans) => { 35 | try { 36 | const provider = await getRPCProvider(ethers.provider); 37 | const nftOwnerSigner = new ethers.Wallet(ans.nft_owner_private_key, provider); 38 | 39 | const nftContract = (await ethers.getContractFactory('OmnuumNFT721')).attach(ans.nftContractAddress); 40 | const txResponse = await nftContract.connect(nftOwnerSigner).changeBaseURI(ans.base_uri); 41 | 42 | const txReceipt = await txResponse.wait(); 43 | 44 | console.log(txReceipt); 45 | console.log(`💋 Base uri is changed.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 46 | } catch (e) { 47 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 48 | } 49 | }); 50 | })(); 51 | -------------------------------------------------------------------------------- /scripts/interactions/caManager/removeManager.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { getRPCProvider, nullCheck } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | dev_deployer_private_key: 'dev_deployer_private_key', 7 | ca_manager_address: 'ca_manager_address', 8 | remove_manager_Address: 'remove_manager_Address', 9 | }; 10 | 11 | const questions = [ 12 | { 13 | name: inquirerParams.dev_deployer_private_key, 14 | type: 'input', 15 | message: '🤔 Dev deployer private key is ...', 16 | validate: nullCheck, 17 | }, 18 | { 19 | name: inquirerParams.ca_manager_address, 20 | type: 'input', 21 | message: '🤔 CA manager proxy address is...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.remove_manager_Address, 26 | type: 'input', 27 | message: '🤔 Manager address to be removed...', 28 | validate: nullCheck, 29 | }, 30 | ]; 31 | 32 | (async () => { 33 | inquirer.prompt(questions).then(async (ans) => { 34 | try { 35 | const provider = await getRPCProvider(ethers.provider); 36 | const devDeployerSigner = new ethers.Wallet(ans.dev_deployer_private_key, provider); 37 | 38 | const caManager = (await ethers.getContractFactory('OmnuumCAManager')).attach(ans.ca_manager_address); 39 | 40 | const txResponse = await caManager.connect(devDeployerSigner).removeContract(ans.remove_manager_Address); 41 | const txReceipt = await txResponse.wait(); 42 | 43 | console.log(txReceipt); 44 | console.log(`💋 Manager Contract is removed..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 45 | } catch (e) { 46 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /contracts/mock/MockNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '../utils/Ownable.sol'; 5 | import '../V1/TicketManager.sol'; 6 | import '../V1/OmnuumMintManager.sol'; 7 | import '../V1/SenderVerifier.sol'; 8 | 9 | // for ticket manager 10 | contract MockNFT is Ownable { 11 | address senderVerifier; 12 | address ticketManager; 13 | 14 | constructor(address _senderVerifierAddress, address _ticketManagerAddress) { 15 | senderVerifier = _senderVerifierAddress; 16 | ticketManager = _ticketManagerAddress; 17 | } 18 | 19 | function useTicket( 20 | address _signer, 21 | address _minter, 22 | uint16 _quantity, 23 | TicketManager.Ticket calldata _ticket 24 | ) public { 25 | TicketManager(ticketManager).useTicket(_signer, _minter, _quantity, _ticket); 26 | } 27 | 28 | function publicMint( 29 | address _target, 30 | uint16 _groupId, 31 | uint32 _quantity, 32 | uint256 value, 33 | address _minter 34 | ) public { 35 | OmnuumMintManager(_target).preparePublicMint(_groupId, _quantity, value, _minter); 36 | } 37 | 38 | function publicContractMint( 39 | address payable _target, 40 | uint16 _groupId, 41 | uint32 _quantity, 42 | SenderVerifier.Payload calldata _payload 43 | ) public payable { 44 | OmnuumNFT721(_target).publicMint(_quantity, _groupId, _payload); 45 | } 46 | 47 | function ticketContractMint( 48 | address payable _target, 49 | uint32 _quantity, 50 | TicketManager.Ticket calldata _ticket, 51 | SenderVerifier.Payload calldata _payload 52 | ) public payable { 53 | OmnuumNFT721(_target).ticketMint(_quantity, _ticket, _payload); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /scripts/interactions/wallet/approvalRequest.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | requester_private_key: 'requester_private_key', 7 | wallet_address: 'wallet_address', 8 | withdrawal_value: 'withdrawal_value', 9 | }; 10 | 11 | const questions = [ 12 | { 13 | name: inquirerParams.requester_private_key, 14 | type: 'input', 15 | message: '🤔 Requester private key is ...', 16 | validate: nullCheck, 17 | }, 18 | { 19 | name: inquirerParams.wallet_address, 20 | type: 'input', 21 | message: '🤔 Wallet contract address is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.withdrawal_value, 26 | type: 'input', 27 | message: '🤔 How much money do you want to withdrawal is ... (unit: ETH)', 28 | validate: nullCheck, 29 | }, 30 | ]; 31 | 32 | (async () => { 33 | inquirer.prompt(questions).then(async (ans) => { 34 | try { 35 | const provider = await getRPCProvider(ethers.provider); 36 | const requesterSigner = new ethers.Wallet(ans.requester_private_key, provider); 37 | 38 | const wallet = (await ethers.getContractFactory('OmnuumWallet')).attach(ans.wallet_address); 39 | 40 | const txResponse = await wallet 41 | .connect(requesterSigner) 42 | .approvalRequest(ethers.utils.parseEther(ans.withdrawal_value), { gasLimit: 10000000 }); 43 | const txReceipt = await txResponse.wait(); 44 | 45 | console.log(txReceipt); 46 | console.log(`💋 Approval request is on the way..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 47 | } catch (e) { 48 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 49 | } 50 | }); 51 | })(); 52 | -------------------------------------------------------------------------------- /utils/wallet.js: -------------------------------------------------------------------------------- 1 | const Mnemonic = require('bitcore-mnemonic'); 2 | const chalk = require('chalk'); 3 | const ethers = require('ethers'); 4 | const { map, range, go } = require('fxjs'); 5 | const assert = require('assert'); 6 | 7 | // console.log(ethers.wordlists.en.getWord(2047)); 8 | 9 | const createWalletPath = (i) => `m/44'/60'/0'/0/${i}`; 10 | 11 | function createWallet() { 12 | // assert.deepStrictEqual( 13 | // Mnemonic.Words.ENGLISH, 14 | // go( 15 | // range(2048), 16 | // map((idx) => ethers.wordlists.en.getWord(idx)), 17 | // ), 18 | // ); 19 | 20 | const mnemonic = new Mnemonic(256); 21 | const xpriv = mnemonic.toHDPrivateKey(); 22 | 23 | console.log( 24 | ` 25 | \t${chalk.red('mnenomic')}: 26 | 27 | \tA: ${mnemonic.toString().split(' ').slice(0, 8)} 28 | 29 | \tB: ${mnemonic.toString().split(' ').slice(8, 16)} 30 | 31 | \tC: ${mnemonic.toString().split(' ').slice(16, 24)} 32 | 33 | \t${chalk.green('xpriv')}: ${xpriv} 34 | `, 35 | ); 36 | 37 | const wallet = ethers.Wallet.fromMnemonic(mnemonic.toString()); 38 | console.log(`${chalk.green('private key')}: ${wallet.privateKey}`); 39 | console.log(`${chalk.green('public key')}: ${wallet.publicKey}`); 40 | console.log(`${chalk.green('wallet address')}: ${wallet.address}`); 41 | console.log(`${chalk.green('recovery phrase')}: ${mnemonic.toString()}`); 42 | console.log(wallet.address.substring(0, 4).toLowerCase()); 43 | if (wallet.address.substring(0, 4).toLowerCase() !== '0xc1') { 44 | createWallet(); 45 | } 46 | } 47 | 48 | function getPkFromMnemonic(mnemonic) { 49 | const wallet = ethers.Wallet.fromMnemonic(mnemonic, createWalletPath(0), ethers.wordlists.en); 50 | 51 | console.log('pk', wallet.privateKey); 52 | 53 | console.log( 54 | ` 55 | \t${chalk.white('Wallet(0):')} ${wallet.address}`, 56 | ); 57 | } 58 | 59 | createWallet(); 60 | // getPkFromMnemonic(); 61 | -------------------------------------------------------------------------------- /scripts/interactions/nft/transferBalance.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | nft_contract_address: 'nft_contract_address', 7 | nft_project_owner_pk: 'nft_project_owner_pk', 8 | to: 'to', 9 | send_value: 'send_value', 10 | }; 11 | 12 | const questions = [ 13 | { 14 | name: inquirerParams.nft_contract_address, 15 | type: 'input', 16 | message: '🤔 NFT contract address is ...', 17 | validate: nullCheck, 18 | }, 19 | { 20 | name: inquirerParams.nft_project_owner_pk, 21 | type: 'input', 22 | message: '🤔 NFT Project Owner private key is ...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.to, 27 | type: 'input', 28 | message: '🤔 Receiver address is...', 29 | validate: nullCheck, 30 | }, 31 | { 32 | name: inquirerParams.send_value, 33 | type: 'input', 34 | message: '🤔 Amount of Ether you want to send is (in ether)...', 35 | validate: nullCheck, 36 | }, 37 | ]; 38 | 39 | (async () => { 40 | inquirer.prompt(questions).then(async (ans) => { 41 | try { 42 | const provider = await getRPCProvider(ethers.provider); 43 | const nftProjectOwnerSigner = new ethers.Wallet(ans.nft_project_owner_pk, provider); 44 | const nftContract = (await ethers.getContractFactory('OmnuumNFT721')).attach(ans.nft_contract_address); 45 | 46 | const txResponse = await nftContract.connect(nftProjectOwnerSigner).transferBalance(ethers.utils.parseEther(ans.send_value), ans.to); 47 | const txReceipt = await txResponse.wait(); 48 | 49 | console.log(txReceipt); 50 | console.log(`💋 Balance is transferred to ${ans.to}. \nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 51 | } catch (e) { 52 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 53 | } 54 | }); 55 | })(); 56 | -------------------------------------------------------------------------------- /scripts/interactions/caManager/registerManager.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { getRPCProvider, nullCheck } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | dev_deployer_private_key: 'dev_deployer_private_key', 7 | ca_manager_address: 'ca_manager_address', 8 | tobe_register_Address: 'tobe_register_Address', 9 | topic: 'topic', 10 | }; 11 | 12 | const questions = [ 13 | { 14 | name: inquirerParams.dev_deployer_private_key, 15 | type: 'input', 16 | message: '🤔 Dev deployer private key is ...', 17 | validate: nullCheck, 18 | }, 19 | { 20 | name: inquirerParams.ca_manager_address, 21 | type: 'input', 22 | message: '🤔 CA manager proxy address is...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.tobe_register_Address, 27 | type: 'input', 28 | message: '🤔 New Manager address you want to register...', 29 | validate: nullCheck, 30 | }, 31 | { 32 | name: inquirerParams.topic, 33 | type: 'input', 34 | message: '🤔 Contract topic is...', 35 | validate: nullCheck, 36 | }, 37 | ]; 38 | 39 | (async () => { 40 | inquirer.prompt(questions).then(async (ans) => { 41 | try { 42 | const provider = await getRPCProvider(ethers.provider); 43 | const devDeployerSigner = new ethers.Wallet(ans.dev_deployer_private_key, provider); 44 | 45 | const caManager = (await ethers.getContractFactory('OmnuumCAManager')).attach(ans.ca_manager_address); 46 | 47 | const txResponse = await caManager.connect(devDeployerSigner).registerContract(ans.tobe_register_Address, ans.topic); 48 | const txReceipt = await txResponse.wait(); 49 | 50 | console.log(txReceipt); 51 | console.log(`💋 Manager Contract is registered..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 52 | } catch (e) { 53 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 54 | } 55 | }); 56 | })(); 57 | -------------------------------------------------------------------------------- /scripts/deployments/deployWallet.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const chalk = require('chalk'); 3 | const { getRPCProvider, createWalletOwnerAccounts, deployConsole, getChainName } = require('./deployHelper'); 4 | const DEP_CONSTANTS = require('./deployConstants'); 5 | 6 | (async () => { 7 | console.log(` 8 | ** ** ** ** ** ******** ********** 9 | /** /** **** /** /** /**///// /////**/// 10 | /** * /** **//** /** /** /** /** 11 | /** *** /** ** //** /** /** /******* /** 12 | /** **/**/** ********** /** /** /**//// /** 13 | /**** //**** /**//////** /** /** /** /** 14 | /**/ ///** /** /** /******** /******** /******** /** 15 | // // // // //////// //////// //////// // 16 | `); 17 | 18 | console.log(`${chalk.blueBright(`START DEPLOYMENT to ${await getChainName()} at ${new Date()}`)}`); 19 | 20 | const OmnuumDeploySigner = await new ethers.Wallet(process.env.OMNUUM_DEPLOYER_PRIVATE_KEY, await getRPCProvider(ethers.provider)); 21 | 22 | const walletOwnerAccounts = createWalletOwnerAccounts(DEP_CONSTANTS.wallet.ownerAddresses, DEP_CONSTANTS.wallet.ownerLevels); 23 | 24 | console.log('Omnuum Owners Club\n', walletOwnerAccounts); 25 | 26 | const contractName = 'OmnuumWallet'; 27 | 28 | const contractFactory = await ethers.getContractFactory(contractName); 29 | 30 | const contract = await contractFactory 31 | .connect(OmnuumDeploySigner) 32 | .deploy(DEP_CONSTANTS.wallet.consensusRatio, DEP_CONSTANTS.wallet.minLimitForConsensus, walletOwnerAccounts); 33 | 34 | const txResponse = await contract.deployed(); 35 | const deployTxReceipt = await txResponse.deployTransaction.wait(DEP_CONSTANTS.confirmWait); 36 | const { gasUsed, blockNumber } = deployTxReceipt; 37 | 38 | deployConsole(contractName, contract.address, gasUsed, txResponse.deployTransaction.hash, blockNumber); 39 | })(); 40 | -------------------------------------------------------------------------------- /scripts/interactions/caManager/roleAddition.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { getRPCProvider, nullCheck } = require('../../deployments/deployHelper'); 4 | const Constants = require('../../../utils/constants'); 5 | 6 | const inquirerParams = { 7 | dev_deployer_private_key: 'dev_deployer_private_key', 8 | ca_manager_address: 'ca_manager_address', 9 | role_register_contract_address: 'role_register_contract_address', 10 | role: 'role', 11 | }; 12 | 13 | const questions = [ 14 | { 15 | name: inquirerParams.dev_deployer_private_key, 16 | type: 'input', 17 | message: '🤔 Dev deployer private key is ...', 18 | validate: nullCheck, 19 | }, 20 | { 21 | name: inquirerParams.ca_manager_address, 22 | type: 'input', 23 | message: '🤔 CA manager proxy address is...', 24 | validate: nullCheck, 25 | }, 26 | { 27 | name: inquirerParams.role_register_contract_address, 28 | type: 'input', 29 | message: '🤔 Contract address you want to register role...', 30 | validate: nullCheck, 31 | }, 32 | { 33 | name: inquirerParams.role, 34 | type: 'input', 35 | message: '🤔 Contract role is...', 36 | validate: nullCheck, 37 | }, 38 | ]; 39 | 40 | (async () => { 41 | inquirer.prompt(questions).then(async (ans) => { 42 | try { 43 | const provider = await getRPCProvider(ethers.provider); 44 | const devDeployerSigner = new ethers.Wallet(ans.dev_deployer_private_key, provider); 45 | 46 | const caManager = (await ethers.getContractFactory('OmnuumCAManager')).attach(ans.ca_manager_address); 47 | 48 | const txResponse = await caManager.connect(devDeployerSigner).addRole([ans.role_register_contract_address], ans.role); 49 | const txReceipt = await txResponse.wait(); 50 | 51 | console.log(txReceipt); 52 | console.log(`💋 Role has been added.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 53 | } catch (e) { 54 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 55 | } 56 | }); 57 | })(); 58 | -------------------------------------------------------------------------------- /scripts/interactions/caManager/roleRemoval.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { getRPCProvider, nullCheck } = require('../../deployments/deployHelper'); 4 | const Constants = require('../../../utils/constants'); 5 | 6 | const inquirerParams = { 7 | dev_deployer_private_key: 'dev_deployer_private_key', 8 | ca_manager_address: 'ca_manager_address', 9 | role_register_contract_address: 'role_register_contract_address', 10 | role: 'role', 11 | }; 12 | 13 | const questions = [ 14 | { 15 | name: inquirerParams.dev_deployer_private_key, 16 | type: 'input', 17 | message: '🤔 Dev deployer private key is ...', 18 | validate: nullCheck, 19 | }, 20 | { 21 | name: inquirerParams.ca_manager_address, 22 | type: 'input', 23 | message: '🤔 CA manager proxy address is...', 24 | validate: nullCheck, 25 | }, 26 | { 27 | name: inquirerParams.role_register_contract_address, 28 | type: 'input', 29 | message: '🤔 Contract address you want to remove role...', 30 | validate: nullCheck, 31 | }, 32 | { 33 | name: inquirerParams.role, 34 | type: 'input', 35 | message: '🤔 Contract role is...', 36 | validate: nullCheck, 37 | }, 38 | ]; 39 | 40 | (async () => { 41 | inquirer.prompt(questions).then(async (ans) => { 42 | try { 43 | const provider = await getRPCProvider(ethers.provider); 44 | const devDeployerSigner = new ethers.Wallet(ans.dev_deployer_private_key, provider); 45 | 46 | const caManager = (await ethers.getContractFactory('OmnuumCAManager')).attach(ans.ca_manager_address); 47 | 48 | const txResponse = await caManager.connect(devDeployerSigner).removeRole([ans.role_register_contract_address], ans.role); 49 | const txReceipt = await txResponse.wait(); 50 | 51 | console.log(txReceipt); 52 | console.log(`💋 Role has been removed.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 53 | } catch (e) { 54 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 55 | } 56 | }); 57 | })(); 58 | -------------------------------------------------------------------------------- /contracts/utils/OwnableUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol'; 5 | import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 6 | 7 | contract OwnableUpgradeable is Initializable, ContextUpgradeable { 8 | address private _owner; 9 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 10 | 11 | /** 12 | * @dev Initializes the contract setting the deployer as the initial owner. 13 | */ 14 | function __Ownable_init() internal onlyInitializing { 15 | __Context_init_unchained(); 16 | __Ownable_init_unchained(); 17 | } 18 | 19 | function __Ownable_init_unchained() internal onlyInitializing { 20 | _transferOwnership(tx.origin); 21 | } 22 | 23 | /** 24 | * @dev Returns the address of the current owner. 25 | */ 26 | function owner() public view virtual returns (address) { 27 | return _owner; 28 | } 29 | 30 | /** 31 | * @dev Throws if called by any account other than the owner. 32 | */ 33 | modifier onlyOwner() { 34 | require(owner() == _msgSender(), 'Ownable: caller is not the owner'); 35 | _; 36 | } 37 | 38 | /** 39 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 40 | * Can only be called by the current owner. 41 | */ 42 | function transferOwnership(address newOwner) public virtual onlyOwner { 43 | require(newOwner != address(0), 'Ownable: new owner is the zero address'); 44 | _transferOwnership(newOwner); 45 | } 46 | 47 | /** 48 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 49 | * Internal function without access restriction. 50 | */ 51 | function _transferOwnership(address newOwner) internal virtual { 52 | address oldOwner = _owner; 53 | _owner = newOwner; 54 | emit OwnershipTransferred(oldOwner, newOwner); 55 | } 56 | 57 | uint256[49] private __gap; 58 | } 59 | -------------------------------------------------------------------------------- /scripts/interactions/caManager/registerNFT.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { getRPCProvider, nullCheck } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | dev_deployer_private_key: 'dev_deployer_private_key', 7 | ca_manager_address: 'ca_manager_address', 8 | tobe_register_nft_Address: 'tobe_register_nft_Address', 9 | nft_project_owner: 'nft_project_owner', 10 | }; 11 | 12 | const questions = [ 13 | { 14 | name: inquirerParams.dev_deployer_private_key, 15 | type: 'input', 16 | message: '🤔 Dev deployer private key is ...', 17 | validate: nullCheck, 18 | }, 19 | { 20 | name: inquirerParams.ca_manager_address, 21 | type: 'input', 22 | message: '🤔 CA manager proxy address is...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.tobe_register_nft_Address, 27 | type: 'input', 28 | message: '🤔 New NFT address you want to register...', 29 | validate: nullCheck, 30 | }, 31 | { 32 | name: inquirerParams.nft_project_owner, 33 | type: 'input', 34 | message: '🤔 NFT project owner address is...', 35 | validate: nullCheck, 36 | }, 37 | ]; 38 | 39 | (async () => { 40 | inquirer.prompt(questions).then(async (ans) => { 41 | try { 42 | const provider = await getRPCProvider(ethers.provider); 43 | const devDeployerSigner = new ethers.Wallet(ans.dev_deployer_private_key, provider); 44 | 45 | const caManager = (await ethers.getContractFactory('OmnuumCAManager')).attach(ans.ca_manager_address); 46 | 47 | const txResponse = await caManager 48 | .connect(devDeployerSigner) 49 | .registerNftContract(ans.tobe_register_nft_Address, ans.nft_project_owner); 50 | const txReceipt = await txResponse.wait(); 51 | 52 | console.log(txReceipt); 53 | console.log(`💋 NFT Contract is registered..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 54 | } catch (e) { 55 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 56 | } 57 | }); 58 | })(); 59 | -------------------------------------------------------------------------------- /deploy_history/mainnet_5-19-2022_14:00:00.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "mainnet", 3 | "deployStartAt": "2:00:00 PM", 4 | "deployer": "0x84803BBd4269f596D94e88fA4E697b21C39370d2", 5 | "mintManager": { 6 | "proxy": "0x2D24c1C8677870d320C10aE7b840A279A0CAF492", 7 | "impl": "0x44eBfaF325961eeA4E1ef7969A327893C796513A", 8 | "admin": "0x6d82f1895bdAe7B6FEA3c57F44fc281AbB6BB649", 9 | "gasUsed": 2152868, 10 | "blockNumber": 14803042, 11 | "address": "0x2D24c1C8677870d320C10aE7b840A279A0CAF492" 12 | }, 13 | "wallet": { 14 | "contract": "0x6DEc671d53F33a4a7314Ea596A8E892F18ecEc91", 15 | "address": "0x6DEc671d53F33a4a7314Ea596A8E892F18ecEc91", 16 | "gasUsed": 3875571, 17 | "blockNumber": 14802811, 18 | "args": [ 19 | 55, 20 | 3, 21 | [ 22 | { "addr": "0x95254b0F9eFc20295AE423373cd105EE8b5a717F", "vote": 2 }, 23 | { "addr": "0x0bb5708C4f71439DB00027F85D87f0232acDeEfA", "vote": 2 }, 24 | { "addr": "0x734724b6bAaF3C407298d24d98ece1E0fA077ffd", "vote": 1 }, 25 | { "addr": "0xDfe2eFA72E86397728c885D91A8786aA9FdE7A24", "vote": 1 }, 26 | { "addr": "0x4E2d208C5f7DB881Ee871F5DF4a23DFDb0e77b58", "vote": 1 } 27 | ] 28 | ] 29 | }, 30 | "revealManager": { 31 | "contract": "0xb2Cff4c533C95fd5B77Ad246539afc5751FA2B4b", 32 | "address": "0xb2Cff4c533C95fd5B77Ad246539afc5751FA2B4b", 33 | "gasUsed": 342309, 34 | "blockNumber": 14802809, 35 | "args": ["0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab"] 36 | }, 37 | "nft721": { 38 | "impl": "0x2359f095fb44b3a8fc642587f006fb5bdee5ac5a", 39 | "beacon": "0xD4d7fd222Ccc3b574Cd6CA7DF632df1DB09ad388", 40 | "address": "0xD4d7fd222Ccc3b574Cd6CA7DF632df1DB09ad388" 41 | }, 42 | "nftFactory": { 43 | "contract": "0x0efe513723df57fa00675a00Bca3C54Cd87792Fb", 44 | "address": "0x0efe513723df57fa00675a00Bca3C54Cd87792Fb", 45 | "gasUsed": 2016818, 46 | "blockNumber": 14802980, 47 | "args": [ 48 | "0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab", 49 | "0xd4d7fd222ccc3b574cd6ca7df632df1db09ad388", 50 | "0x81876853baef4001B844B11dF010E9647b7c9a2b" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripts/deployments/deployConstants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Please check, double-check, triple-check When the time you deploy contracts to MAINNET 3 | * */ 4 | 5 | const { ethers } = require('hardhat'); 6 | 7 | const DEP_CONSTANTS = { 8 | confirmWait: 1, 9 | pollingInterval: 600000, 10 | OmnuumDeployer: process.env.OMNUUM_DEPLOYER_PRIVATE_KEY, 11 | mintManager: { 12 | feeRate: 5000, // feeRate: 5.000 % (default) 13 | }, 14 | wallet: { 15 | consensusRatio: 55, 16 | minLimitForConsensus: 3, 17 | ownerLevels: [2, 2, 1, 1, 1], 18 | ownerAddresses: [ 19 | process.env.WALLET_OWNER_A_ADDR, 20 | process.env.WALLET_OWNER_B_ADDR, 21 | process.env.WALLET_OWNER_C_ADDR, 22 | process.env.WALLET_OWNER_D_ADDR, 23 | process.env.WALLET_OWNER_E_ADDR, 24 | ], 25 | }, 26 | caManager: { 27 | topic: 'CAMANAGER', 28 | }, 29 | vrfManager: { 30 | chainlink: { 31 | mainnet: { 32 | LINK: '0x514910771AF9Ca656af840dff83E8264EcF986CA', 33 | COORD: '0xf0d54349aDdcf704F77AE15b96510dEA15cb7952', 34 | hash: '0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445', 35 | fee: ethers.utils.parseEther('2'), 36 | }, 37 | rinkeby: { 38 | LINK: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709', 39 | COORD: '0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B', 40 | hash: '0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311', 41 | fee: ethers.utils.parseEther('0.1'), 42 | }, 43 | localhost: { 44 | LINK: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709', 45 | COORD: '0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B', 46 | hash: '0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311', 47 | fee: ethers.utils.parseEther('0.1'), 48 | }, 49 | goerli: { 50 | LINK: '0x326C977E6efc84E512bB9C30f76E30c160eD06FB', 51 | COORD: '0x2bce784e69d2Ff36c71edcB9F88358dB0DfB55b4', 52 | hash: '0x0476f9a745b61ea5c0ab224d3a6e4c99f0b02fce4da01143a4f70aa80ae76e8a', 53 | fee: ethers.utils.parseEther('0.1'), 54 | }, 55 | }, 56 | }, 57 | roles: { 58 | EXCHANGE: 'EXCHANGE', 59 | VRF: 'VRF', 60 | }, 61 | }; 62 | 63 | module.exports = DEP_CONSTANTS; 64 | -------------------------------------------------------------------------------- /scripts/interactions/wallet/payment.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | 5 | const inquirerParams = { 6 | wallet_address: 'wallet_address', 7 | ether_sender_private_key: 'ether_sender_private_key', 8 | payment_value: 'payment_value', 9 | payment_topic: 'payment_topic', 10 | payment_description: 'payment_description', 11 | }; 12 | 13 | const questions = [ 14 | { 15 | name: inquirerParams.wallet_address, 16 | type: 'input', 17 | message: '🤔 Wallet contract address is ...', 18 | validate: nullCheck, 19 | }, 20 | { 21 | name: inquirerParams.ether_sender_private_key, 22 | type: 'input', 23 | message: '🤔 Ether sender private key is ...', 24 | validate: nullCheck, 25 | }, 26 | { 27 | name: inquirerParams.payment_value, 28 | type: 'input', 29 | message: '🤔 Amount of payment is (in ether)...', 30 | validate: nullCheck, 31 | }, 32 | { 33 | name: inquirerParams.payment_topic, 34 | type: 'input', 35 | message: '🤔 What is payment topic?', 36 | validate: nullCheck, 37 | }, 38 | { 39 | name: inquirerParams.payment_description, 40 | type: 'input', 41 | message: '🤔 What is payment description?', 42 | validate: nullCheck, 43 | }, 44 | ]; 45 | 46 | (async () => { 47 | inquirer.prompt(questions).then(async (ans) => { 48 | try { 49 | const provider = await getRPCProvider(ethers.provider); 50 | const senderSigner = new ethers.Wallet(ans.ether_sender_private_key, provider); 51 | const wallet = (await ethers.getContractFactory('OmnuumWallet')).attach(ans.wallet_address); 52 | const txResponse = await wallet 53 | .connect(senderSigner) 54 | .makePayment(ans.payment_topic, ans.payment_description, { value: ethers.utils.parseEther(ans.payment_value) }); 55 | 56 | const txReceipt = await txResponse.wait(); 57 | 58 | console.log(txReceipt); 59 | console.log(`💋 Pay to Wallet. \nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 60 | } catch (e) { 61 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 62 | } 63 | }); 64 | })(); 65 | -------------------------------------------------------------------------------- /scripts/interactions/ticketManager/setEndDate.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { addHours } = require('date-fns'); 4 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 5 | const { toSolDate } = require('../../../test/etc/util.js'); 6 | 7 | const inquirerParams = { 8 | nft_owner_private_key: 'nft_owner_private_key', 9 | nft_address: 'nft_address', 10 | group_id: 'group_id', 11 | end_day_from_now: 'end_day_from_now', 12 | ticket_manager_address: 'ticket_manager_address', 13 | }; 14 | 15 | const questions = [ 16 | { 17 | name: inquirerParams.nft_owner_private_key, 18 | type: 'input', 19 | message: '🤔 NFT project owners private key is ...', 20 | validate: nullCheck, 21 | }, 22 | { 23 | name: inquirerParams.nft_address, 24 | type: 'input', 25 | message: '🤔 NFT project contract address is...', 26 | validate: nullCheck, 27 | }, 28 | { 29 | name: inquirerParams.ticket_manager_address, 30 | type: 'input', 31 | message: '🤔 Ticket manager address is...', 32 | validate: nullCheck, 33 | }, 34 | { 35 | name: inquirerParams.group_id, 36 | type: 'input', 37 | message: '🤔 Ticket schedule group id is... (uint: dec)', 38 | validate: nullCheck, 39 | }, 40 | { 41 | name: inquirerParams.end_day_from_now, 42 | type: 'input', 43 | message: '🤔 How much end day from now is... (unit: hr)', 44 | validate: nullCheck, 45 | }, 46 | ]; 47 | 48 | (async () => { 49 | inquirer.prompt(questions).then(async (ans) => { 50 | try { 51 | const provider = await getRPCProvider(ethers.provider); 52 | const nftOwnerSigner = new ethers.Wallet(ans.nft_owner_private_key, provider); 53 | 54 | const ticketManager = (await ethers.getContractFactory('TicketManager')).attach(ans.ticket_manager_address); 55 | 56 | const txResponse = await ticketManager 57 | .connect(nftOwnerSigner) 58 | .setEndDate(ans.nft_address, ans.group_id, toSolDate(addHours(new Date(), Number(ans.end_day_from_now)))); 59 | 60 | const txReceipt = await txResponse.wait(); 61 | 62 | console.log(txReceipt); 63 | console.log(`💋 Ticket schedule is set..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 64 | } catch (e) { 65 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 66 | } 67 | }); 68 | })(); 69 | -------------------------------------------------------------------------------- /utils/transaction.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const chalk = require('chalk'); 3 | const { getWalletFromMnemonic } = require('./walletFromMnemonic.js'); 4 | const { getRPCProvider, getChainName } = require('../scripts/deployments/deployHelper.js'); 5 | 6 | async function sendMoney(toAddress, amountInEther, gasPrices) { 7 | try { 8 | let provider; 9 | if (gasPrices) { 10 | // Wrap the provider so we can override fee data. 11 | provider = new ethers.providers.FallbackProvider([ethers.provider], 1); 12 | const FEE_DATA = { 13 | maxFeePerGas: ethers.utils.parseUnits(gasPrices.maxFeePerGas, 'gwei'), 14 | maxPriorityFeePerGas: ethers.utils.parseUnits(gasPrices.maxPriorityFeePerGas, 'gwei'), 15 | }; 16 | provider.getFeeData = async () => FEE_DATA; 17 | } else { 18 | provider = await getRPCProvider(ethers.provider); 19 | } 20 | 21 | const chainName = await getChainName(); 22 | 23 | console.log(`chainName: ${chainName}`); 24 | 25 | const senderSigner = chainName === 'localhost' ? (await ethers.getSigners())[0] : await getWalletFromMnemonic(provider); 26 | 27 | const senderAddress = await senderSigner.getAddress(); 28 | 29 | console.log(` 30 | address: ${senderAddress} 31 | balance: ${await ethers.provider.getBalance(senderAddress)} 32 | `); 33 | 34 | const tx = await senderSigner.sendTransaction({ 35 | to: toAddress, 36 | value: ethers.utils.parseEther(amountInEther), 37 | }); 38 | 39 | const receipt = await tx.wait(); 40 | 41 | const senderBalance = await senderSigner.getBalance(); 42 | const toBalance = await provider.getBalance(toAddress); 43 | 44 | console.log(`Transaction Complete! 45 | 46 | \t${chalk.green('Hash')}: ${receipt.transactionHash} 47 | \t${chalk.green('GasUsed')}: ${receipt.gasUsed} 48 | \t${chalk.green('BlockNumber')}: ${receipt.blockNumber} 49 | \t${chalk.green('GasPrice')}: ${ethers.utils.formatUnits(receipt.effectiveGasPrice, 'gwei')} gwei 50 | \t${chalk.green('Value')}: ${amountInEther} ether 51 | \t${chalk.green('Balance')}: 52 | \t\treceiver (${ethers.utils.formatEther(toBalance.toString())} ether) 53 | \t\tsender (${ethers.utils.formatEther(senderBalance.toString())} ether) 54 | 55 | `); 56 | } catch (err) { 57 | console.error(err); 58 | } 59 | } 60 | 61 | // sendMoney('0x70997970c51812dc3a010c7d01b50e0d17dc79c8', '100'); 62 | // sendMoney('0x8a54AaBCccf6299f138Ff3cabe6F637716449EB4', '0.15'); 63 | -------------------------------------------------------------------------------- /test/etc/util.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const { go, filter, keys, map, object } = require('fxjs'); 3 | 4 | module.exports = { 5 | waitTx(sign) { 6 | return sign.then((tx) => tx.wait()); 7 | }, 8 | nullAddress: '0x0000000000000000000000000000000000000000', 9 | async signPayload(sender, topic, nonce, signer, verifierAddress) { 10 | const SIGNING_DOMAIN_NAME = 'Omnuum'; 11 | const SIGNING_DOMAIN_VERSION = '1'; 12 | 13 | const types = { 14 | Payload: [ 15 | { name: 'sender', type: 'address' }, 16 | { name: 'topic', type: 'string' }, 17 | { name: 'nonce', type: 'uint256' }, 18 | ], 19 | }; 20 | 21 | const domain = { 22 | name: SIGNING_DOMAIN_NAME, 23 | version: SIGNING_DOMAIN_VERSION, 24 | verifyingContract: verifierAddress, 25 | chainId: (await ethers.provider.getNetwork()).chainId, 26 | }; 27 | 28 | const data = { sender, topic, nonce }; 29 | 30 | const signature = await signer._signTypedData(domain, types, data); 31 | 32 | return { ...data, signature }; 33 | }, 34 | async createTicket(data, signer, verifierAddress) { 35 | const SIGNING_DOMAIN_NAME = 'OmnuumTicket'; 36 | const SIGNING_DOMAIN_VERSION = '1'; 37 | 38 | const types = { 39 | Ticket: [ 40 | { name: 'user', type: 'address' }, 41 | { name: 'nft', type: 'address' }, 42 | { name: 'price', type: 'uint256' }, 43 | { name: 'quantity', type: 'uint32' }, 44 | { name: 'groupId', type: 'uint256' }, 45 | ], 46 | }; 47 | 48 | const domain = { 49 | name: SIGNING_DOMAIN_NAME, 50 | version: SIGNING_DOMAIN_VERSION, 51 | verifyingContract: verifierAddress, 52 | chainId: (await ethers.provider.getNetwork()).chainId, 53 | }; 54 | 55 | const signature = await signer._signTypedData(domain, types, data); 56 | 57 | return { ...data, signature }; 58 | }, 59 | async isLocalNetwork(provider) { 60 | const { chainId } = await provider.getNetwork(); 61 | return chainId != 1 && chainId != 4; 62 | }, 63 | calcGasFeeInEther(receipt) { 64 | return receipt.gasUsed.mul(receipt.effectiveGasPrice); 65 | }, 66 | toSolDate(date) { 67 | return Math.floor(date / 1000); 68 | }, 69 | parseEvent(ifaces, receipt) { 70 | return receipt.logs.map((log, idx) => ifaces[idx].parseLog(log)); 71 | }, 72 | parseStruct(struct) { 73 | return go( 74 | struct, 75 | keys, 76 | filter((k) => Number.isNaN(+k)), 77 | map((k) => [k, struct[k]]), 78 | object, 79 | ); 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /contracts/V1/OperatorFilterRegistry/IOperatorFilterRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | interface IOperatorFilterRegistry { 5 | function isOperatorAllowed(address registrant, address operator) external view returns (bool); 6 | 7 | function register(address registrant) external; 8 | 9 | function registerAndSubscribe(address registrant, address subscription) external; 10 | 11 | function registerAndCopyEntries(address registrant, address registrantToCopy) external; 12 | 13 | function unregister(address addr) external; 14 | 15 | function updateOperator( 16 | address registrant, 17 | address operator, 18 | bool filtered 19 | ) external; 20 | 21 | function updateOperators( 22 | address registrant, 23 | address[] calldata operators, 24 | bool filtered 25 | ) external; 26 | 27 | function updateCodeHash( 28 | address registrant, 29 | bytes32 codehash, 30 | bool filtered 31 | ) external; 32 | 33 | function updateCodeHashes( 34 | address registrant, 35 | bytes32[] calldata codeHashes, 36 | bool filtered 37 | ) external; 38 | 39 | function subscribe(address registrant, address registrantToSubscribe) external; 40 | 41 | function unsubscribe(address registrant, bool copyExistingEntries) external; 42 | 43 | function subscriptionOf(address addr) external returns (address registrant); 44 | 45 | function subscribers(address registrant) external returns (address[] memory); 46 | 47 | function subscriberAt(address registrant, uint256 index) external returns (address); 48 | 49 | function copyEntriesOf(address registrant, address registrantToCopy) external; 50 | 51 | function isOperatorFiltered(address registrant, address operator) external returns (bool); 52 | 53 | function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool); 54 | 55 | function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool); 56 | 57 | function filteredOperators(address addr) external returns (address[] memory); 58 | 59 | function filteredCodeHashes(address addr) external returns (bytes32[] memory); 60 | 61 | function filteredOperatorAt(address registrant, uint256 index) external returns (address); 62 | 63 | function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32); 64 | 65 | function isRegistered(address addr) external returns (bool); 66 | 67 | function codeHashOf(address addr) external returns (bytes32); 68 | } 69 | -------------------------------------------------------------------------------- /scripts/deployments/runDeploy.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const chalk = require('chalk'); 3 | const main = require('./index'); 4 | 5 | const { nullCheck } = require('./deployHelper'); 6 | 7 | const inquirerParams = { 8 | devDeployerPrivateKey: 'devDeployerPrivateKey', 9 | signatureSignerAddress: 'signatureSignerAddress', 10 | maxPriorityFeePerGas: 'maxPriorityFeePerGas', 11 | maxFeePerGas: 'maxFeePerGas', 12 | eip1559: 'eip1559', 13 | localSave: 'localSave', 14 | s3Save: 's3Save', 15 | }; 16 | 17 | const questions = [ 18 | { 19 | name: inquirerParams.devDeployerPrivateKey, 20 | type: 'input', 21 | message: '🤔 Dev deployer private key is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.signatureSignerAddress, 26 | type: 'input', 27 | message: '🤔 Signature signer address is (not private key, just address)...', 28 | validate: nullCheck, 29 | }, 30 | { 31 | name: inquirerParams.localSave, 32 | type: 'confirm', 33 | message: chalk.yellow(`🤔 Save result JSON file to ${chalk.redBright('local')}`), 34 | }, 35 | { 36 | name: inquirerParams.s3Save, 37 | type: 'confirm', 38 | message: chalk.yellow(`🤔 Save result JSON file to ${chalk.redBright('S3')}`), 39 | }, 40 | { 41 | name: inquirerParams.eip1559, 42 | type: 'list', 43 | message: '🤔 You wanna gas fee customizable?', 44 | choices: ['normal', 'eip1559'], 45 | }, 46 | ]; 47 | 48 | const gasQuestions = [ 49 | { 50 | name: inquirerParams.maxFeePerGas, 51 | type: 'input', 52 | message: '🤔 maxFeePerGas is ...', 53 | validate: nullCheck, 54 | }, 55 | { 56 | name: inquirerParams.maxPriorityFeePerGas, 57 | type: 'input', 58 | message: '🤔 maxPriorityFeePerGas is ...', 59 | validate: nullCheck, 60 | }, 61 | ]; 62 | 63 | (async () => { 64 | inquirer.prompt(questions).then(async (ans) => { 65 | try { 66 | if (ans.eip1559 === 'normal') { 67 | await main({ 68 | deployerPK: ans.devDeployerPrivateKey, 69 | signerAddress: ans.signatureSignerAddress, 70 | localSave: ans.localSave, 71 | s3Save: ans.s3Save, 72 | }); 73 | } else { 74 | inquirer.prompt(gasQuestions).then(async (gasAns) => { 75 | await main({ 76 | deployerPK: ans.devDeployerPrivateKey, 77 | signerAddress: ans.signatureSignerAddress, 78 | gasPrices: { 79 | maxFeePerGas: gasAns.maxFeePerGas, 80 | maxPriorityFeePerGas: gasAns.maxPriorityFeePerGas, 81 | }, 82 | localSave: ans.localSave, 83 | s3Save: ans.s3Save, 84 | }); 85 | }); 86 | } 87 | } catch (e) { 88 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 89 | } 90 | }); 91 | })(); 92 | -------------------------------------------------------------------------------- /contracts/V1/SenderVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; 5 | import '@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol'; 6 | 7 | /// @title SenderVerifier - verifier contract that payload is signed by omnuum or not 8 | /// @author Omnuum Dev Team - 9 | contract SenderVerifier is EIP712 { 10 | constructor() EIP712(SIGNING_DOMAIN, SIGNATURE_VERSION) {} 11 | 12 | string private constant SIGNING_DOMAIN = 'Omnuum'; 13 | string private constant SIGNATURE_VERSION = '1'; 14 | 15 | struct Payload { 16 | address sender; // sender or address who received this payload 17 | string topic; // topic of payload 18 | uint256 nonce; // separate same topic payload for multiple steps or checks 19 | bytes signature; // signature of this payload 20 | } 21 | 22 | /// @notice verify function 23 | /// @param _owner address who is believed to be signer of payload signature 24 | /// @param _sender address who is believed to be target of payload signature 25 | /// @param _topic topic of payload 26 | /// @param _nonce nonce of payload 27 | /// @param _payload payload struct 28 | function verify( 29 | address _owner, 30 | address _sender, 31 | string calldata _topic, 32 | uint256 _nonce, 33 | Payload calldata _payload 34 | ) public view { 35 | address signer = recoverSigner(_payload); 36 | 37 | /// @custom:error (VR1) - False Signer 38 | require(_owner == signer, 'VR1'); 39 | 40 | /// @custom:error (VR2) - False Nonce 41 | require(_nonce == _payload.nonce, 'VR2'); 42 | 43 | /// @custom:error (VR3) - False Topic 44 | require(keccak256(abi.encodePacked(_payload.topic)) == keccak256(abi.encodePacked(_topic)), 'VR3'); 45 | 46 | /// @custom:error (VR4) - False Sender 47 | require(_payload.sender == _sender, 'VR4'); 48 | } 49 | 50 | /// @dev recover signer from payload hash 51 | /// @param _payload payload struct 52 | function recoverSigner(Payload calldata _payload) internal view returns (address) { 53 | bytes32 digest = _hash(_payload); 54 | return ECDSA.recover(digest, _payload.signature); 55 | } 56 | 57 | /// @dev hash payload 58 | /// @param _payload payload struct 59 | function _hash(Payload calldata _payload) internal view returns (bytes32) { 60 | return 61 | _hashTypedDataV4( 62 | keccak256( 63 | abi.encode( 64 | keccak256('Payload(address sender,string topic,uint256 nonce)'), 65 | _payload.sender, 66 | keccak256(bytes(_payload.topic)), 67 | _payload.nonce 68 | ) 69 | ) 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /scripts/interactions/mintManager/airDrop.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 4 | const { testValues } = require('../../../utils/constants'); 5 | 6 | const inquirerParams = { 7 | nft_owner_private_key: 'nft_owner_private_key', 8 | nft_address: 'nft_address', 9 | mint_manager_address: 'mint_manager_address', 10 | airdrop_receiver_A: 'airdrop_receiver_A', 11 | quantity_to_A: 'quantity_to_A', 12 | airdrop_receiver_B: 'airdrop_receiver_B', 13 | quantity_to_B: 'quantity_to_B', 14 | estimate_gas: 'estimate_gas', 15 | }; 16 | 17 | const questions = [ 18 | { 19 | name: inquirerParams.nft_owner_private_key, 20 | type: 'input', 21 | message: '🤔 NFT project owner private key is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.nft_address, 26 | type: 'input', 27 | message: '🤔 NFT contract address is...', 28 | validate: nullCheck, 29 | }, 30 | { 31 | name: inquirerParams.mint_manager_address, 32 | type: 'input', 33 | message: '🤔 Mint manager address is...', 34 | validate: nullCheck, 35 | }, 36 | { 37 | name: inquirerParams.airdrop_receiver_A, 38 | type: 'input', 39 | message: '🤔 Airdrop receiver address is...', 40 | validate: nullCheck, 41 | }, 42 | { 43 | name: inquirerParams.quantity_to_A, 44 | type: 'input', 45 | message: '🤔 How many nfts airdrop to receiver is...', 46 | validate: nullCheck, 47 | }, 48 | { 49 | name: inquirerParams.estimate_gas, 50 | type: 'confirm', 51 | message: '🤔 Just estimate gas?', 52 | }, 53 | ]; 54 | 55 | (async () => { 56 | inquirer.prompt(questions).then(async (ans) => { 57 | try { 58 | const provider = await getRPCProvider(ethers.provider); 59 | const nftOwnerSigner = new ethers.Wallet(ans.nft_owner_private_key, provider); 60 | const mintManager = (await ethers.getContractFactory('OmnuumMintManager')).attach(ans.mint_manager_address); 61 | 62 | const value = testValues.minFee.mul(Number(ans.quantity_to_A)); 63 | const args = [ans.nft_address, [ans.airdrop_receiver_A], [ans.quantity_to_A], { value }]; 64 | 65 | if (ans.estimate_gas) { 66 | const gas = await mintManager.connect(nftOwnerSigner).estimateGas.mintMultiple(...args); 67 | console.log(`Gas Estimation: ${gas}`); 68 | return; 69 | } 70 | 71 | const txResponse = await mintManager.connect(nftOwnerSigner).mintMultiple(...args); 72 | console.log('txRseponse', txResponse); 73 | 74 | const txReceipt = await txResponse.wait(); 75 | console.log(txReceipt); 76 | console.log(`💋 Air drop is on the way.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 77 | } catch (e) { 78 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 79 | } 80 | }); 81 | })(); 82 | -------------------------------------------------------------------------------- /contracts/V1/NftFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol'; 5 | import '../utils/Ownable.sol'; 6 | import './OmnuumCAManager.sol'; 7 | import './SenderVerifier.sol'; 8 | 9 | /// @title NftFactory - Factory contract for deploying new NFT PFP projects for creators 10 | /// @author Omnuum Dev Team - 11 | contract NftFactory is Ownable { 12 | event NftContractDeployed(address indexed nftContract, address indexed creator, uint256 indexed collectionId); 13 | 14 | address public immutable caManager; 15 | address public immutable nftContractBeacon; 16 | address public omnuumSigner; 17 | 18 | /// @notice constructor 19 | /// @param _caManager CA manager address managed by Omnuum 20 | /// @param _nftContractBeacon Nft contract beacon address managed by Omnuum 21 | /// @param _omnuumSigner Address of Omnuum signer for creating and verifying off-chain ECDSA signature 22 | constructor( 23 | address _caManager, 24 | address _nftContractBeacon, 25 | address _omnuumSigner 26 | ) Ownable() { 27 | caManager = _caManager; 28 | omnuumSigner = _omnuumSigner; 29 | nftContractBeacon = _nftContractBeacon; 30 | } 31 | 32 | /// @notice deploy 33 | /// @param _maxSupply max amount can be minted 34 | /// @param _coverBaseURI metadata uri for before reveal 35 | /// @param _collectionId collection id 36 | /// @param _payload payload for signature to verify collection id 37 | function deploy( 38 | uint32 _maxSupply, 39 | string calldata _coverBaseURI, 40 | uint256 _collectionId, 41 | string calldata _name, 42 | string calldata _symbol, 43 | SenderVerifier.Payload calldata _payload 44 | ) external { 45 | address senderVerifier = OmnuumCAManager(caManager).getContract('VERIFIER'); 46 | SenderVerifier(senderVerifier).verify(omnuumSigner, msg.sender, 'DEPLOY_COL', _collectionId, _payload); 47 | 48 | /// @dev 0x9da8ac6a == keccak256('initialize(address,address,uint32,string,address,string,string)') 49 | bytes memory data = abi.encodeWithSelector( 50 | 0xc903b7f2, 51 | caManager, 52 | omnuumSigner, 53 | _maxSupply, 54 | _coverBaseURI, 55 | msg.sender, 56 | _name, 57 | _symbol 58 | ); 59 | 60 | BeaconProxy beacon = new BeaconProxy(nftContractBeacon, data); 61 | emit NftContractDeployed(address(beacon), msg.sender, _collectionId); 62 | } 63 | 64 | /// @notice changeOmnuumSigner 65 | /// @param _newSigner Address of Omnuum signer for replacing previous signer 66 | function changeOmnuumSigner(address _newSigner) external onlyOwner { 67 | require(_newSigner != address(0), 'AE1'); 68 | omnuumSigner = _newSigner; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /scripts/interactions/mintManager/setPublicMintSchedule.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | const { addHours } = require('date-fns'); 4 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 5 | const { toSolDate } = require('../../../test/etc/util.js'); 6 | 7 | const inquirerParams = { 8 | nft_owner_private_key: 'nft_owner_private_key', 9 | nft_address: 'nft_address', 10 | mint_manager_address: 'mint_manager_address', 11 | group_id: 'group_id', 12 | end_day_from_now: 'end_day_from_now', 13 | base_price: 'base_price', 14 | supply: 'supply', 15 | max_mint_at_address: 'max_mint_at_address', 16 | }; 17 | 18 | const questions = [ 19 | { 20 | name: inquirerParams.nft_owner_private_key, 21 | type: 'input', 22 | message: '🤔 NFT project owner private key is ...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.nft_address, 27 | type: 'input', 28 | message: '🤔 NFT contract address is...', 29 | validate: nullCheck, 30 | }, 31 | { 32 | name: inquirerParams.mint_manager_address, 33 | type: 'input', 34 | message: '🤔 Mint manager address is...', 35 | validate: nullCheck, 36 | }, 37 | { 38 | name: inquirerParams.group_id, 39 | type: 'input', 40 | message: '🤔 Public mint schedule group id is you want to be set...', 41 | validate: nullCheck, 42 | }, 43 | { 44 | name: inquirerParams.end_day_from_now, 45 | type: 'input', 46 | message: '🤔 How much end day from now is... (unit: hr)', 47 | validate: nullCheck, 48 | }, 49 | { 50 | name: inquirerParams.base_price, 51 | type: 'input', 52 | message: '🤔 How much is the base_price... (unit: ether)', 53 | validate: nullCheck, 54 | }, 55 | { 56 | name: inquirerParams.supply, 57 | type: 'input', 58 | message: '🤔 How much supply...', 59 | validate: nullCheck, 60 | }, 61 | { 62 | name: inquirerParams.max_mint_at_address, 63 | type: 'input', 64 | message: '🤔 How much max mint quantity per address...', 65 | validate: nullCheck, 66 | }, 67 | ]; 68 | 69 | (async () => { 70 | inquirer.prompt(questions).then(async (ans) => { 71 | try { 72 | const provider = await getRPCProvider(ethers.provider); 73 | const nftOwnerSigner = new ethers.Wallet(ans.nft_owner_private_key, provider); 74 | 75 | const mintManager = (await ethers.getContractFactory('OmnuumMintManager')).attach(ans.mint_manager_address); 76 | 77 | const txResponse = await mintManager 78 | .connect(nftOwnerSigner) 79 | .setPublicMintSchedule( 80 | ans.nft_address, 81 | ans.group_id, 82 | toSolDate(addHours(new Date(), Number(ans.end_day_from_now))), 83 | ethers.utils.parseEther(ans.base_price), 84 | Number(ans.supply), 85 | Number(ans.max_mint_at_address), 86 | ); 87 | 88 | const txReceipt = await txResponse.wait(); 89 | 90 | console.log(txReceipt); 91 | console.log(`💋 Mint schedule is set..\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 92 | } catch (e) { 93 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 94 | } 95 | }); 96 | })(); 97 | -------------------------------------------------------------------------------- /contracts/V1/OmnuumExchange.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts-upgradeable/interfaces/IERC20Upgradeable.sol'; 5 | import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol'; 6 | import '../utils/OwnableUpgradeable.sol'; 7 | import './OmnuumCAManager.sol'; 8 | 9 | /// @title OmnuumExchange - Omnuum internal exchange contract to use token freely by other omnuum contracts 10 | /// @author Omnuum Dev Team - 11 | /// @notice Use only purpose for Omnuum 12 | /// @dev Warning:: This contract is for temporary use and will be upgraded to version 2 which use dex to exchange token, 13 | /// @dev Until version 2, LINK token will be deposited this contract directly and send LINK to omnuum contracts whenever they want 14 | contract OmnuumExchange is OwnableUpgradeable { 15 | using SafeERC20Upgradeable for IERC20Upgradeable; 16 | 17 | OmnuumCAManager private caManager; 18 | 19 | /// @notice temporary use purpose of LINK/ETH exchange rate 20 | uint256 public tmpLinkExRate; 21 | 22 | event Exchange(address indexed baseToken, address indexed targetToken, uint256 amount, address user, address indexed receipient); 23 | 24 | function initialize(address _caManagerA) public initializer { 25 | /// @custom:error (AE1) - Zero address not acceptable 26 | require(_caManagerA != address(0), 'AE1'); 27 | 28 | __Ownable_init(); 29 | 30 | caManager = OmnuumCAManager(_caManagerA); 31 | 32 | tmpLinkExRate = 0.01466666666 ether; 33 | } 34 | 35 | /// @notice calculate amount when given amount of token is swapped to target token 36 | /// @param _fromToken 'from' token for swapping 37 | /// @param _toToken 'to' token for swapping 38 | /// @param _amount 'from' token's amount for swapping 39 | /// @return amount 'to' token's expected amount after swapping 40 | function getExchangeAmount( 41 | address _fromToken, 42 | address _toToken, 43 | uint256 _amount 44 | ) public view returns (uint256 amount) { 45 | return (tmpLinkExRate * _amount) / 1 ether; 46 | } 47 | 48 | /// @notice update temporary exchange rate which is used for LINK/ETH 49 | /// @param _newRate new exchange rate (LINK/ETH) 50 | function updateTmpExchangeRate(uint256 _newRate) external onlyOwner { 51 | tmpLinkExRate = _newRate; 52 | } 53 | 54 | /// @notice give requested token to omnuum contract 55 | /// @param _token request token address 56 | /// @param _amount amount of token requested 57 | /// @param _to address where specified token and amount should delivered to 58 | function exchangeToken( 59 | address _token, 60 | uint256 _amount, 61 | address _to 62 | ) external payable { 63 | /// @custom:error (OO7) - Only role owner can access 64 | require(caManager.hasRole(msg.sender, 'EXCHANGE'), 'OO7'); 65 | 66 | IERC20Upgradeable(_token).safeTransfer(msg.sender, _amount); 67 | 68 | emit Exchange(address(0), _token, _amount, msg.sender, _to); 69 | } 70 | 71 | /// @notice withdraw specific amount to omnuum wallet contract 72 | /// @param _amount amount of ether to be withdrawn 73 | function withdraw(uint256 _amount) external onlyOwner { 74 | /// @custom:error (ARG2) - Arguments are not correct 75 | require(_amount <= address(this).balance, 'ARG2'); 76 | payable(caManager.getContract('WALLET')).transfer(_amount); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /scripts/interactions/nft/publicMint.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { getPayloadWithSignature } = require('../interactionHelpers'); 4 | const { payloadTopic } = require('../../../utils/constants'); 5 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 6 | 7 | const inquirerParams = { 8 | nftContractAddress: 'nftContractAddress', 9 | senderVerifierAddress: 'senderVerifierAddress', 10 | minterAddress: 'minterAddress', 11 | minterPrivateKey: 'minterPrivateKey', 12 | groupId: 'groupId', 13 | OmSignatureSignerPrivateKey: 'OmSignatureSignerPrivateKey', 14 | publicPrice: 'publicPrice', 15 | mintQuantity: 'mintQuantity', 16 | }; 17 | 18 | const questions = [ 19 | { 20 | name: inquirerParams.nftContractAddress, 21 | type: 'input', 22 | message: '🤔 nft contract address is...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.senderVerifierAddress, 27 | type: 'input', 28 | message: '🤔 senderVerifier contract address is...', 29 | validate: nullCheck, 30 | }, 31 | { 32 | name: inquirerParams.minterAddress, 33 | type: 'input', 34 | message: '🤔 Minter Address is...', 35 | validate: nullCheck, 36 | }, 37 | { 38 | name: inquirerParams.minterPrivateKey, 39 | type: 'input', 40 | message: '🤔 Minter Private Key is...', 41 | validate: nullCheck, 42 | }, 43 | { 44 | name: inquirerParams.groupId, 45 | type: 'input', 46 | message: '🤔 Public schedule group id is...(uint: dec)', 47 | validate: nullCheck, 48 | }, 49 | { 50 | name: inquirerParams.OmSignatureSignerPrivateKey, 51 | type: 'input', 52 | message: '🤔 Omnuum Signature Signer PrivateKey is...', 53 | validate: nullCheck, 54 | }, 55 | { 56 | name: inquirerParams.publicPrice, 57 | type: 'input', 58 | message: '🤔 Mint Price at public is...(must be greater or equal to public base price) (unit: ETH)', 59 | validate: nullCheck, 60 | }, 61 | { 62 | name: inquirerParams.mintQuantity, 63 | type: 'input', 64 | message: '🤔 Mint Quantity is...', 65 | validate: nullCheck, 66 | }, 67 | ]; 68 | 69 | (async () => { 70 | inquirer.prompt(questions).then(async (ans) => { 71 | try { 72 | const provider = await getRPCProvider(ethers.provider); 73 | 74 | const payload = await getPayloadWithSignature({ 75 | senderVerifierAddress: ans.senderVerifierAddress, 76 | minterAddress: ans.minterAddress, 77 | payloadTopic: payloadTopic.mint, 78 | groupId: ans.groupId, 79 | signerPrivateKey: ans.OmSignatureSignerPrivateKey, 80 | }); 81 | 82 | const sendValue = ethers.utils.parseEther(ans.publicPrice).mul(Number(ans.mintQuantity)); 83 | 84 | const nftContract = (await ethers.getContractFactory('OmnuumNFT721')).attach(ans.nftContractAddress); 85 | 86 | const minterSigner = new ethers.Wallet(ans.minterPrivateKey, provider); 87 | 88 | const txResponse = await nftContract 89 | .connect(minterSigner) 90 | .publicMint(ans.mintQuantity, ans.groupId, payload, { value: sendValue, gasLimit: 10000000 }); 91 | 92 | console.log('txRseponse', txResponse); 93 | const txReceipt = await txResponse.wait(); 94 | 95 | console.log(txReceipt); 96 | console.log(`💋 Public mint is on the way.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 97 | } catch (e) { 98 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 99 | } 100 | }); 101 | })(); 102 | -------------------------------------------------------------------------------- /scripts/interactions/interactionHelpers.js: -------------------------------------------------------------------------------- 1 | const { ifElse, identity } = require('fxjs'); 2 | const { ethers } = require('hardhat'); 3 | 4 | const getPayloadTypedData = async ({ senderVerifierAddress, minterAddress, payloadTopic, groupId }) => ({ 5 | types: { 6 | EIP712Domain: [ 7 | { name: 'name', type: 'string' }, 8 | { name: 'version', type: 'string' }, 9 | { name: 'chainId', type: 'uint256' }, 10 | { name: 'verifyingContract', type: 'address' }, 11 | ], 12 | Payload: [ 13 | { name: 'sender', type: 'address' }, 14 | { name: 'topic', type: 'string' }, 15 | { name: 'nonce', type: 'uint256' }, 16 | ], 17 | }, 18 | primaryType: 'Payload', 19 | domain: { 20 | name: 'Omnuum', 21 | version: '1', 22 | verifyingContract: senderVerifierAddress, 23 | chainId: (await ethers.provider.getNetwork()).chainId, 24 | }, 25 | message: { 26 | sender: minterAddress, 27 | topic: payloadTopic, 28 | nonce: groupId, 29 | }, 30 | }); 31 | 32 | const getTicketTypedDate = async ({ ticketManagerAddress, minterAddress, nftContractAddress, groupId, ether_price, quantity }) => { 33 | const ethPrice = ethers.utils.parseEther(`${ether_price}`); 34 | return { 35 | types: { 36 | EIP712Domain: [ 37 | { name: 'name', type: 'string' }, 38 | { name: 'version', type: 'string' }, 39 | { name: 'chainId', type: 'uint256' }, 40 | { name: 'verifyingContract', type: 'address' }, 41 | ], 42 | Ticket: [ 43 | { name: 'user', type: 'address' }, 44 | { name: 'nft', type: 'address' }, 45 | { name: 'price', type: 'uint256' }, 46 | { name: 'quantity', type: 'uint32' }, 47 | { name: 'groupId', type: 'uint256' }, 48 | ], 49 | }, 50 | primaryType: 'Ticket', 51 | domain: { 52 | name: 'OmnuumTicket', 53 | version: '1', 54 | verifyingContract: ticketManagerAddress, 55 | chainId: (await ethers.provider.getNetwork()).chainId, 56 | }, 57 | message: { 58 | user: minterAddress, 59 | nft: nftContractAddress, 60 | groupId, 61 | price: ethPrice, 62 | quantity, 63 | }, 64 | }; 65 | }; 66 | 67 | const getTicketWithSignature = async ({ 68 | ticketManagerAddress, 69 | minterAddress, 70 | nftContractAddress, 71 | groupId, 72 | ether_price, 73 | quantity, 74 | signerPrivateKey, 75 | }) => { 76 | const ticketTypedData = await getTicketTypedDate({ 77 | ticketManagerAddress, 78 | minterAddress, 79 | nftContractAddress, 80 | groupId, 81 | ether_price, 82 | quantity, 83 | }); 84 | const { signingEIP712 } = await import('@marpple/omnuum-digitalSigning'); 85 | const { signature } = signingEIP712({ 86 | typedData: ticketTypedData, 87 | privateKey: signerPrivateKey, 88 | }); 89 | 90 | return { ...ticketTypedData.message, signature }; 91 | }; 92 | 93 | const getPayloadWithSignature = async ({ senderVerifierAddress, minterAddress, payloadTopic, groupId, signerPrivateKey }) => { 94 | const { signingEIP712 } = await import('@marpple/omnuum-digitalSigning'); 95 | const payloadTypedData = await getPayloadTypedData({ senderVerifierAddress, minterAddress, payloadTopic, groupId }); 96 | 97 | const { signature } = signingEIP712({ 98 | typedData: payloadTypedData, 99 | privateKey: ifElse( 100 | (key) => /^0x/.test(key), 101 | (key) => key.slice(2), 102 | identity, 103 | )(signerPrivateKey), 104 | }); 105 | 106 | return { ...payloadTypedData.message, signature }; 107 | }; 108 | 109 | module.exports = { getTicketWithSignature, getPayloadWithSignature }; 110 | -------------------------------------------------------------------------------- /deploy_history/mainnet_4-15-2022_14:49:04.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "mainnet", 3 | "deployStartAt": "2:46:59 PM", 4 | "deployer": "0x84803BBd4269f596D94e88fA4E697b21C39370d2", 5 | "caManager": { 6 | "proxy": "0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab", 7 | "impl": "0x995f1452E0c7EF28aEDB03e645De8F71B765c19C", 8 | "admin": "0x6d82f1895bdAe7B6FEA3c57F44fc281AbB6BB649", 9 | "gasUsed": 643213, 10 | "blockNumber": 14588099, 11 | "address": "0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab" 12 | }, 13 | "mintManager": { 14 | "proxy": "0x2D24c1C8677870d320C10aE7b840A279A0CAF492", 15 | "impl": "0xC723122f3809A89c2cdB17bE89B4Ac851352C400", 16 | "admin": "0x6d82f1895bdAe7B6FEA3c57F44fc281AbB6BB649", 17 | "gasUsed": 688045, 18 | "blockNumber": 14588127, 19 | "address": "0x2D24c1C8677870d320C10aE7b840A279A0CAF492" 20 | }, 21 | "exchange": { 22 | "proxy": "0x680756B2794B4d4Ad265040B18539f19f90F13CC", 23 | "impl": "0x6C65f1B9a3039dee9eaC97bBCae79357Aa07c40A", 24 | "admin": "0x6d82f1895bdAe7B6FEA3c57F44fc281AbB6BB649", 25 | "gasUsed": 688369, 26 | "blockNumber": 14588133, 27 | "address": "0x680756B2794B4d4Ad265040B18539f19f90F13CC" 28 | }, 29 | "wallet": { 30 | "contract": "0x85ed922D503Eb40D962A6c82CE7480e9bf0a87c8", 31 | "address": "0x85ed922D503Eb40D962A6c82CE7480e9bf0a87c8", 32 | "gasUsed": 3698819, 33 | "blockNumber": 14588144, 34 | "args": [ 35 | 55, 36 | 3, 37 | [ 38 | { "addr": "0x95254b0F9eFc20295AE423373cd105EE8b5a717F", "vote": 2 }, 39 | { "addr": "0x0bb5708C4f71439DB00027F85D87f0232acDeEfA", "vote": 2 }, 40 | { "addr": "0x734724b6bAaF3C407298d24d98ece1E0fA077ffd", "vote": 1 }, 41 | { "addr": "0xDfe2eFA72E86397728c885D91A8786aA9FdE7A24", "vote": 1 }, 42 | { "addr": "0x4E2d208C5f7DB881Ee871F5DF4a23DFDb0e77b58", "vote": 1 } 43 | ] 44 | ] 45 | }, 46 | "ticketManager": { 47 | "contract": "0x80ba159a1D187A4C4F8722F1262316Aa3976eec0", 48 | "address": "0x80ba159a1D187A4C4F8722F1262316Aa3976eec0", 49 | "gasUsed": 1509528, 50 | "blockNumber": 14588135, 51 | "args": [] 52 | }, 53 | "vrfManager": { 54 | "contract": "0x1795c86d5e52EeF689981f89D3276C70a3e61149", 55 | "address": "0x1795c86d5e52EeF689981f89D3276C70a3e61149", 56 | "gasUsed": 2383391, 57 | "blockNumber": 14588139, 58 | "args": [ 59 | "0x514910771AF9Ca656af840dff83E8264EcF986CA", 60 | "0xf0d54349aDdcf704F77AE15b96510dEA15cb7952", 61 | "0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445", 62 | { "type": "BigNumber", "hex": "0x1bc16d674ec80000" }, 63 | "0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab" 64 | ] 65 | }, 66 | "revealManager": { 67 | "contract": "0x618be88829Be3e29ebEF719dAFfb3037C20A8f64", 68 | "address": "0x618be88829Be3e29ebEF719dAFfb3037C20A8f64", 69 | "gasUsed": 428364, 70 | "blockNumber": 14588136, 71 | "args": ["0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab"] 72 | }, 73 | "senderVerifier": { 74 | "contract": "0x4b0997358df0F6428ACD252FeEBC3469790f1774", 75 | "address": "0x4b0997358df0F6428ACD252FeEBC3469790f1774", 76 | "gasUsed": 1093362, 77 | "blockNumber": 14588137, 78 | "args": [] 79 | }, 80 | "nft1155": { 81 | "impl": "0x853A06b944aBEF31984c08beAB9ad2Bf43F00c4F", 82 | "beacon": "0x25a374f03B20ba168F723630cc1850A8D1A9767a", 83 | "address": "0x25a374f03B20ba168F723630cc1850A8D1A9767a" 84 | }, 85 | "nftFactory": { 86 | "contract": "0x21AF6949Ec33cd7CEd838Db2CF5875905CE82CEf", 87 | "address": "0x21AF6949Ec33cd7CEd838Db2CF5875905CE82CEf", 88 | "gasUsed": 1990745, 89 | "blockNumber": 14588171, 90 | "args": [ 91 | "0xD52F874978c3B86Ef4A8DC5e03AdaA4F3C81B8Ab", 92 | "0x25a374f03B20ba168F723630cc1850A8D1A9767a", 93 | "0x81876853baef4001B844B11dF010E9647b7c9a2b" 94 | ] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /contracts/V1/OperatorFilterRegistry/OperatorFiltererUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import { IOperatorFilterRegistry } from './IOperatorFilterRegistry.sol'; 5 | import { Initializable } from '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 6 | 7 | abstract contract OperatorFiltererUpgradeable is Initializable { 8 | error OperatorNotAllowed(address operator); 9 | 10 | IOperatorFilterRegistry constant operatorFilterRegistry = IOperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E); 11 | 12 | function __OperatorFilterer_init(address subscriptionOrRegistrantToCopy, bool subscribe) internal onlyInitializing { 13 | // If an inheriting token contract is deployed to a network without the registry deployed, the modifier 14 | // will not revert, but the contract will need to be registered with the registry once it is deployed in 15 | // order for the modifier to filter addresses. 16 | if (address(operatorFilterRegistry).code.length > 0) { 17 | if (!operatorFilterRegistry.isRegistered(address(this))) { 18 | if (subscribe) { 19 | operatorFilterRegistry.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy); 20 | } else { 21 | if (subscriptionOrRegistrantToCopy != address(0)) { 22 | operatorFilterRegistry.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy); 23 | } else { 24 | operatorFilterRegistry.register(address(this)); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | function __OperatorFilterer(address subscriptionOrRegistrantToCopy, bool subscribe) internal { 32 | // If an inheriting token contract is deployed to a network without the registry deployed, the modifier 33 | // will not revert, but the contract will need to be registered with the registry once it is deployed in 34 | // order for the modifier to filter addresses. 35 | if (address(operatorFilterRegistry).code.length > 0) { 36 | if (!operatorFilterRegistry.isRegistered(address(this))) { 37 | if (subscribe) { 38 | operatorFilterRegistry.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy); 39 | } else { 40 | if (subscriptionOrRegistrantToCopy != address(0)) { 41 | operatorFilterRegistry.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy); 42 | } else { 43 | operatorFilterRegistry.register(address(this)); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | modifier onlyAllowedOperator(address from) virtual { 51 | // Check registry code length to facilitate testing in environments without a deployed registry. 52 | if (address(operatorFilterRegistry).code.length > 0) { 53 | // Allow spending tokens from addresses with balance 54 | // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred 55 | // from an EOA. 56 | if (from == msg.sender) { 57 | _; 58 | return; 59 | } 60 | if (!operatorFilterRegistry.isOperatorAllowed(address(this), msg.sender)) { 61 | revert OperatorNotAllowed(msg.sender); 62 | } 63 | } 64 | _; 65 | } 66 | 67 | modifier onlyAllowedOperatorApproval(address operator) virtual { 68 | // Check registry code length to facilitate testing in environments without a deployed registry. 69 | if (address(operatorFilterRegistry).code.length > 0) { 70 | if (!operatorFilterRegistry.isOperatorAllowed(address(this), operator)) { 71 | revert OperatorNotAllowed(operator); 72 | } 73 | } 74 | _; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/senderVerifier.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { ethers, upgrades } = require('hardhat'); 3 | 4 | const { signPayload } = require('./etc/util.js'); 5 | const Constants = require('../utils/constants.js'); 6 | require('chai').should(); 7 | 8 | const { prepareDeploy, prepareMockDeploy, testDeploy } = require('./etc/mock.js'); 9 | 10 | const group_id = 0; 11 | 12 | upgrades.silenceWarnings(); 13 | 14 | describe('SenderVerifier', () => { 15 | before(async () => { 16 | await prepareDeploy.call(this); 17 | await prepareMockDeploy.call(this); 18 | }); 19 | 20 | beforeEach(async () => { 21 | this.accounts = await ethers.getSigners(); 22 | await testDeploy.call(this, this.accounts); 23 | }); 24 | 25 | describe('[Method] verify', () => { 26 | it('Verify signed by omnuum', async () => { 27 | const { 28 | accounts: [omnuumAC, minterAC], 29 | senderVerifier, 30 | } = this; 31 | 32 | const payload = await signPayload( 33 | minterAC.address, // sender 34 | Constants.payloadTopic.ticket, // topic 35 | group_id, // nonce 36 | omnuumAC, // signer 37 | senderVerifier.address // verify contract address 38 | ); 39 | 40 | const verify_result = await senderVerifier.verify( 41 | omnuumAC.address, // signer 42 | minterAC.address, // sender 43 | Constants.payloadTopic.ticket, // topic 44 | group_id, // nonce 45 | payload, 46 | ); 47 | 48 | expect(verify_result).to.be.deep.equal([]); 49 | }); 50 | it('[Revert] False topic', async () => { 51 | const { 52 | accounts: [omnuumAC, minterAC], 53 | senderVerifier, 54 | } = this; 55 | 56 | const payload = await signPayload(minterAC.address, Constants.payloadTopic.ticket, group_id, omnuumAC, senderVerifier.address); 57 | 58 | await expect( 59 | senderVerifier.verify( 60 | omnuumAC.address, 61 | minterAC.address, 62 | Constants.payloadTopic.mint, // topic - false 63 | group_id, 64 | payload, 65 | ), 66 | ).to.be.revertedWith(Constants.reasons.code.VR3); 67 | }); 68 | it('[Revert] False Signer', async () => { 69 | const { 70 | accounts: [omnuumAC, minterAC, falseSignerAC], 71 | senderVerifier, 72 | } = this; 73 | 74 | const payload = await signPayload(minterAC.address, Constants.payloadTopic.ticket, group_id, falseSignerAC, senderVerifier.address); 75 | 76 | await expect( 77 | senderVerifier.verify( 78 | omnuumAC.address, 79 | minterAC.address, 80 | Constants.payloadTopic.ticket, // topic - false 81 | group_id, 82 | payload, 83 | ), 84 | ).to.be.revertedWith(Constants.reasons.code.VR1); 85 | }); 86 | it('[Revert] False Nonce', async () => { 87 | const { 88 | accounts: [omnuumAC, minterAC], 89 | senderVerifier, 90 | } = this; 91 | 92 | const payload = await signPayload(minterAC.address, Constants.payloadTopic.ticket, group_id, omnuumAC, senderVerifier.address); 93 | 94 | await expect( 95 | senderVerifier.verify( 96 | omnuumAC.address, 97 | minterAC.address, 98 | Constants.payloadTopic.ticket, 99 | group_id + 1, // nonce - false 100 | payload, 101 | ), 102 | ).to.be.revertedWith(Constants.reasons.code.VR2); 103 | }); 104 | it('[Revert] False Sender', async () => { 105 | const { 106 | accounts: [omnuumAC, minterAC, falseSenderAC], 107 | senderVerifier, 108 | } = this; 109 | 110 | const payload = await signPayload(minterAC.address, Constants.payloadTopic.ticket, group_id, omnuumAC, senderVerifier.address); 111 | 112 | await expect( 113 | senderVerifier.verify(omnuumAC.address, falseSenderAC.address, Constants.payloadTopic.ticket, group_id, payload), 114 | ).to.be.revertedWith(Constants.reasons.code.VR4); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | require('@nomiclabs/hardhat-etherscan'); 4 | require('@nomiclabs/hardhat-waffle'); 5 | require('@nomiclabs/hardhat-solhint'); 6 | 7 | require('hardhat-gas-reporter'); 8 | 9 | require('@openzeppelin/hardhat-upgrades'); 10 | require('hardhat-contract-sizer'); 11 | 12 | require('hardhat-abi-exporter'); 13 | require('solidity-coverage'); 14 | 15 | module.exports = { 16 | solidity: { 17 | version: '0.8.10', 18 | settings: { 19 | optimizer: { 20 | enabled: true, 21 | runs: 1000, 22 | }, 23 | }, 24 | }, 25 | defaultNetwork: 'localhost', 26 | networks: { 27 | hardhat: { 28 | accounts: { 29 | count: 20, 30 | }, 31 | mining: { 32 | auto: true, 33 | interval: process.env.MINING_INTERVAL !== undefined ? Number(process.env.MINING_INTERVAL) : 0, 34 | }, 35 | }, 36 | mainnet: { 37 | url: process.env.MAINNET_URL || '', 38 | }, 39 | goerli: { 40 | url: process.env.GOERLI_URL || '', 41 | accounts: [ 42 | process.env.OMNUUM_DEPLOYER_PRIVATE_KEY, 43 | process.env.ACCOUNT_TESTER_A, 44 | process.env.ACCOUNT_TESTER_B, 45 | process.env.ACCOUNT_TESTER_C, 46 | process.env.ACCOUNT_TESTER_D, 47 | process.env.ACCOUNT_TESTER_E, 48 | ].filter((a) => a), 49 | }, 50 | rinkeby: { 51 | url: process.env.RINKEBY_URL || '', 52 | accounts: [ 53 | process.env.OMNUUM_DEPLOYER_PRIVATE_KEY, 54 | process.env.ACCOUNT_TESTER_A, 55 | process.env.ACCOUNT_TESTER_B, 56 | process.env.ACCOUNT_TESTER_C, 57 | process.env.ACCOUNT_TESTER_D, 58 | process.env.ACCOUNT_TESTER_E, 59 | ].filter((a) => a), 60 | // gasPrice: 50 * 10 ** 9, 61 | gasPrice: 'auto', 62 | gasLimit: 10_000_000, 63 | }, 64 | ropsten: { 65 | url: process.env.ROPSTEN_URL || '', 66 | accounts: [ 67 | process.env.OMNUUM_DEPLOYER_PRIVATE_KEY, 68 | process.env.ACCOUNT_TESTER_A, 69 | process.env.ACCOUNT_TESTER_B, 70 | process.env.ACCOUNT_TESTER_C, 71 | process.env.ACCOUNT_TESTER_D, 72 | process.env.ACCOUNT_TESTER_E, 73 | ].filter((a) => a), 74 | }, 75 | }, 76 | paths: { 77 | sources: './contracts', 78 | }, 79 | // gasReporter: { 80 | // enabled: process.env.REPORT_GAS ?? false, 81 | // currency: 'USD', 82 | // coinmarketcap: process.env.COINMARKETCAP_KEY, 83 | // }, 84 | etherscan: { 85 | apiKey: { 86 | mainnet: process.env.ETHERSCAN_API_KEY, 87 | goerli: process.env.ETHERSCAN_API_KEY, 88 | }, 89 | }, 90 | // contractSizer: { 91 | // alphaSort: true, 92 | // disambiguatePaths: false, 93 | // runOnCompile: true, 94 | // strict: true, 95 | // }, 96 | abiExporter: [ 97 | { 98 | path: './data/abi', 99 | runOnCompile: true, 100 | clear: true, 101 | flat: true, 102 | pretty: false, 103 | except: ['./mock'], 104 | }, 105 | { 106 | path: './data/abi/pretty', 107 | runOnCompile: true, 108 | clear: true, 109 | flat: true, 110 | pretty: true, 111 | except: ['./mock'], 112 | }, 113 | ], 114 | }; 115 | 116 | /* localhost hardhat accounts 117 | Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH) 118 | Private Key: ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 119 | 120 | Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH) 121 | Private Key: 59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d 122 | 123 | Account #2: 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc (10000 ETH) 124 | Private Key: 5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a 125 | 126 | Account #3: 0x90f79bf6eb2c4f870365e785982e1f101e93b906 (10000 ETH) 127 | Private Key: 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 128 | 129 | Account #4: 0x15d34aaf54267db7d7c367839aaf71a00a2c6a65 (10000 ETH) 130 | Private Key: 47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a 131 | 132 | Account #5: 0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc (10000 ETH) 133 | Private Key: 8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba 134 | * */ 135 | -------------------------------------------------------------------------------- /scripts/interactions/nft/ticketMint.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const { getTicketWithSignature, getPayloadWithSignature } = require('../interactionHelpers'); 4 | const { payloadTopic } = require('../../../utils/constants'); 5 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 6 | 7 | require('dotenv').config(); 8 | 9 | const inquirerParams = { 10 | ticketManagerAddress: 'ticketManagerAddress', 11 | nftContractAddress: 'nftContractAddress', 12 | senderVerifierAddress: 'senderVerifierAddress', 13 | minterAddress: 'minterAddress', 14 | minterPrivateKey: 'minterPrivateKey', 15 | groupId: 'groupId', 16 | OmSignatureSignerPrivateKey: 'OmSignatureSignerPrivateKey', 17 | ticketPrice: 'ticketPrice', 18 | ticketQuantity: 'ticketQuantity', 19 | mintQuantity: 'mintQuantity', 20 | }; 21 | 22 | const questions = [ 23 | { 24 | name: inquirerParams.ticketManagerAddress, 25 | type: 'input', 26 | message: '🤔 Ticket Manager Address is ...', 27 | validate: nullCheck, 28 | }, 29 | { 30 | name: inquirerParams.nftContractAddress, 31 | type: 'input', 32 | message: '🤔 nft contract address is...', 33 | validate: nullCheck, 34 | }, 35 | { 36 | name: inquirerParams.senderVerifierAddress, 37 | type: 'input', 38 | message: '🤔 senderVerifier contract address is...', 39 | validate: nullCheck, 40 | }, 41 | { 42 | name: inquirerParams.minterAddress, 43 | type: 'input', 44 | message: '🤔 Minter Address is...', 45 | validate: nullCheck, 46 | }, 47 | { 48 | name: inquirerParams.minterPrivateKey, 49 | type: 'input', 50 | message: '🤔 Minter Private Key is...', 51 | validate: nullCheck, 52 | }, 53 | { 54 | name: inquirerParams.groupId, 55 | type: 'input', 56 | message: '🤔 Ticket schedule group id is...(uint: dec)', 57 | validate: nullCheck, 58 | }, 59 | { 60 | name: inquirerParams.OmSignatureSignerPrivateKey, 61 | type: 'input', 62 | message: '🤔 Omnuum Signature Signer PrivateKey is...', 63 | validate: nullCheck, 64 | }, 65 | { 66 | name: inquirerParams.ticketPrice, 67 | type: 'input', 68 | message: '🤔 Mint Price of ticket is...(unit: ETH)', 69 | validate: nullCheck, 70 | }, 71 | { 72 | name: inquirerParams.ticketQuantity, 73 | type: 'input', 74 | message: '🤔 Ticket Quantity is...', 75 | validate: nullCheck, 76 | }, 77 | { 78 | name: inquirerParams.mintQuantity, 79 | type: 'input', 80 | message: '🤔 Mint Quantity is...', 81 | validate: nullCheck, 82 | }, 83 | ]; 84 | 85 | (async () => { 86 | inquirer.prompt(questions).then(async (ans) => { 87 | try { 88 | const provider = await getRPCProvider(ethers.provider); 89 | 90 | const ticket = await getTicketWithSignature({ 91 | ticketManagerAddress: ans.ticketManagerAddress, 92 | minterAddress: ans.minterAddress, 93 | nftContractAddress: ans.nftContractAddress, 94 | groupId: ans.groupId, 95 | ether_price: ans.ticketPrice, 96 | quantity: ans.ticketQuantity, 97 | signerPrivateKey: ans.OmSignatureSignerPrivateKey, 98 | }); 99 | 100 | const payload = await getPayloadWithSignature({ 101 | senderVerifierAddress: ans.senderVerifierAddress, 102 | minterAddress: ans.minterAddress, 103 | payloadTopic: payloadTopic.ticket, 104 | groupId: ans.groupId, 105 | signerPrivateKey: ans.OmSignatureSignerPrivateKey, 106 | }); 107 | 108 | const sendValue = ethers.utils.parseEther(ans.ticketPrice).mul(Number(ans.mintQuantity)); 109 | 110 | const nftContract = (await ethers.getContractFactory('OmnuumNFT721')).attach(ans.nftContractAddress); 111 | 112 | const minterSigner = new ethers.Wallet(ans.minterPrivateKey, provider); 113 | const txResponse = await nftContract 114 | .connect(minterSigner) 115 | .ticketMint(ans.mintQuantity, ticket, payload, { value: sendValue, gasLimit: 10000000 }); 116 | const txReceipt = await txResponse.wait(); 117 | 118 | console.log(txReceipt); 119 | console.log(`💋 Ticket Mint is on the way.\nBlock: ${txReceipt.blockNumber}\nTransaction: ${txReceipt.transactionHash}`); 120 | } catch (e) { 121 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 122 | } 123 | }); 124 | })(); 125 | -------------------------------------------------------------------------------- /scripts/interactions/nft/transferNFT.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const inquirer = require('inquirer'); 3 | const chalk = require('chalk'); 4 | const { nullCheck, getRPCProvider } = require('../../deployments/deployHelper'); 5 | 6 | const inquirerParams = { 7 | nftContractAddress: 'nftContractAddress', 8 | nftOwnerPrivateKey: 'nftOwnerPrivateKey', 9 | receiverAddress: 'receiverAddress', 10 | tokenId: 'tokenId', 11 | }; 12 | const questions = [ 13 | { 14 | name: inquirerParams.nftContractAddress, 15 | type: 'input', 16 | message: '🤔 nft contract address is...', 17 | validate: nullCheck, 18 | }, 19 | { 20 | name: inquirerParams.nftOwnerPrivateKey, 21 | type: 'input', 22 | message: '🤔 NFT owner private key is ...', 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.receiverAddress, 27 | type: 'input', 28 | message: '🤔 Who address do you want transfer is ...', 29 | validate: nullCheck, 30 | }, 31 | { 32 | name: inquirerParams.tokenId, 33 | type: 'input', 34 | message: '🤔 Token id you want to transfer is ...', 35 | validate: nullCheck, 36 | }, 37 | ]; 38 | 39 | (async () => { 40 | inquirer.prompt(questions).then(async (ans) => { 41 | try { 42 | const provider = await getRPCProvider(ethers.provider); 43 | const nftOwnerSigner = new ethers.Wallet(ans.nftOwnerPrivateKey, provider); 44 | 45 | const nftContract = (await ethers.getContractFactory('OmnuumNFT721')).attach(ans.nftContractAddress).connect(nftOwnerSigner); 46 | 47 | const { wantToApprove } = inquirer.prompt({ 48 | name: 'wantToApprove', 49 | type: 'confirm', 50 | message: '🤔 Want to approve?', 51 | }); 52 | 53 | if (wantToApprove) { 54 | const txResponseApprove = await nftContract.approve(ans.receiverAddress, ans.tokenId); 55 | const txReceiptApprove = await txResponseApprove.wait(); 56 | // event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 57 | const { approved } = nftContract.interface.parseLog(txReceiptApprove.events[0]).args; 58 | console.log(`\n${chalk.yellow(`Token id ${ans.tokenId} is ${chalk.redBright('approved')} to receiver ${approved}`)}\n`); 59 | } 60 | 61 | const { tokenTrasnfer } = await inquirer.prompt([ 62 | { 63 | name: 'tokenTrasnfer', 64 | type: 'list', 65 | message: '🤔 Choose transfer method?', 66 | choices: ['safeTransferFrom', 'transferFrom', 'cancel'], 67 | }, 68 | ]); 69 | if (tokenTrasnfer !== 'cancel') { 70 | let txResponseSafeTransfer; 71 | const transferArguments = [nftOwnerSigner.address, ans.receiverAddress, ans.tokenId]; 72 | if (tokenTrasnfer === 'safeTransferFrom') { 73 | txResponseSafeTransfer = await nftContract['safeTransferFrom(address,address,uint256)'](...transferArguments); 74 | } else if (tokenTrasnfer === 'transferFrom') { 75 | txResponseSafeTransfer = await nftContract.transferFrom(...transferArguments); 76 | } 77 | 78 | const txReceiptSafeTransfer = await txResponseSafeTransfer.wait(); 79 | 80 | console.log(txReceiptSafeTransfer.events); 81 | 82 | // event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 83 | // event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 84 | const { to } = nftContract.interface.parseLog(txReceiptSafeTransfer.events[1]).args; 85 | console.log(to, ans.receiverAddress); 86 | if (to.toLowerCase() === ans.receiverAddress.toLowerCase()) { 87 | console.log( 88 | `\n${chalk.yellow(`Token id ${ans.tokenId} is ${chalk.redBright('safe transferred')} to receiver ${ans.receiverAddress}`)}\n`, 89 | ); 90 | console.log( 91 | `💋 Token ${ans.tokenId} from nftContract ${ans.nftContractAddress} is ${chalk.redBright('safe transferred')} to receiver ${ 92 | ans.receiverAddress 93 | }.\nBlock: ${txReceiptSafeTransfer.blockNumber}\nTransaction: ${txReceiptSafeTransfer.transactionHash}`, 94 | ); 95 | } else { 96 | throw new Error('Wrong receiver'); 97 | } 98 | } 99 | } catch (e) { 100 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 101 | } 102 | }); 103 | })(); 104 | -------------------------------------------------------------------------------- /scripts/interactions/revealManager/vrfRequest.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers } = require('hardhat'); 3 | 4 | const chalk = require('chalk'); 5 | const { nullCheck, getRPCProvider, getChainName } = require('../../deployments/deployHelper'); 6 | const { testValues, chainlink } = require('../../../utils/constants'); 7 | 8 | const inquirerParams = { 9 | dev_deployer_private_key: 'dev_deployer_private_key', 10 | nft_owner_private_key: 'nft_owner_private_key', 11 | nft_address: 'nft_address', 12 | reveal_manager_address: 'reveal_manager_address', 13 | vrf_manager_address: 'vrf_manager_address', 14 | exchange_manager_address: 'exchange_manager_address', 15 | }; 16 | 17 | const questions = [ 18 | { 19 | name: inquirerParams.dev_deployer_private_key, 20 | type: 'input', 21 | message: '🤔 Dev deployer private key is ...', 22 | validate: nullCheck, 23 | }, 24 | { 25 | name: inquirerParams.nft_owner_private_key, 26 | type: 'input', 27 | message: '🤔 NFT project owner private key is ...', 28 | validate: nullCheck, 29 | }, 30 | { 31 | name: inquirerParams.nft_address, 32 | type: 'input', 33 | message: '🤔 NFT contract address is...', 34 | validate: nullCheck, 35 | }, 36 | { 37 | name: inquirerParams.exchange_manager_address, 38 | type: 'input', 39 | message: '🤔 Exchange manager address is...', 40 | validate: nullCheck, 41 | }, 42 | { 43 | name: inquirerParams.reveal_manager_address, 44 | type: 'input', 45 | message: '🤔 Reveal manager address is...', 46 | validate: nullCheck, 47 | }, 48 | { 49 | name: inquirerParams.vrf_manager_address, 50 | type: 'input', 51 | message: '🤔 VRF manager address is...', 52 | validate: nullCheck, 53 | }, 54 | ]; 55 | 56 | (async () => { 57 | inquirer.prompt(questions).then(async (ans) => { 58 | try { 59 | const chainName = await getChainName(); 60 | const provider = await getRPCProvider(ethers.provider); 61 | const devDeployerSigner = new ethers.Wallet(ans.dev_deployer_private_key, provider); 62 | 63 | const nftOwnerSigner = new ethers.Wallet(ans.nft_owner_private_key, provider); 64 | const revealManager = (await ethers.getContractFactory('RevealManager')).attach(ans.reveal_manager_address); 65 | const vrfManager = (await ethers.getContractFactory('OmnuumVRFManager')).attach(ans.vrf_manager_address); 66 | 67 | const requiredLinkFee = chainlink[chainName].fee; 68 | 69 | const { sendLink } = await inquirer.prompt([ 70 | { 71 | name: 'sendLink', 72 | type: 'confirm', 73 | message: `${chalk.yellowBright(`🤔 Do you want to send ${requiredLinkFee} LINK to exchange manager contract?`)}`, 74 | }, 75 | ]); 76 | if (sendLink) { 77 | const linkContract = new ethers.Contract( 78 | chainlink[chainName].LINK, 79 | ['function transfer(address _to, uint256 _value) returns (bool)'], 80 | devDeployerSigner, 81 | ); 82 | const txTransfer = await linkContract.transfer(ans.exchange_manager_address, requiredLinkFee); 83 | 84 | const txTransferReceipt = await txTransfer.wait(); 85 | console.log( 86 | `💰💰💰 LINK fee is transferred from devDeployer to Exchange Manager.\nBlock: ${txTransferReceipt.blockNumber}\nTransaction: ${txTransferReceipt.transactionHash}\nValue: ${chainlink[chainName].fee}`, 87 | ); 88 | } 89 | 90 | const sendEtherFee = testValues.tmpLinkExRate 91 | .mul(requiredLinkFee) 92 | .div(ethers.utils.parseEther('1')) 93 | .mul(ethers.BigNumber.from(await vrfManager.safetyRatio())) 94 | .div(ethers.BigNumber.from('100')); 95 | 96 | console.log( 97 | `💰 Sending ${chalk.redBright( 98 | sendEtherFee, 99 | )} ETH to revealManager...\n=> Value is sent through internal transaction to VRF manager\n${chalk.green( 100 | '=> Request Verifiable Random Function to ChainLINK Oracle', 101 | )}`, 102 | sendEtherFee, 103 | ); 104 | 105 | const txResponse = await revealManager 106 | .connect(nftOwnerSigner) 107 | .vrfRequest(ans.nft_address, { value: sendEtherFee, gasLimit: 10000000 }); 108 | 109 | console.log('txRseponse', txResponse); 110 | const txReceipt = await txResponse.wait(); 111 | 112 | console.log(txReceipt); 113 | console.log( 114 | `${chalk.yellowBright('💋 VRF request is on the way.')}\nBlock: ${txReceipt.blockNumber}\nTransaction: ${ 115 | txReceipt.transactionHash 116 | }`, 117 | ); 118 | } catch (e) { 119 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 120 | } 121 | }); 122 | })(); 123 | -------------------------------------------------------------------------------- /scripts/deployments/deployNFTBeaconProxy.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers, upgrades } = require('hardhat'); 3 | const fs = require('fs'); 4 | const chalk = require('chalk'); 5 | const { mkdir } = require('fs/promises'); 6 | const { deployNFT } = require('./deployments'); 7 | const { getDateSuffix, nullCheck, getRPCProvider, getChainName } = require('./deployHelper'); 8 | 9 | const inquirerParams = { 10 | project_owner_private_key: 'project_owner_private_key', 11 | signer_privateKey: 'signer_privateKey', 12 | sender_verifier_address: 'sender_verifier_address', 13 | nft_factory_address: 'nft_factory_address', 14 | max_supply: 'max_supply', 15 | cover_uri: 'cover_uri', 16 | nft_collection_id: 'nft_collection_id', 17 | name: 'name', 18 | symbol: 'symbol', 19 | }; 20 | 21 | const questions = [ 22 | { 23 | name: inquirerParams.project_owner_private_key, 24 | type: 'input', 25 | message: '🤔 Project owner private key is...', 26 | validate: nullCheck, 27 | }, 28 | { 29 | name: inquirerParams.signer_privateKey, 30 | type: 'input', 31 | message: '🤔 Payload signers private key is...', 32 | validate: nullCheck, 33 | }, 34 | { 35 | name: inquirerParams.sender_verifier_address, 36 | type: 'input', 37 | message: '🤔 Sender verifier address is...', 38 | validate: nullCheck, 39 | }, 40 | { 41 | name: inquirerParams.nft_factory_address, 42 | type: 'input', 43 | message: '🤔 Nft factory address is...', 44 | validate: nullCheck, 45 | }, 46 | { 47 | name: inquirerParams.max_supply, 48 | type: 'input', 49 | message: '🤔 Max supply is...', 50 | validate: nullCheck, 51 | }, 52 | { 53 | name: inquirerParams.cover_uri, 54 | type: 'input', 55 | message: '🤔 Cover uri is...', 56 | validate: nullCheck, 57 | }, 58 | { 59 | name: inquirerParams.nft_collection_id, 60 | type: 'input', 61 | message: '🤔 NFT collection id you would like is...', 62 | validate: nullCheck, 63 | }, 64 | { 65 | name: inquirerParams.name, 66 | type: 'input', 67 | message: '🤔 NFT name is...', 68 | validate: nullCheck, 69 | }, 70 | { 71 | name: inquirerParams.symbol, 72 | type: 'input', 73 | message: '🤔 NFT symbol is...', 74 | validate: nullCheck, 75 | }, 76 | ]; 77 | 78 | (async () => { 79 | console.log(` 80 | **** ** ******** ********** 81 | /**/** /** /**///// /////**/// 82 | /**//** /** /** /** 83 | /** //** /** /******* /** 84 | /** //**/** /**//// /** 85 | /** //**** /** /** 86 | /** //*** /** /** 87 | // /// // // 88 | `); 89 | 90 | inquirer.prompt(questions).then(async (ans) => { 91 | try { 92 | const chainName = await getChainName(); 93 | console.log(chalk.green(`${`\nSTART DEPLOYMENT to ${chainName} at ${new Date()}`}`)); 94 | 95 | const provider = await getRPCProvider(ethers.provider); 96 | 97 | const projectOwnerSigner = new ethers.Wallet(ans.project_owner_private_key, provider); 98 | 99 | const nftDeployment = await deployNFT({ 100 | projectOwnerSigner, 101 | senderVerifierAddress: ans.sender_verifier_address, 102 | signerPrivateKey: ans.signer_privateKey, 103 | maxSupply: ans.max_supply, 104 | coverUri: ans.cover_uri, 105 | nftFactoryAddress: ans.nft_factory_address, 106 | collectionId: ans.nft_collection_id, 107 | name: ans.name, 108 | symbol: ans.symbol, 109 | }); 110 | 111 | const { transactionHash, blockNumber, gasUsed } = nftDeployment.deployReceipt; 112 | 113 | console.log( 114 | chalk.yellow( 115 | `🌹 NFT Project Proxy is deployed. 116 | - Beacon Proxy Address: ${nftDeployment.beaconProxyAddress} 117 | - Owner:${projectOwnerSigner.address} 118 | - Block:${blockNumber} 119 | - Transaction:${transactionHash} 120 | - Gas:${gasUsed.toNumber()} 121 | `, 122 | ), 123 | ); 124 | 125 | const filename = `${chainName}_${getDateSuffix()}.json`; 126 | 127 | await mkdir('./scripts/deployments/deployResults/nft', { recursive: true }); 128 | fs.writeFileSync( 129 | `./scripts/deployments/deployResults/nft/${filename}`, 130 | Buffer.from( 131 | JSON.stringify({ 132 | nftProject: { 133 | beaconProxy: nftDeployment.beaconProxyAddress, 134 | projectOwner: projectOwnerSigner.address, 135 | blockNumber, 136 | transactionHash, 137 | gasUsed: gasUsed.toNumber(), 138 | }, 139 | }), 140 | ), 141 | 'utf8', 142 | ); 143 | } catch (e) { 144 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 145 | } 146 | }); 147 | })(); 148 | -------------------------------------------------------------------------------- /contracts/V1/TicketManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; 5 | import '@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol'; 6 | import '../utils/Ownable.sol'; 7 | 8 | /// @title TicketManager - manage ticket and verify ticket signature 9 | /// @author Omnuum Dev Team - 10 | contract TicketManager is EIP712 { 11 | constructor() EIP712(SIGNING_DOMAIN, SIGNATURE_VERSION) {} 12 | 13 | struct Ticket { 14 | address user; // owner of this ticket 15 | address nft; // ticket nft contract 16 | uint256 price; // price of mint with this ticket 17 | uint32 quantity; // possible mint quantity 18 | uint256 groupId; // ticket's group id 19 | bytes signature; // ticket's signature 20 | } 21 | 22 | /// @dev nft => groupId => end date 23 | mapping(address => mapping(uint256 => uint256)) public endDates; 24 | 25 | /// @dev nft => groupId => ticket owner => use count 26 | mapping(address => mapping(uint256 => mapping(address => uint32))) public ticketUsed; 27 | 28 | string private constant SIGNING_DOMAIN = 'OmnuumTicket'; 29 | string private constant SIGNATURE_VERSION = '1'; 30 | 31 | event SetTicketSchedule(address indexed nftContract, uint256 indexed groupId, uint256 endDate); 32 | 33 | event TicketMint( 34 | address indexed nftContract, 35 | address indexed minter, 36 | uint256 indexed groupId, 37 | uint32 quantity, 38 | uint32 maxQuantity, 39 | uint256 price 40 | ); 41 | 42 | /// @notice set end date for ticket group 43 | /// @param _nft nft contract 44 | /// @param _groupId id of ticket group 45 | /// @param _endDate end date timestamp 46 | function setEndDate( 47 | address _nft, 48 | uint256 _groupId, 49 | uint256 _endDate 50 | ) external { 51 | /// @custom:error (OO1) - Ownable: Caller is not the collection owner 52 | require(Ownable(_nft).owner() == msg.sender, 'OO1'); 53 | endDates[_nft][_groupId] = _endDate; 54 | 55 | emit SetTicketSchedule(_nft, _groupId, _endDate); 56 | } 57 | 58 | /// @notice use ticket for minting 59 | /// @param _signer address who is believed to be signer of ticket 60 | /// @param _minter address who is believed to be owner of ticket 61 | /// @param _quantity quantity of which minter is willing to mint 62 | /// @param _ticket ticket 63 | function useTicket( 64 | address _signer, 65 | address _minter, 66 | uint32 _quantity, 67 | Ticket calldata _ticket 68 | ) external { 69 | verify(_signer, msg.sender, _minter, _quantity, _ticket); 70 | 71 | ticketUsed[msg.sender][_ticket.groupId][_minter] += _quantity; 72 | emit TicketMint(msg.sender, _minter, _ticket.groupId, _quantity, _ticket.quantity, _ticket.price); 73 | } 74 | 75 | /// @notice verify ticket 76 | /// @param _signer address who is believed to be signer of ticket 77 | /// @param _nft nft contract address 78 | /// @param _minter address who is believed to be owner of ticket 79 | /// @param _quantity quantity of which minter is willing to mint 80 | /// @param _ticket ticket 81 | function verify( 82 | address _signer, 83 | address _nft, 84 | address _minter, 85 | uint32 _quantity, 86 | Ticket calldata _ticket 87 | ) public view { 88 | /// @custom:error (MT8) - Minting period is ended 89 | require(block.timestamp <= endDates[_nft][_ticket.groupId], 'MT8'); 90 | 91 | /// @custom:error (VR1) - False Signer 92 | require(_signer == recoverSigner(_ticket), 'VR1'); 93 | 94 | /// @custom:error (VR5) - False NFT 95 | require(_ticket.nft == _nft, 'VR5'); 96 | 97 | /// @custom:error (VR6) - False Minter 98 | require(_minter == _ticket.user, 'VR6'); 99 | 100 | /// @custom:error (MT3) - Remaining token count is not enough 101 | require(ticketUsed[_nft][_ticket.groupId][_minter] + _quantity <= _ticket.quantity, 'MT3'); 102 | } 103 | 104 | /// @dev recover signer from payload hash 105 | /// @param _ticket payload struct 106 | function recoverSigner(Ticket calldata _ticket) internal view returns (address) { 107 | bytes32 digest = _hash(_ticket); 108 | return ECDSA.recover(digest, _ticket.signature); 109 | } 110 | 111 | /// @dev hash payload 112 | /// @param _ticket payload struct 113 | function _hash(Ticket calldata _ticket) internal view returns (bytes32) { 114 | return 115 | _hashTypedDataV4( 116 | keccak256( 117 | abi.encode( 118 | keccak256('Ticket(address user,address nft,uint256 price,uint32 quantity,uint256 groupId)'), 119 | _ticket.user, 120 | _ticket.nft, 121 | _ticket.price, 122 | _ticket.quantity, 123 | _ticket.groupId 124 | ) 125 | ) 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /contracts/V1/OmnuumCAManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol'; 5 | import '../utils/OwnableUpgradeable.sol'; 6 | 7 | /// @title OmnuumCAManager - Contract Manager for Omnuum Protocol 8 | /// @author Omnuum Dev Team - 9 | /// @notice Use only purpose for Omnuum 10 | contract OmnuumCAManager is OwnableUpgradeable { 11 | using AddressUpgradeable for address; 12 | 13 | struct Contract { 14 | string topic; 15 | bool active; 16 | } 17 | 18 | /// @notice (omnuum contract address => (bytes32 topic => hasRole)) 19 | mapping(address => mapping(string => bool)) public roles; 20 | 21 | /// @notice (omnuum contract address => (topic, active)) 22 | mapping(address => Contract) public managerContracts; 23 | 24 | // @notice topic indexed mapping, (string topic => omnuum contract address) 25 | mapping(string => address) public indexedContracts; 26 | 27 | event ContractRegistered(address indexed managerContract, string topic); 28 | event ContractRemoved(address indexed managerContract, string topic); 29 | event RoleAdded(address indexed ca, string role); 30 | event RoleRemoved(address indexed ca, string role); 31 | 32 | function initialize() public initializer { 33 | __Ownable_init(); 34 | } 35 | 36 | /// @notice Add role to multiple addresses 37 | /// @param _CAs list of contract address which will have specified role 38 | /// @param _role role name to grant permission 39 | function addRole(address[] calldata _CAs, string calldata _role) external onlyOwner { 40 | uint256 len = _CAs.length; 41 | 42 | for (uint256 i = 0; i < len; i++) { 43 | /// @custom:error (AE2) - Contract address not acceptable 44 | require(_CAs[i].isContract(), 'AE2'); 45 | } 46 | 47 | for (uint256 i = 0; i < len; i++) { 48 | roles[_CAs[i]][_role] = true; 49 | emit RoleAdded(_CAs[i], _role); 50 | } 51 | } 52 | 53 | /// @notice Remove role to multiple addresses 54 | /// @param _CAs list of contract address which will be deprived specified role 55 | /// @param _role role name to be removed 56 | function removeRole(address[] calldata _CAs, string calldata _role) external onlyOwner { 57 | uint256 len = _CAs.length; 58 | for (uint256 i = 0; i < len; i++) { 59 | /// @custom:error (NX4) - Non-existent role to CA 60 | require(roles[_CAs[i]][_role], 'NX4'); 61 | roles[_CAs[i]][_role] = false; 62 | emit RoleRemoved(_CAs[i], _role); 63 | } 64 | } 65 | 66 | /// @notice Check whether target address has role or not 67 | /// @param _target address to be checked 68 | /// @param _role role name to be checked with 69 | /// @return whether target address has specified role or not 70 | function hasRole(address _target, string calldata _role) public view returns (bool) { 71 | return roles[_target][_role]; 72 | } 73 | 74 | /// @notice Register multiple addresses at once 75 | /// @param _CAs list of contract address which will be registered 76 | /// @param _topics topic list for each contract address 77 | function registerContractMultiple(address[] calldata _CAs, string[] calldata _topics) external onlyOwner { 78 | uint256 len = _CAs.length; 79 | /// @custom:error (ARG1) - Arguments length should be same 80 | require(_CAs.length == _topics.length, 'ARG1'); 81 | for (uint256 i = 0; i < len; i++) { 82 | registerContract(_CAs[i], _topics[i]); 83 | } 84 | } 85 | 86 | /// @notice Register contract address with topic 87 | /// @param _CA contract address 88 | /// @param _topic topic for address 89 | function registerContract(address _CA, string calldata _topic) public onlyOwner { 90 | /// @custom:error (AE1) - Zero address not acceptable 91 | require(_CA != address(0), 'AE1'); 92 | 93 | /// @custom:error (AE2) - Contract address not acceptable 94 | require(_CA.isContract(), 'AE2'); 95 | 96 | managerContracts[_CA] = Contract(_topic, true); 97 | indexedContracts[_topic] = _CA; 98 | emit ContractRegistered(_CA, _topic); 99 | } 100 | 101 | /// @notice Check whether contract address is registered 102 | /// @param _CA contract address 103 | /// @return isRegistered - boolean 104 | function checkRegistration(address _CA) public view returns (bool isRegistered) { 105 | return managerContracts[_CA].active; 106 | } 107 | 108 | /// @notice Remove contract address 109 | /// @param _CA contract address which will be removed 110 | function removeContract(address _CA) external onlyOwner { 111 | string memory topic = managerContracts[_CA].topic; 112 | delete managerContracts[_CA]; 113 | 114 | if (indexedContracts[topic] == _CA) { 115 | delete indexedContracts[topic]; 116 | } 117 | 118 | emit ContractRemoved(_CA, topic); 119 | } 120 | 121 | /// @notice Get contract address for specified topic 122 | /// @param _topic topic for address 123 | /// @return address which is registered with topic 124 | function getContract(string calldata _topic) public view returns (address) { 125 | return indexedContracts[_topic]; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /scripts/deployments/deployments.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | const chalk = require('chalk'); 3 | const CONSTANTS = require('../../utils/constants'); 4 | const DEP_CONSTANTS = require('./deployConstants'); 5 | const { deployProxy, deployNormal, deployBeacon, getChainName } = require('./deployHelper'); 6 | const { getPayloadWithSignature } = require('../interactions/interactionHelpers'); 7 | 8 | const deployManagers = async ({ deploySigner, signatureSignerAddress, walletOwnerAccounts }) => { 9 | /* Deploy CA Manager */ 10 | const caManager = await deployProxy({ 11 | contractName: 'OmnuumCAManager', 12 | deploySigner, 13 | }); 14 | 15 | /* Register CA Manager itself */ 16 | await ( 17 | await caManager.proxyContract.connect(deploySigner).registerContract(caManager.proxyContract.address, DEP_CONSTANTS.caManager.topic) 18 | ).wait(DEP_CONSTANTS.confirmWait); 19 | console.log(`\n${chalk.yellow('Complete self-registration to CA Manager')} - ${new Date()}`); 20 | 21 | /* Deploy Mint Manager */ 22 | const mintManager = await deployProxy({ 23 | contractName: 'OmnuumMintManager', 24 | deploySigner, 25 | args: [DEP_CONSTANTS.mintManager.feeRate, caManager.proxyContract.address], 26 | }); 27 | 28 | /* Deploy Exchange */ 29 | const exchange = await deployProxy({ 30 | contractName: 'OmnuumExchange', 31 | deploySigner, 32 | args: [caManager.proxyContract.address], 33 | }); 34 | 35 | /* Deploy Ticket Manager */ 36 | const ticketManager = await deployNormal({ 37 | contractName: 'TicketManager', 38 | deploySigner, 39 | }); 40 | 41 | /* Deploy Reveal Manager */ 42 | const revealManager = await deployNormal({ 43 | contractName: 'RevealManager', 44 | deploySigner, 45 | args: [caManager.proxyContract.address], 46 | }); 47 | 48 | /* Deploy Sender Verifier */ 49 | const senderVerifier = await deployNormal({ 50 | contractName: 'SenderVerifier', 51 | deploySigner, 52 | }); 53 | 54 | /* Deploy VRF Manager */ 55 | const vrfManager = await deployNormal({ 56 | contractName: 'OmnuumVRFManager', 57 | deploySigner, 58 | args: [...Object.values(DEP_CONSTANTS.vrfManager.chainlink[await getChainName()]), caManager.proxyContract.address], 59 | }); 60 | 61 | /* Deploy Wallet */ 62 | const wallet = await deployNormal({ 63 | contractName: 'OmnuumWallet', 64 | deploySigner, 65 | args: [DEP_CONSTANTS.wallet.consensusRatio, DEP_CONSTANTS.wallet.minLimitForConsensus, walletOwnerAccounts], 66 | }); 67 | 68 | /* Deploy NFT721 Beacon */ 69 | const nft = await deployBeacon({ 70 | contractName: 'OmnuumNFT721', 71 | deploySigner, 72 | }); 73 | 74 | const nftFactory = await deployNormal({ 75 | contractName: 'NftFactory', 76 | deploySigner, 77 | args: [caManager.proxyContract.address, nft.beacon.address, signatureSignerAddress], 78 | }); 79 | 80 | /* Register CA accounts to CA Manager */ 81 | console.log(`\n${chalk.green('Start Contract Registrations to CA Manager...')} - ${new Date()}`); 82 | await ( 83 | await caManager.proxyContract 84 | .connect(deploySigner) 85 | .registerContractMultiple( 86 | [ 87 | mintManager.proxyContract.address, 88 | exchange.proxyContract.address, 89 | ticketManager.contract.address, 90 | revealManager.contract.address, 91 | senderVerifier.contract.address, 92 | vrfManager.contract.address, 93 | wallet.contract.address, 94 | nftFactory.contract.address, 95 | ], 96 | [ 97 | CONSTANTS.ContractTopic.MINTMANAGER, 98 | CONSTANTS.ContractTopic.EXCHANGE, 99 | CONSTANTS.ContractTopic.TICKET, 100 | CONSTANTS.ContractTopic.REVEAL, 101 | CONSTANTS.ContractTopic.VERIFIER, 102 | CONSTANTS.ContractTopic.VRF, 103 | CONSTANTS.ContractTopic.WALLET, 104 | CONSTANTS.ContractTopic.NFTFACTORY, 105 | ], 106 | ) 107 | ).wait(DEP_CONSTANTS.confirmWait); 108 | console.log(`${chalk.yellow('Complete!')} - ${new Date()}`); 109 | 110 | /* Register contract roles to CA manager */ 111 | /* VRF manager => EXCHANGE role */ 112 | /* Reveal manager => VRF role */ 113 | 114 | console.log(`\n${chalk.green('Start Role Additions to CA Manager...')} - ${new Date()}`); 115 | await (await caManager.proxyContract.connect(deploySigner).addRole([vrfManager.contract.address], DEP_CONSTANTS.roles.EXCHANGE)).wait(); 116 | await ( 117 | await caManager.proxyContract.connect(deploySigner).addRole([revealManager.contract.address], DEP_CONSTANTS.roles.VRF) 118 | ).wait(DEP_CONSTANTS.confirmWait); 119 | console.log(`${chalk.yellow('Complete!')} - ${new Date()}`); 120 | 121 | return { 122 | nft, 123 | nftFactory, 124 | vrfManager, 125 | mintManager, 126 | caManager, 127 | exchange, 128 | ticketManager, 129 | senderVerifier, 130 | revealManager, 131 | wallet, 132 | }; 133 | }; 134 | 135 | const deployNFT = async ({ 136 | projectOwnerSigner, 137 | signerPrivateKey, 138 | senderVerifierAddress, 139 | maxSupply, 140 | coverUri, 141 | nftFactoryAddress, 142 | collectionId, 143 | name, 144 | symbol, 145 | }) => { 146 | /* Deploy NFT721 Beacon Proxy */ 147 | const NftFactory = await ethers.getContractFactory('NftFactory'); 148 | 149 | // in this case, minterAddress means nft project owner 150 | const payload = await getPayloadWithSignature({ 151 | senderVerifierAddress, 152 | minterAddress: projectOwnerSigner.address, 153 | payloadTopic: CONSTANTS.payloadTopic.deployCol, 154 | groupId: collectionId, 155 | signerPrivateKey, 156 | }); 157 | 158 | const txResponse = await NftFactory.attach(nftFactoryAddress) 159 | .connect(projectOwnerSigner) 160 | .deploy(maxSupply, coverUri, collectionId, name, symbol, payload); 161 | const deployReceipt = await txResponse.wait(); 162 | 163 | const { logs } = deployReceipt; 164 | 165 | const { 166 | args: { nftContract: beaconProxyAddress }, 167 | } = NftFactory.interface.parseLog(logs[logs.length - 1]); 168 | 169 | return { beaconProxyAddress, deployReceipt }; 170 | }; 171 | 172 | module.exports = { deployNFT, deployManagers }; 173 | -------------------------------------------------------------------------------- /scripts/upgrades/upgradeBeacon.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers, upgrades } = require('hardhat'); 3 | const chalk = require('chalk'); 4 | const { mkdir, writeFile } = require('fs/promises'); 5 | const { getRPCProvider, nullCheck, getDateSuffix, getChainName } = require('../deployments/deployHelper'); 6 | const { getWalletFromMnemonic } = require('../../utils/walletFromMnemonic'); 7 | const { s3Upload } = require('../../utils/s3.js'); 8 | 9 | const inquirerParams = { 10 | deployer_private_key: 'deployer_private_key', 11 | beacon_address: 'beacon_address', 12 | signer_method: 'signer_method', 13 | proceed: 'proceed', 14 | localSave: 'localSave', 15 | s3Save: 's3Save', 16 | }; 17 | 18 | const questions = [ 19 | { 20 | name: inquirerParams.beacon_address, 21 | type: 'input', 22 | message: chalk.yellowBright('🤔 Beacon address (for NFT721) to be upgraded is...'), 23 | validate: nullCheck, 24 | }, 25 | { 26 | name: inquirerParams.signer_method, 27 | type: 'list', 28 | message: chalk.yellowBright('🤔 How to create deployer signer'), 29 | choices: ['from_PK', 'from_mnemonic'], 30 | validate: nullCheck, 31 | }, 32 | ]; 33 | 34 | (async () => { 35 | console.log( 36 | chalk.yellow(` 37 | ###### 38 | # # ##### #### ##### ## ##### ###### # # ###### ## #### #### # # 39 | # # # # # # # # # # # # # # # # # # # # # # ## # 40 | # # # # # # # # # # # ##### ###### ##### # # # # # # # # 41 | # # ##### # ### ##### ###### # # # # # # ###### # # # # # # 42 | # # # # # # # # # # # # # # # # # # # # # # ## 43 | #### # #### # # # # ##### ###### ###### ###### # # #### #### # # 44 | 45 | `), 46 | ); 47 | 48 | inquirer.prompt(questions).then(async (ans) => { 49 | try { 50 | const dirPath = './scripts/deployments/deployResults/upgrades'; 51 | await mkdir(dirPath, { recursive: true }); 52 | const chainName = await getChainName(); 53 | 54 | const provider = await getRPCProvider(ethers.provider); 55 | 56 | let deployerSigner; 57 | if (ans.signer_method === 'from_PK') { 58 | const { deployer_private_key } = await inquirer.prompt([ 59 | { 60 | name: inquirerParams.deployer_private_key, 61 | type: 'input', 62 | message: chalk.yellowBright('🤔 Deployer Private Key is...'), 63 | validate: nullCheck, 64 | }, 65 | ]); 66 | deployerSigner = new ethers.Wallet(deployer_private_key, provider); 67 | } else if (ans.signer_method === 'from_mnemonic') { 68 | deployerSigner = await getWalletFromMnemonic(provider); 69 | } 70 | 71 | const deployerAddress = deployerSigner.address; 72 | const deployerBalance = ethers.utils.formatEther(await deployerSigner.getBalance()); 73 | 74 | const { proceed } = await inquirer.prompt([ 75 | { 76 | name: inquirerParams.proceed, 77 | type: 'confirm', 78 | message: chalk.yellow( 79 | `${chalk.redBright( 80 | 'ATTENTION!!!!!!', 81 | )}\n\tDeployer: ${deployerAddress}\n\tBalance: ${deployerBalance} ETH\n\tNetwork: ${chainName}\n ${chalk.red( 82 | '=> Do you want to proceed?', 83 | )}`, 84 | ), 85 | }, 86 | ]); 87 | 88 | // eslint-disable-next-line consistent-return 89 | if (!proceed) return; 90 | 91 | const ContractFactory = (await ethers.getContractFactory('OmnuumNFT721')).connect(deployerSigner); 92 | const BeaconContract = new ethers.Contract( 93 | ans.beacon_address, 94 | new ethers.utils.Interface(['function implementation() external view returns(address)']), 95 | provider, 96 | ); 97 | 98 | const previousImplAddress = await BeaconContract.implementation(); 99 | 100 | // Go Beacon Upgrade! 101 | console.log(chalk.greenBright('!!! Starting Beacon Upgrade --- NFT721')); 102 | const upgraded = await upgrades.upgradeBeacon(ans.beacon_address, ContractFactory); 103 | const txReceipt = await upgraded.deployTransaction.wait(); 104 | 105 | const iFace = new ethers.utils.Interface(['event Upgraded(address indexed implementation)']); 106 | const { implementation } = iFace.parseLog(txReceipt.events[0]).args; 107 | 108 | const resultData = { 109 | upgradeTarget: 'OmnuumNFT721', 110 | chain: chainName, 111 | timeStamp: new Date().toLocaleString(), 112 | deployer: deployerSigner.address, 113 | beaconAddress: txReceipt.to, 114 | previousImplAddress, 115 | upgradedImplAddress: implementation, 116 | transaction: txReceipt.transactionHash, 117 | }; 118 | console.log(chalk.yellowBright('\n☀️ Result\n'), resultData); 119 | console.log(chalk.greenBright('\nNew NFT721 Implementation deployed, then Beacon upgrade is done!')); 120 | 121 | inquirer 122 | .prompt([ 123 | { 124 | name: inquirerParams.localSave, 125 | type: 'confirm', 126 | message: chalk.yellow(`Save result JSON file to ${chalk.redBright('local')}`), 127 | }, 128 | { 129 | name: inquirerParams.s3Save, 130 | type: 'confirm', 131 | message: chalk.yellow(`Save result JSON file to ${chalk.redBright('S3')}`), 132 | }, 133 | ]) 134 | .then(async (result) => { 135 | const filename = `${chainName}_${getDateSuffix()}_NFT721_upgrade.json`; 136 | if (result.localSave) { 137 | await writeFile(`${dirPath}/${filename}`, JSON.stringify(resultData), 'utf8'); 138 | } 139 | if (result.s3Save) { 140 | await s3Upload({ 141 | bucketName: 'omnuum-prod-website-resources', 142 | keyName: `contracts/upgrades/${filename}`, 143 | fileBuffer: Buffer.from(JSON.stringify(resultData)), 144 | }); 145 | } 146 | }); 147 | } catch (e) { 148 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 149 | } 150 | }); 151 | })(); 152 | -------------------------------------------------------------------------------- /utils/constants.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | 3 | module.exports = { 4 | events: { 5 | NFT: { 6 | Transfer: 'Transfer', // address indexed from, address indexed to, uint256 indexed tokenId 7 | Ticket: 'Ticket', 8 | Public: 'Public', 9 | BaseURIChanged: 'BaseURIChanged', 10 | BalanceTransferred: 'BalanceTransferred', 11 | EtherReceived: 'EtherReceived', 12 | Revealed: 'Revealed', 13 | }, 14 | MintManager: { 15 | SetSpecialFeeRate: 'SetSpecialFeeRate', 16 | ChangeFeeRate: 'ChangeFeeRate', 17 | Airdrop: 'Airdrop', 18 | SetPublicSchedule: 'SetPublicSchedule', 19 | PublicMint: 'PublicMint', 20 | SetMinFee: 'SetMinFee', 21 | }, 22 | CAManager: { 23 | RoleAdded: 'RoleAdded', 24 | RoleRemoved: 'RoleRemoved', 25 | ContractRegistered: 'ContractRegistered', 26 | ContractRemoved: 'ContractRemoved', 27 | NftContractRegistered: 'NftContractRegistered', 28 | }, 29 | TicketManager: { 30 | SetTicketSchedule: 'SetTicketSchedule', 31 | TicketMint: 'TicketMint', 32 | }, 33 | VRFManager: { 34 | RequestVRF: 'RequestVRF', 35 | ResponseVRF: 'ResponseVRF', 36 | Updated: 'Updated', 37 | }, 38 | Wallet: { 39 | PaymentReceived: 'PaymentReceived', 40 | EtherReceived: 'EtherReceived', 41 | Requested: 'Requested', 42 | Approved: 'Approved', 43 | Revoked: 'Revoked', 44 | Canceled: 'Canceled', 45 | Executed: 'Executed', 46 | }, 47 | }, 48 | reasons: { 49 | common: { 50 | initialize: 'Initializable: contract is already initialized', 51 | onlyOwner: 'Ownable: caller is not the owner', 52 | notExistTokenQuery: 'ERC721: owner query for nonexistent token', 53 | }, 54 | RevertMessage: { 55 | silent: 'Transaction reverted silently', 56 | }, 57 | code: { 58 | // Arguments Error 59 | ARG1: 'ARG1', // Arguments length should be same 60 | ARG2: 'ARG2', // Arguments are not correct 61 | ARG3: 'ARG3', // Not enough ether sent 62 | // Only Owner Error 63 | OO1: 'OO1', // Ownable: Caller is not the collection owner 64 | OO2: 'OO2', // Only Omnuum or owner can change 65 | OO3: 'OO3', // Only Omnuum can call 66 | OO4: 'OO4', // Only the owner of the wallet is allowed 67 | OO5: 'OO5', // Already the owner of the wallet 68 | OO6: 'OO6', // Only the requester is allowed 69 | OO7: 'OO7', // Only role owner can access 70 | OO8: 'OO8', // Only reveal manager allowed 71 | OO9: 'OO9', // Caller is not owner nor approved 72 | // Not Exist Error 73 | NX1: 'NX1', // ERC721Metadata: URI query for nonexistent token 74 | NX2: 'NX2', // Non-existent wallet account 75 | NX3: 'NX3', // Non-existent owner request 76 | NX4: 'NX4', // Non-existent role to CA 77 | // Number Error 78 | NE1: 'NE1', // Fee rate should be lower than 100% 79 | NE2: 'NE2', // Not reach consensus 80 | NE3: 'NE3', // A zero payment is not acceptable 81 | NE4: 'NE4', // Insufficient balance 82 | NE5: 'NE5', // Violate min limit for consensus 83 | // State Error 84 | SE1: 'SE1', // Already executed 85 | SE2: 'SE2', // Request canceled 86 | SE3: 'SE3', // Already voted 87 | SE4: 'SE4', // Not voted 88 | SE5: 'SE5', // Address: unable to send value, recipient may have reverted 89 | SE6: 'SE6', // NFT already revealed 90 | SE7: 'SE7', // Not enough LINK at exchange contract 91 | SE8: 'SE8', // Already used address 92 | // Mint Error 93 | MT1: 'MT1', // There is no available ticket 94 | MT2: 'MT2', // Cannot mint more than possible amount per address 95 | MT3: 'MT3', // Remaining token count is not enough 96 | MT5: 'MT5', // Not enough money 97 | MT7: 'MT7', // Mint is ended 98 | MT8: 'MT8', // Minting period is ended 99 | MT9: 'MT9', // Minter cannot be CA 100 | // Address Error 101 | AE1: 'AE1', // Zero address not acceptable 102 | AE2: 'AE2', // Contract address not acceptable 103 | // Verification Error 104 | VR1: 'VR1', // False Signer 105 | VR2: 'VR2', // False Nonce 106 | VR3: 'VR3', // False Topic 107 | VR4: 'VR4', // False Sender 108 | VR5: 'VR5', // False NFT 109 | VR6: 'VR6', // False Minter 110 | }, 111 | }, 112 | contractRole: { 113 | exchange: 'EXCHANGE', 114 | vrf: 'VRF', 115 | }, 116 | payloadTopic: { 117 | mint: 'MINT', 118 | ticket: 'TICKET', 119 | deployCol: 'DEPLOY_COL', 120 | }, 121 | ContractTopic: { 122 | VRF: 'VRF', 123 | NFT: 'NFT', 124 | VERIFIER: 'VERIFIER', 125 | TICKET: 'TICKET', 126 | MINTMANAGER: 'MINTMANAGER', 127 | EXCHANGE: 'EXCHANGE', 128 | WALLET: 'WALLET', 129 | TEST: 'TEST', 130 | REVEAL: 'REVEAL', 131 | CAMANAGER: 'CAMANAGER', 132 | NFTFACTORY: 'NFTFACTORY', 133 | }, 134 | vrfTopic: { 135 | REVEAL_PFP: 'REVEAL_PFP', 136 | }, 137 | chainlink: { 138 | mainnet: { 139 | LINK: '0x514910771af9ca656af840dff83e8264ecf986ca', 140 | COORD: '0xf0d54349aDdcf704F77AE15b96510dEA15cb7952', 141 | hash: '0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445', 142 | fee: ethers.utils.parseEther('2'), 143 | }, 144 | rinkeby: { 145 | LINK: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709', 146 | COORD: '0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B', 147 | hash: '0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311', 148 | fee: ethers.utils.parseEther('0.1'), 149 | }, 150 | }, 151 | testValues: { 152 | paymentTarget: '0x0000000000000000DEad0000000000000000DEad', 153 | paymentDescription: 'Test for Payment', 154 | paymentTestTopic: ethers.utils.keccak256(ethers.utils.toUtf8Bytes('TEST')), 155 | zeroOwnerAccount: { addr: ethers.constants.AddressZero, vote: 0 }, 156 | feeRate: 5000, // converted as 0.05 (5 percent) 157 | specialFeeRate: 10000, // converted as 0.1 (10 percent) 158 | walletOwnersLen: 3, 159 | minFee: ethers.utils.parseEther('0.0005'), 160 | sendEthValue: '10', 161 | consensusRatio: 55, 162 | minLimitForConsensus: 3, 163 | mintFee: 2500, // 0.025 == 2.5% 164 | coverUri: 'https://cover.com/', 165 | baseURI: 'https://reveal.com/', 166 | tmpLinkExRate: ethers.utils.parseEther('0.01466666666'), 167 | collectionId: 71, 168 | name: 'hello', 169 | symbol: 'WRD', 170 | }, 171 | }; 172 | -------------------------------------------------------------------------------- /contracts/V1/OmnuumVRFManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@chainlink/contracts/src/v0.8/VRFConsumerBase.sol'; 5 | import '../utils/Ownable.sol'; 6 | import './OmnuumCAManager.sol'; 7 | import './OmnuumExchange.sol'; 8 | import '../library/RevertMessage.sol'; 9 | 10 | /// @title OmnuumVRFManager - Manage VRF logic for omnuum 11 | /// @author Omnuum Dev Team - 12 | /// @notice Use only purpose for Omnuum 13 | contract OmnuumVRFManager is Ownable, VRFConsumerBase { 14 | address private s_LINK; 15 | uint256 private fee; 16 | bytes32 private s_key_hash; 17 | address private omA; 18 | 19 | OmnuumCAManager private caManager; 20 | 21 | /// @notice safety margin ratio of LINK/ETH exchange rate to prevent risk of price volatility 22 | /// @dev 2 decimals (150 == 1.5) 23 | uint16 public safetyRatio = 150; 24 | 25 | constructor( 26 | address _LINK, 27 | address _vrf_coord, 28 | bytes32 _key_hash, 29 | uint256 _fee, 30 | address _omnuumCA 31 | ) VRFConsumerBase(_vrf_coord, _LINK) { 32 | /// @custom:error (AE1) - Zero address not acceptable 33 | require(_LINK != address(0), 'AE1'); 34 | require(_vrf_coord != address(0), 'AE1'); 35 | require(_omnuumCA != address(0), 'AE1'); 36 | 37 | s_LINK = _LINK; 38 | s_key_hash = _key_hash; 39 | fee = _fee; 40 | caManager = OmnuumCAManager(_omnuumCA); 41 | } 42 | 43 | /// @notice request address to request ID 44 | mapping(address => bytes32) public aToId; 45 | 46 | /// @notice request ID to request address 47 | mapping(bytes32 => address) public idToA; 48 | 49 | /// @notice request ID to topic 50 | mapping(bytes32 => string) public idToTopic; 51 | 52 | /// @dev actionType: fee, safetyRatio 53 | event Updated(uint256 value, string actionType); 54 | event RequestVRF(address indexed roller, bytes32 indexed requestId, string topic); 55 | event ResponseVRF(bytes32 indexed requestId, uint256 randomness, string topic, bool success, string reason); 56 | 57 | /// @notice request vrf call 58 | /// @dev only allowed contract which has VRF role 59 | /// @param _topic contract which will use this vrf result 60 | function requestVRF(string calldata _topic) external payable { 61 | address exchangeAddress = caManager.getContract('EXCHANGE'); 62 | 63 | // @custom:error (OO7) - Only role owner can access 64 | require(caManager.hasRole(msg.sender, 'VRF'), 'OO7'); 65 | 66 | // @custom:error (SE7) - Not enough LINK at exchange contract 67 | require(LINK.balanceOf(exchangeAddress) >= fee, 'SE7'); 68 | 69 | /// @custom:dev receive link from exchange, send all balance because there isn't any withdraw feature 70 | OmnuumExchange(exchangeAddress).exchangeToken{ value: address(this).balance }(s_LINK, fee, address(this)); 71 | 72 | bytes32 requestId = requestRandomness(s_key_hash, fee); 73 | idToA[requestId] = msg.sender; 74 | idToTopic[requestId] = _topic; 75 | 76 | emit RequestVRF(msg.sender, requestId, _topic); 77 | } 78 | 79 | /// @notice request vrf call 80 | /// @dev only allowed contract which has VRF role 81 | /// @dev Can use this function only once per target address 82 | /// @param _targetAddress contract which will use this vrf result 83 | /// @param _topic contract which will use this vrf result 84 | function requestVRFOnce(address _targetAddress, string calldata _topic) external payable { 85 | /// @custom:error (SE8) - Already used address 86 | require(aToId[_targetAddress] == '', 'SE8'); 87 | 88 | address exchangeAddress = caManager.getContract('EXCHANGE'); 89 | 90 | // @custom:error (OO7) - Only role owner can access 91 | require(caManager.hasRole(msg.sender, 'VRF'), 'OO7'); 92 | 93 | /// @custom:error (SE7) - Not enough LINK at exchange contract 94 | require(LINK.balanceOf(exchangeAddress) >= fee, 'SE7'); 95 | 96 | uint256 required_amount = OmnuumExchange(exchangeAddress).getExchangeAmount(address(0), s_LINK, fee); 97 | 98 | /// @custom:error (ARG3) - Not enough ether sent 99 | require(msg.value >= (required_amount * safetyRatio) / 100, 'ARG3'); 100 | 101 | /// @custom:dev receive link from exchange, send all balance because there isn't any withdraw feature 102 | OmnuumExchange(exchangeAddress).exchangeToken{ value: address(this).balance }(s_LINK, fee, address(this)); 103 | 104 | bytes32 requestId = requestRandomness(s_key_hash, fee); 105 | idToA[requestId] = _targetAddress; 106 | idToTopic[requestId] = _topic; 107 | 108 | emit RequestVRF(_targetAddress, requestId, _topic); 109 | } 110 | 111 | /// @notice hook function which called when vrf response received 112 | /// @param _requestId used to find request history and emit event for matching info 113 | /// @param _randomness result number of VRF 114 | function fulfillRandomness(bytes32 _requestId, uint256 _randomness) internal override { 115 | address requestAddress = idToA[_requestId]; 116 | /// @custom:dev Not required to implement, but if developer wants to do specific action at response time, he/she should implement vrfResponse method at target contract 117 | bytes memory payload = abi.encodeWithSignature('vrfResponse(uint256)', _randomness); 118 | (bool success, bytes memory returnData) = address(requestAddress).call(payload); 119 | 120 | string memory reason = success ? '' : RevertMessage.parse(returnData); 121 | 122 | aToId[requestAddress] = _requestId; 123 | delete idToA[_requestId]; 124 | 125 | emit ResponseVRF(_requestId, _randomness, idToTopic[_requestId], success, reason); 126 | } 127 | 128 | /// @notice update ChainLink VRF fee 129 | /// @param _fee fee of ChainLink VRF 130 | function updateFee(uint256 _fee) external onlyOwner { 131 | fee = _fee; 132 | emit Updated(_fee, 'vrfFee'); 133 | } 134 | 135 | /// @notice update safety ratio 136 | /// @param _safetyRatio safety margin ratio of LINK/ETH exchange rate to prevent risk of price volatility 137 | function updateSafetyRatio(uint16 _safetyRatio) external onlyOwner { 138 | /// @custom:error (NE6) - Margin rate should above or equal 100 139 | require(_safetyRatio >= 100, 'NE6'); 140 | safetyRatio = _safetyRatio; 141 | emit Updated(_safetyRatio, 'safetyRatio'); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /test/exchange.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { ethers, upgrades } = require('hardhat'); 3 | const Constants = require('../utils/constants.js'); 4 | require('chai').should(); 5 | 6 | Error.stackTraceLimit = Infinity; 7 | 8 | const { prepareDeploy, prepareMockDeploy, testDeploy } = require('./etc/mock.js'); 9 | const { nullAddress, isLocalNetwork } = require('./etc/util.js'); 10 | 11 | upgrades.silenceWarnings(); 12 | 13 | describe('OmnuumExchange', () => { 14 | before(async () => { 15 | await prepareDeploy.call(this); 16 | await prepareMockDeploy.call(this); 17 | }); 18 | 19 | beforeEach(async () => { 20 | this.accounts = await ethers.getSigners(); 21 | await testDeploy.call(this, this.accounts); 22 | }); 23 | 24 | describe('Security', () => { 25 | it('[Revert] Should not initialize after deploy', async () => { 26 | const { omnuumExchange, accounts } = this; 27 | 28 | await expect(omnuumExchange.connect(accounts[1]).initialize(accounts[1].address)).to.be.revertedWith( 29 | Constants.reasons.common.initialize, 30 | ); 31 | }); 32 | }); 33 | 34 | describe('[Method] getExchangeAmount', () => { 35 | it('Get Exchange Amount for token', async () => { 36 | const { omnuumExchange } = this; 37 | const amount = ethers.utils.parseEther('2'); 38 | const exchangeAmount = await omnuumExchange.getExchangeAmount(nullAddress, Constants.chainlink.rinkeby.LINK, amount); 39 | const tmpLinkExRate = await omnuumExchange.tmpLinkExRate(); 40 | 41 | expect(exchangeAmount).to.be.equal(tmpLinkExRate.mul(amount).div(ethers.utils.parseEther('1'))); 42 | }); 43 | }); 44 | 45 | describe('[Method] updateTmpExchangeRate', () => { 46 | it('should update exchangeRate', async () => { 47 | const { omnuumExchange } = this; 48 | const new_rate = ethers.utils.parseEther('0.1'); 49 | 50 | const tx = await omnuumExchange.updateTmpExchangeRate(new_rate); 51 | await tx.wait(); 52 | 53 | expect(await omnuumExchange.tmpLinkExRate()).to.be.equal(new_rate); 54 | }); 55 | it('[Revert] Only omnuum can update rate', async () => { 56 | const { omnuumExchange, accounts } = this; 57 | 58 | const rate = ethers.utils.parseEther('0.1'); 59 | 60 | await expect(omnuumExchange.connect(accounts[1]).updateTmpExchangeRate(rate)).to.be.revertedWith(Constants.reasons.common.onlyOwner); 61 | }); 62 | }); 63 | 64 | describe('[Method] exchangeToken', () => { 65 | // !! Only work for rinkeby or mainnet, in local, LINK token contract is required 66 | it('Receive token from exchange (rinkeby)', async () => { 67 | if (await isLocalNetwork(ethers.provider)) return; 68 | const { omnuumExchange, accounts } = this; 69 | const amount = 2; 70 | const tx = await omnuumExchange.exchangeToken(Constants.chainlink.rinkeby.LINK, amount, accounts[1].address); 71 | 72 | await tx.wait(); 73 | 74 | expect(tx) 75 | .to.emit(omnuumExchange, 'Exchange') 76 | .withArgs(nullAddress, Constants.chainlink.rinkeby.LINK, amount, accounts[0].address, accounts[1].address); 77 | }); 78 | it('Receive token from exchange (local mock)', async () => { 79 | if (!(await isLocalNetwork(ethers.provider))) return; 80 | const { omnuumExchange, mockExchange, accounts, mockLink } = this; 81 | const amount = 2; 82 | 83 | const tx = await mockExchange.exchangeToken(omnuumExchange.address, mockLink.address, amount, accounts[1].address); 84 | 85 | await tx.wait(); 86 | 87 | expect(tx) 88 | .to.emit(omnuumExchange, 'Exchange') 89 | .withArgs(nullAddress, mockLink.address, amount, mockExchange.address, accounts[1].address); 90 | }); 91 | it('[Revert] Check sender has EXCHANGE role', async () => { 92 | const { 93 | omnuumExchange, 94 | accounts: [, not_omnuum_eoa], 95 | } = this; 96 | const amount = 2; 97 | 98 | await expect( 99 | omnuumExchange.connect(not_omnuum_eoa).exchangeToken(Constants.chainlink.rinkeby.LINK, amount, not_omnuum_eoa.address), 100 | ).to.be.revertedWith(Constants.reasons.code.OO7); 101 | }); 102 | }); 103 | 104 | describe('[Method] withdraw', () => { 105 | it('Withdraw successfully', async () => { 106 | const { 107 | omnuumExchange, 108 | mockExchange, 109 | mockLink, 110 | omnuumWallet, 111 | accounts: [depositer], 112 | } = this; 113 | 114 | const value = ethers.utils.parseEther('5'); 115 | const test_link_move_amount = ethers.utils.parseEther('1'); 116 | 117 | // mock proxy request through mockExchange -> omnuumExchange 118 | // depositer send 5 ether to exchange contract 119 | const deposit_tx = await mockExchange.exchangeToken( 120 | omnuumExchange.address, 121 | mockLink.address, 122 | test_link_move_amount, 123 | depositer.address, 124 | { 125 | value, 126 | }, 127 | ); 128 | 129 | await deposit_tx.wait(); 130 | 131 | const exchange_balance = await ethers.provider.getBalance(omnuumExchange.address); 132 | const wallet_prev_bal = await ethers.provider.getBalance(omnuumWallet.address); 133 | 134 | await (await omnuumExchange.withdraw(exchange_balance)).wait(); 135 | 136 | const wallet_cur_bal = await ethers.provider.getBalance(omnuumWallet.address); 137 | 138 | expect(wallet_cur_bal).to.equal(wallet_prev_bal.add(value)); 139 | }); 140 | it('[Revert] Only omnuum can withdraw', async () => { 141 | const { omnuumExchange, accounts } = this; 142 | 143 | await expect(omnuumExchange.connect(accounts[1]).withdraw(1)).to.be.revertedWith(Constants.reasons.common.onlyOwner); 144 | }); 145 | it('[Revert] Withdraw more than contract\'s balance', async () => { 146 | const { 147 | omnuumExchange, 148 | mockExchange, 149 | mockLink, 150 | accounts: [depositer], 151 | } = this; 152 | 153 | const value = ethers.utils.parseEther('5'); 154 | const test_link_move_amount = ethers.utils.parseEther('1'); 155 | 156 | // mock proxy request through mockExchange -> omnuumExchange 157 | // depositer send 5 ether to exchange contract 158 | const deposit_tx = await mockExchange.exchangeToken( 159 | omnuumExchange.address, 160 | mockLink.address, 161 | test_link_move_amount, 162 | depositer.address, 163 | { 164 | value, 165 | }, 166 | ); 167 | 168 | await deposit_tx.wait(); 169 | 170 | const exchange_balance = await ethers.provider.getBalance(omnuumExchange.address); 171 | 172 | await expect(omnuumExchange.withdraw(exchange_balance.add(1))).to.be.revertedWith(Constants.reasons.code.ARG2); 173 | }); 174 | }); 175 | }); 176 | -------------------------------------------------------------------------------- /scripts/upgrades/upgradeProxy.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { ethers, upgrades } = require('hardhat'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const chalk = require('chalk'); 6 | const { getAdminAddress } = require('@openzeppelin/upgrades-core'); 7 | const { mkdir, writeFile } = require('fs/promises'); 8 | const { nullCheck, getRPCProvider, getChainName, getDateSuffix } = require('../deployments/deployHelper'); 9 | const { getWalletFromMnemonic } = require('../../utils/walletFromMnemonic'); 10 | const { s3Upload } = require('../../utils/s3.js'); 11 | 12 | const inquirerParams = { 13 | deployer_private_key: 'deployer_private_key', 14 | proxy_address: 'proxy_address', 15 | contract_name: 'contract_name', 16 | signer_method: 'signer_method', 17 | proceed: 'proceed', 18 | localSave: 'localSave', 19 | s3Save: 's3Save', 20 | }; 21 | 22 | const getSolidityFileList = fs 23 | .readdirSync(path.resolve(__dirname, '../../contracts/V1')) 24 | .map((filename) => filename.substr(0, filename.indexOf('.'))); 25 | 26 | const questions = [ 27 | { 28 | name: inquirerParams.contract_name, 29 | type: 'list', 30 | message: chalk.yellowBright('🤔 Choose contract you want to upgrade is ...'), 31 | choices: getSolidityFileList, 32 | }, 33 | { 34 | name: inquirerParams.proxy_address, 35 | type: 'input', 36 | message: chalk.yellowBright('🤔 Proxy Address to be upgraded is...'), 37 | validate: nullCheck, 38 | }, 39 | { 40 | name: inquirerParams.signer_method, 41 | type: 'list', 42 | message: chalk.yellowBright('🤔 How to create deployer signer'), 43 | choices: ['from_PK', 'from_mnemonic'], 44 | validate: nullCheck, 45 | }, 46 | ]; 47 | 48 | (async () => { 49 | console.log( 50 | chalk.yellow(` 51 | ###### 52 | # # ##### #### ##### ## ##### ###### # # ##### #### # # # # 53 | # # # # # # # # # # # # # # # # # # # # # # # 54 | # # # # # # # # # # # ##### ###### # # # # ## # 55 | # # ##### # ### ##### ###### # # # # ##### # # ## # 56 | # # # # # # # # # # # # # # # # # # # # 57 | #### # #### # # # # ##### ###### # # # #### # # # 58 | `), 59 | ); 60 | inquirer.prompt(questions).then(async (ans) => { 61 | try { 62 | const dirPath = './scripts/deployments/deployResults/upgrades'; 63 | await mkdir(dirPath, { recursive: true }); 64 | const chainName = await getChainName(); 65 | 66 | const provider = await getRPCProvider(ethers.provider); 67 | 68 | let deployerSigner; 69 | if (ans.signer_method === 'from_PK') { 70 | const { deployer_private_key } = await inquirer.prompt([ 71 | { 72 | name: inquirerParams.deployer_private_key, 73 | type: 'input', 74 | message: chalk.yellowBright('🤔 Deployer Private Key is...'), 75 | validate: nullCheck, 76 | }, 77 | ]); 78 | deployerSigner = new ethers.Wallet(deployer_private_key, provider); 79 | } else if (ans.signer_method === 'from_mnemonic') { 80 | deployerSigner = await getWalletFromMnemonic(provider); 81 | } 82 | 83 | const deployerAddress = deployerSigner.address; 84 | const deployerBalance = ethers.utils.formatEther(await deployerSigner.getBalance()); 85 | 86 | const { proceed } = await inquirer.prompt([ 87 | { 88 | name: inquirerParams.proceed, 89 | type: 'confirm', 90 | message: chalk.yellow( 91 | `ATTENTION!!!!!!\n\tDeployer: ${deployerAddress}\n\tBalance: ${deployerBalance} ETH\n\tNetwork: ${chainName}\n ${chalk.red( 92 | '=> Do you want to proceed?', 93 | )}`, 94 | ), 95 | }, 96 | ]); 97 | 98 | // eslint-disable-next-line consistent-return 99 | if (!proceed) return; 100 | 101 | const ContractFactory = (await ethers.getContractFactory(ans.contract_name)).connect(deployerSigner); 102 | 103 | const adminAddress = await getAdminAddress(provider, ans.proxy_address); 104 | const AdminContract = new ethers.Contract( 105 | adminAddress, 106 | new ethers.utils.Interface(['function getProxyImplementation(address proxy) public view returns (address)']), 107 | provider, 108 | ); 109 | 110 | const previousImplAddress = await AdminContract.getProxyImplementation(ans.proxy_address); 111 | 112 | // Go Proxy Upgrade! 113 | console.log(chalk.greenBright(`!!! Starting Proxy Upgrade --- ${ans.contract_name}`)); 114 | const upgraded = await upgrades.upgradeProxy(ans.proxy_address, ContractFactory); 115 | const txReceipt = await upgraded.deployTransaction.wait(); 116 | 117 | const iFace = new ethers.utils.Interface(['event Upgraded(address indexed implementation)']); 118 | const { implementation } = iFace.parseLog(txReceipt.events[0]).args; 119 | 120 | const resultData = { 121 | upgradeTarget: `${ans.contract_name}`, 122 | chain: chainName, 123 | timeStamp: new Date().toLocaleString(), 124 | deployer: deployerSigner.address, 125 | proxyAddress: ans.proxy_address, 126 | adminAddress, 127 | previousImplAddress, 128 | upgradedImplAddress: implementation, 129 | }; 130 | 131 | console.log(chalk.yellowBright('\n☀️ Result\n'), resultData); 132 | console.log(chalk.yellow(`\n${ans.contract_name} Proxy upgrade is done!`)); 133 | 134 | inquirer 135 | .prompt([ 136 | { 137 | name: inquirerParams.localSave, 138 | type: 'confirm', 139 | message: chalk.yellow(`Save result JSON file to ${chalk.redBright('local')}`), 140 | }, 141 | { 142 | name: inquirerParams.s3Save, 143 | type: 'confirm', 144 | message: chalk.yellow(`Save result JSON file to ${chalk.redBright('S3')}`), 145 | }, 146 | ]) 147 | .then(async (result) => { 148 | const filename = `${chainName}_${getDateSuffix()}_${ans.contract_name}_upgrade.json`; 149 | if (result.localSave) { 150 | await writeFile(`${dirPath}/${filename}`, JSON.stringify(resultData), 'utf8'); 151 | } 152 | if (result.s3Save) { 153 | await s3Upload({ 154 | bucketName: 'omnuum-prod-website-resources', 155 | keyName: `contracts/upgrades/${filename}`, 156 | fileBuffer: Buffer.from(JSON.stringify(resultData)), 157 | }); 158 | } 159 | }); 160 | } catch (e) { 161 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 162 | } 163 | }); 164 | })(); 165 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omnuum-contracts", 3 | "version": "1.0.0", 4 | "license": "GPL-3.0-or-later", 5 | "repository": "https://github.com/ciety-xyz/subgraph-v1", 6 | "engines": { 7 | "npm": ">=8.0.0", 8 | "node": ">=16.0.0" 9 | }, 10 | "scripts": { 11 | "compile": "rimraf './artifacts' './cache', './data' && npx hardhat compile", 12 | "test": "npm run compile && npx hardhat test --network localhost", 13 | "node": "npx hardhat node", 14 | "run:local": "npx hardhat --network localhost run ", 15 | "deploy:managers:local": "npx hardhat run scripts/deployments/runDeploy.js --network localhost", 16 | "deploy:managers:goerli": "npx hardhat run scripts/deployments/runDeploy.js --network goerli", 17 | "deploy:nft:local": "npm run compile && npx hardhat run scripts/deployments/deployNFTBeaconProxy.js --network localhost", 18 | "deploy:nft:goerli": "npm run compile && npx hardhat run scripts/deployments/deployNFTBeaconProxy.js --network goerli", 19 | "deploy:wallet:goerli": "npx hardhat run scripts/deployments/deployWallet.js --network goerli", 20 | "verify:wallet": "npx hardhat verify $npm_config_address --constructor-args scripts/verifications/wallet.js --network $npm_config_network", 21 | "verify:nftBeaconProxy": "npx hardhat verify $npm_config_address --constructor-args scripts/verifications/nftBeaconProxy.js --network $npm_config_network", 22 | "verify:nftFactory": "npx hardhat verify $npm_config_address --constructor-args scripts/verifications/nftFactory.js --network $npm_config_network", 23 | "verify:vrfManager": "npx hardhat verify $npm_config_address --constructor-args scripts/verifications/vrfManager.js --network $npm_config_network", 24 | "verify:revealManager": "npx hardhat verify $npm_config_address --constructor-args scripts/verifications/revealManager.js --network $npm_config_network", 25 | "itx:registerManager": "npx hardhat run scripts/interactions/caManager/registerManager.js --network $npm_config_network", 26 | "itx:initFilterRegistry": "npx hardhat run scripts/interactions/nft/initFilterRegistry.js --network $npm_config_network", 27 | "itx:removeManager": "npx hardhat run scripts/interactions/caManager/removeManager.js --network $npm_config_network", 28 | "itx:registerNFT": "npx hardhat run scripts/interactions/caManager/registerNFT.js --network $npm_config_network", 29 | "itx:addRole": "npx hardhat run scripts/interactions/caManager/roleAddition.js --network $npm_config_network", 30 | "itx:removeRole": "npx hardhat run scripts/interactions/caManager/roleRemoval.js --network $npm_config_network", 31 | "itx:setTicketSchedule": "npx hardhat run scripts/interactions/ticketManager/setEndDate.js --network $npm_config_network", 32 | "itx:setPublicSchedule": "npx hardhat run scripts/interactions/mintManager/setPublicMintSchedule.js --network $npm_config_network", 33 | "itx:mintTicket": "npx hardhat run scripts/interactions/nft/ticketMint.js --network $npm_config_network", 34 | "itx:mintPublic": "npx hardhat run scripts/interactions/nft/publicMint.js --network $npm_config_network", 35 | "itx:airdrop": "npx hardhat run scripts/interactions/mintManager/airDrop.js --network $npm_config_network", 36 | "itx:vrfRequest": "npx hardhat run scripts/interactions/revealManager/vrfRequest.js --network $npm_config_network", 37 | "itx:changeBaseURI": "npx hardhat run scripts/interactions/nft/changeBaseURI.js --network $npm_config_network", 38 | "itx:setReveal": "npx hardhat run scripts/interactions/nft/setReveal.js --network $npm_config_network", 39 | "itx:transferNFT": "npx hardhat run scripts/interactions/nft/transferNFT.js --network $npm_config_network", 40 | "itx:approvalRequest": "npx hardhat run scripts/interactions/wallet/approvalRequest.js --network $npm_config_network", 41 | "itx:approve": "npx hardhat run scripts/interactions/wallet/approve.js --network $npm_config_network", 42 | "itx:revoke": "npx hardhat run scripts/interactions/wallet/revoke.js --network $npm_config_network", 43 | "itx:sendEtherToWallet": "npx hardhat run scripts/interactions/wallet/sendEther.js --network $npm_config_network", 44 | "itx:sendEtherToNFT": "npx hardhat run scripts/interactions/nft/sendEther.js --network $npm_config_network", 45 | "itx:payment": "npx hardhat run scripts/interactions/wallet/payment.js --network $npm_config_network", 46 | "itx:transferBalance": "npx hardhat run scripts/interactions/nft/transferBalance.js --network $npm_config_network", 47 | "upgrade:proxy": "npx hardhat run scripts/upgrades/upgradeProxy.js --network $npm_config_network", 48 | "upgrade:beacon": "npx hardhat run scripts/upgrades/upgradeBeacon.js --network $npm_config_network", 49 | "coverage": "npx hardhat coverage --network hardhat", 50 | "forceImport": "npx hardhat run scripts/upgrades/forceImport.js --network $npm_config_network" 51 | }, 52 | "dependencies": { 53 | "@marpple/omnuum-digitalSigning": "npm:@marpple/omnuum-digitalsigning@^1.0.4", 54 | "@nomiclabs/hardhat-etherscan": "^3.1.2", 55 | "@nomiclabs/hardhat-solhint": "^2.0.0", 56 | "@openzeppelin/hardhat-upgrades": "^1.21.0", 57 | "@openzeppelin/upgrades-core": "^1.20.4", 58 | "@nomiclabs/hardhat-waffle": "^2.0.3", 59 | "aws-sdk": "^2.1131.0", 60 | "axios": "^0.27.2", 61 | "bitcore-mnemonic": "^8.25.28", 62 | "chalk": "^4.1.2", 63 | "date-fns": "^2.28.0", 64 | "dotenv": "^10.0.0", 65 | "ethereumjs-tx": "^2.1.2", 66 | "ethereumjs-util": "^7.1.4", 67 | "ethers-eip712": "^0.2.0", 68 | "fxjs": "^0.21.3", 69 | "hardhat": "^2.12.2", 70 | "hardhat-abi-exporter": "^2.10.1", 71 | "hardhat-contract-sizer": "^2.6.1", 72 | "hardhat-gas-reporter": "^1.0.9", 73 | "inquirer": "^8.2.1", 74 | "keccak256": "^1.0.6", 75 | "rlp": "^3.0.0", 76 | "solhint": "^3.3.7", 77 | "solidity-coverage": "^0.8.2", 78 | "xlsx": "^0.18.5" 79 | }, 80 | "devDependencies": { 81 | "@chainlink/contracts": "^0.4.0", 82 | "@metamask/eth-sig-util": "^4.0.0", 83 | "@nomiclabs/hardhat-ethers": "^2.0.6", 84 | "@openzeppelin/contracts": "^4.5.0", 85 | "@openzeppelin/contracts-upgradeable": "^4.5.2", 86 | "@openzeppelin/test-environment": "^0.1.9", 87 | "@openzeppelin/test-helpers": "^0.5.15", 88 | "chai": "^4.3.6", 89 | "eslint": "^8.8.0", 90 | "eslint-config-airbnb": "^19.0.4", 91 | "eslint-config-prettier": "^8.3.0", 92 | "eslint-plugin-import": "^2.25.4", 93 | "eslint-plugin-jsx-a11y": "^6.5.1", 94 | "eslint-plugin-node": "^11.1.0", 95 | "eslint-plugin-prettier": "^3.4.1", 96 | "eslint-plugin-promise": "^5.2.0", 97 | "eslint-plugin-react": "^7.28.0", 98 | "eslint-plugin-react-hooks": "^4.3.0", 99 | "ethereum-waffle": "^3.4.4", 100 | "ethers": "^5.6.9", 101 | "mocha": "^9.2.0", 102 | "prettier": "^2.5.1", 103 | "prettier-eslint": "^13.0.0", 104 | "prettier-plugin-solidity": "^1.0.0-beta.13", 105 | "rimraf": "^3.0.2" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/etc/mock.js: -------------------------------------------------------------------------------- 1 | const { ethers, upgrades } = require('hardhat'); 2 | const { mapC, go, zip, map } = require('fxjs'); 3 | const { ContractTopic, chainlink, testValues, contractRole } = require('../../utils/constants.js'); 4 | const { deployNFT } = require('../../scripts/deployments/deployments'); 5 | 6 | Error.stackTraceLimit = Infinity; 7 | 8 | module.exports = { 9 | createNftContractArgs: (context, { prjOwner, signatureSignerPrivateKey, maxSupply = 10000 } = {}) => ({ 10 | prjOwnerSigner: prjOwner || context?.accounts?.[0], 11 | // Since you cannot receive a response by requesting the private key from the provider, just hard-code it. 12 | signatureSignerPrivateKey: signatureSignerPrivateKey || 'f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897', // hardhat account[10] 13 | senderVerifierAddress: context.senderVerifier.address, 14 | maxSupply, 15 | coverUri: testValues.coverUri, 16 | nftFactoryAddress: context.nftFactory.address, 17 | collectionId: testValues.collectionId, 18 | name: testValues.name, 19 | symbol: testValues.symbol, 20 | }), 21 | async deployNFT(context, overrideArgs) { 22 | const args = module.exports.createNftContractArgs(context, overrideArgs); 23 | 24 | const { beaconProxyAddress, deployReceipt } = await deployNFT({ 25 | projectOwnerSigner: args.prjOwnerSigner, 26 | signerPrivateKey: args.signatureSignerPrivateKey, 27 | senderVerifierAddress: args.senderVerifierAddress, 28 | maxSupply: args.maxSupply, 29 | coverUri: args.coverUri, 30 | nftFactoryAddress: args.nftFactoryAddress, 31 | collectionId: args.collectionId, 32 | name: args.name, 33 | symbol: args.symbol, 34 | }); 35 | 36 | const omnuumNFT721 = context.OmnuumNFT721.attach(beaconProxyAddress); 37 | omnuumNFT721.deployReceipt = deployReceipt; 38 | return omnuumNFT721; 39 | }, 40 | async prepareDeploy() { 41 | this.OmnuumMintManager = await ethers.getContractFactory('OmnuumMintManager'); 42 | this.OmnuumNFT721 = await ethers.getContractFactory('OmnuumNFT721'); 43 | this.TicketManager = await ethers.getContractFactory('TicketManager'); 44 | this.SenderVerifier = await ethers.getContractFactory('SenderVerifier'); 45 | this.OmnuumCAManager = await ethers.getContractFactory('OmnuumCAManager'); 46 | this.OmnuumVRFManager = await ethers.getContractFactory('OmnuumVRFManager'); 47 | this.OmnuumExchange = await ethers.getContractFactory('OmnuumExchange'); 48 | this.RevealManager = await ethers.getContractFactory('RevealManager'); 49 | this.OmnuumWallet = await ethers.getContractFactory('OmnuumWallet'); 50 | this.NftFactory = await ethers.getContractFactory('NftFactory'); 51 | this.NFTbeacon = await upgrades.deployBeacon(this.OmnuumNFT721); 52 | 53 | /* Hardhat Accounts 54 | * Account #10: 0xbcd4042de499d14e55001ccbb24a551f3b954096 (10000 ETH) 55 | * Private Key: 0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897 56 | * */ 57 | // eslint-disable-next-line prefer-destructuring 58 | this.signatureSigner = (await ethers.getSigners())[10]; 59 | }, 60 | async prepareMockDeploy() { 61 | this.MockLink = await ethers.getContractFactory('MockLink'); 62 | this.MockVrfCoords = await ethers.getContractFactory('MockVrfCoords'); 63 | this.MockNFT = await ethers.getContractFactory('MockNFT'); 64 | this.MockExchange = await ethers.getContractFactory('MockExchange'); 65 | this.MockVrfRequester = await ethers.getContractFactory('MockVrfRequester'); 66 | }, 67 | async testDeploy(accounts, overrides = []) { 68 | /* Deploy Upgradeable Proxies */ 69 | this.omnuumCAManager = await upgrades.deployProxy(this.OmnuumCAManager); 70 | this.omnuumMintManager = await upgrades.deployProxy(this.OmnuumMintManager, [testValues.feeRate, this.omnuumCAManager.address]); 71 | this.omnuumExchange = await upgrades.deployProxy(this.OmnuumExchange, [this.omnuumCAManager.address]); 72 | 73 | /* Deploy Contracts */ 74 | this.walletOwnerSigner = accounts.slice(-5); 75 | this.walletOwnerAccounts = go( 76 | this.walletOwnerSigner, 77 | zip([2, 2, 1, 1, 1]), 78 | map(([vote, signer]) => ({ addr: signer.address, vote })), 79 | ); 80 | 81 | this.omnuumWallet = await this.OmnuumWallet.deploy( 82 | testValues.consensusRatio, 83 | testValues.minLimitForConsensus, 84 | this.walletOwnerAccounts, 85 | ); 86 | this.revealManager = await this.RevealManager.deploy(this.omnuumCAManager.address); 87 | 88 | this.nftFactory = await this.NftFactory.deploy(this.omnuumCAManager.address, this.NFTbeacon.address, this.signatureSigner.address); 89 | 90 | [this.senderVerifier, this.ticketManager, this.mockLink, this.mockVrfCoords, this.mockVrfRequester, this.mockExchange] = await go( 91 | [this.SenderVerifier, this.TicketManager, this.MockLink, this.MockVrfCoords, this.MockVrfRequester, this.MockExchange], 92 | mapC(async (conFactory) => { 93 | const contract = await conFactory.deploy(); 94 | await contract.deployed(); 95 | return contract; 96 | }), 97 | ); 98 | 99 | /* Deploy VRF Manager */ 100 | this.omnuumVRFManager = await this.OmnuumVRFManager.deploy( 101 | this.mockLink.address, 102 | this.mockVrfCoords.address, 103 | chainlink.rinkeby.hash, 104 | chainlink.rinkeby.fee, 105 | this.omnuumCAManager.address, 106 | ); 107 | 108 | /* Register CA accounts to CA Manager */ 109 | await ( 110 | await this.omnuumCAManager.registerContractMultiple( 111 | [ 112 | this.omnuumVRFManager.address, 113 | this.omnuumMintManager.address, 114 | this.omnuumExchange.address, 115 | this.ticketManager.address, 116 | this.senderVerifier.address, 117 | this.revealManager.address, 118 | this.omnuumWallet.address, 119 | this.mockVrfRequester.address, 120 | this.nftFactory.address, 121 | ], 122 | [ 123 | ContractTopic.VRF, 124 | ContractTopic.MINTMANAGER, 125 | ContractTopic.EXCHANGE, 126 | ContractTopic.TICKET, 127 | ContractTopic.VERIFIER, 128 | ContractTopic.REVEAL, 129 | ContractTopic.WALLET, 130 | ContractTopic.TEST, 131 | ContractTopic.NFTFACTORY, 132 | ], 133 | ) 134 | ).wait(); 135 | 136 | await (await this.omnuumCAManager.addRole([this.omnuumVRFManager.address, this.mockExchange.address], contractRole.exchange)).wait(); 137 | await (await this.omnuumCAManager.addRole([this.revealManager.address, this.mockVrfRequester.address], contractRole.vrf)).wait(); 138 | 139 | /* Deploy NFT beacon proxy */ 140 | this.omnuumNFT721 = await module.exports.deployNFT(this, overrides); 141 | /* Deploy Mock NFT */ 142 | this.mockNFT = await (await this.MockNFT.deploy(this.senderVerifier.address, this.ticketManager.address)).deployed(); 143 | }, 144 | }; 145 | -------------------------------------------------------------------------------- /test/nftFactory.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { ethers, upgrades } = require('hardhat'); 3 | const { uniq, go, range, map, delay, last } = require('fxjs'); 4 | const { addDays } = require('date-fns'); 5 | const Constants = require('../utils/constants.js'); 6 | require('chai').should(); 7 | 8 | const { prepareDeploy, testDeploy, deployNFT, prepareMockDeploy } = require('./etc/mock.js'); 9 | const { signPayload, nullAddress, toSolDate, createTicket } = require('./etc/util.js'); 10 | 11 | const nonce = 1; 12 | const end_date = toSolDate(addDays(new Date(), 2)); 13 | const group_id = 0; 14 | 15 | upgrades.silenceWarnings(); 16 | 17 | describe('NftFactory', () => { 18 | before(async () => { 19 | await prepareDeploy.call(this); 20 | await prepareMockDeploy.call(this); 21 | }); 22 | 23 | beforeEach(async () => { 24 | this.accounts = await ethers.getSigners(); 25 | await testDeploy.call(this, this.accounts); 26 | }); 27 | 28 | describe('[Method] deploy', () => { 29 | it('Should deploy NFT contract', async () => { 30 | const { 31 | accounts: [, prjOwnerAC], 32 | senderVerifier, 33 | signatureSigner, 34 | nftFactory, 35 | } = this; 36 | 37 | const maxSupply = 100; 38 | const collectionId = 12; 39 | 40 | const tx = await nftFactory 41 | .connect(prjOwnerAC) 42 | .deploy( 43 | maxSupply, 44 | Constants.testValues.coverUri, 45 | collectionId, 46 | Constants.testValues.name, 47 | Constants.testValues.symbol, 48 | await signPayload(prjOwnerAC.address, Constants.payloadTopic.deployCol, collectionId, signatureSigner, senderVerifier.address), 49 | ); 50 | 51 | const receipt = await tx.wait(); 52 | 53 | const { args: deployEvent } = nftFactory.interface.parseLog(last(receipt.logs)); 54 | 55 | expect(deployEvent.creator).to.be.equal(prjOwnerAC.address); 56 | expect(deployEvent.collectionId).to.be.equal(collectionId); 57 | }); 58 | 59 | it('Should deploy multiple collection and addresses should be unique', async () => { 60 | const { 61 | accounts: [, prjOwnerAC], 62 | senderVerifier, 63 | ticketManager, 64 | signatureSigner, 65 | nftFactory, 66 | } = this; 67 | 68 | const maxSupply = 100; 69 | const collectionCount = 100; 70 | 71 | await go( 72 | range(collectionCount), 73 | map(async (collectionId) => { 74 | const tx = await nftFactory 75 | .connect(prjOwnerAC) 76 | .deploy( 77 | maxSupply, 78 | Constants.testValues.coverUri, 79 | collectionId, 80 | Constants.testValues.name, 81 | Constants.testValues.symbol, 82 | await signPayload( 83 | prjOwnerAC.address, 84 | Constants.payloadTopic.deployCol, 85 | collectionId, 86 | signatureSigner, 87 | senderVerifier.address, 88 | ), 89 | ); 90 | 91 | const receipt = await tx.wait(); 92 | 93 | const { args: deployEvent } = nftFactory.interface.parseLog(last(receipt.logs)); 94 | 95 | expect(deployEvent.creator).to.be.equal(prjOwnerAC.address); 96 | expect(deployEvent.collectionId).to.be.equal(collectionId); 97 | 98 | return deployEvent.nftContract; 99 | }), 100 | uniq, 101 | (addresses) => expect(addresses.length).to.be.equal(collectionCount), 102 | ); 103 | }).timeout(1000 * 60); 104 | 105 | it('[Revert] false signature', async () => { 106 | const { 107 | accounts: [falseSigner, prjOwnerAC], 108 | senderVerifier, 109 | ticketManager, 110 | signatureSigner, 111 | nftFactory, 112 | } = this; 113 | 114 | const maxSupply = 100; 115 | const collectionId = 12; 116 | 117 | // 1. false signer 118 | await expect( 119 | nftFactory 120 | .connect(prjOwnerAC) 121 | .deploy( 122 | maxSupply, 123 | Constants.testValues.coverUri, 124 | collectionId, 125 | Constants.testValues.name, 126 | Constants.testValues.symbol, 127 | await signPayload(prjOwnerAC.address, Constants.payloadTopic.deployCol, collectionId, falseSigner, senderVerifier.address), 128 | ), 129 | ).to.be.revertedWith(Constants.reasons.code.VR1); 130 | 131 | // 2. false nonce 132 | await expect( 133 | nftFactory.connect(prjOwnerAC).deploy( 134 | maxSupply, 135 | Constants.testValues.coverUri, 136 | collectionId, 137 | Constants.testValues.name, 138 | Constants.testValues.symbol, 139 | // payload collection id is not equal to above argument 140 | await signPayload( 141 | prjOwnerAC.address, 142 | Constants.payloadTopic.deployCol, 143 | collectionId + 1, 144 | signatureSigner, 145 | senderVerifier.address, 146 | ), 147 | ), 148 | ).to.be.revertedWith(Constants.reasons.code.VR2); 149 | 150 | // 3. false topic 151 | await expect( 152 | nftFactory 153 | .connect(prjOwnerAC) 154 | .deploy( 155 | maxSupply, 156 | Constants.testValues.coverUri, 157 | collectionId, 158 | Constants.testValues.name, 159 | Constants.testValues.symbol, 160 | await signPayload(prjOwnerAC.address, Constants.payloadTopic.mint, collectionId, signatureSigner, senderVerifier.address), 161 | ), 162 | ).to.be.revertedWith(Constants.reasons.code.VR3); 163 | }); 164 | }); 165 | describe('[Method] changeOmnuumSigner', () => { 166 | it('Should change signer', async () => { 167 | const { 168 | accounts: [, changedSigner], 169 | nftFactory, 170 | } = this; 171 | 172 | const prevSigner = await nftFactory.omnuumSigner(); 173 | 174 | const tx = await nftFactory.changeOmnuumSigner(changedSigner.address); 175 | 176 | await tx.wait(); 177 | 178 | const currentSigner = await nftFactory.omnuumSigner(); 179 | 180 | expect(prevSigner).not.to.be.equal(currentSigner); 181 | expect(currentSigner).to.be.equal(changedSigner.address); 182 | }); 183 | 184 | it('[Revert] Only owner', async () => { 185 | const { 186 | accounts: [, notOwnerAC], 187 | nftFactory, 188 | } = this; 189 | 190 | await expect(nftFactory.connect(notOwnerAC).changeOmnuumSigner(notOwnerAC.address)).to.be.revertedWith( 191 | Constants.reasons.common.onlyOwner, 192 | ); 193 | }); 194 | 195 | it('[Revert] Cannot set null address', async () => { 196 | const { nftFactory } = this; 197 | 198 | await expect(nftFactory.changeOmnuumSigner(nullAddress)).to.be.revertedWith(Constants.reasons.code.AE1); 199 | }); 200 | }); 201 | }); 202 | -------------------------------------------------------------------------------- /scripts/deployments/index.js: -------------------------------------------------------------------------------- 1 | const { ethers, upgrades, config, run } = require('hardhat'); 2 | const { writeFile, mkdir, rm, access } = require('fs/promises'); 3 | 4 | upgrades.silenceWarnings(); 5 | 6 | const chalk = require('chalk'); 7 | const { deployManagers } = require('./deployments'); 8 | const { 9 | prev_history_file_path, 10 | tryCatch, 11 | getDateSuffix, 12 | structurizeProxyData, 13 | structurizeContractData, 14 | getChainName, 15 | getRPCProvider, 16 | createWalletOwnerAccounts, 17 | } = require('./deployHelper'); 18 | const DEP_CONSTANTS = require('./deployConstants'); 19 | const { compile } = require('../../utils/hardhat.js'); 20 | const { s3Upload } = require('../../utils/s3.js'); 21 | 22 | async function main({ deployerPK, signerAddress, gasPrices, localSave = true, s3Save = false }) { 23 | try { 24 | console.log(` 25 | ******* **** **** **** ** ** ** ** ** **** **** 26 | **/////** /**/** **/**/**/** /**/** /**/** /**/**/** **/** 27 | ** //**/**//** ** /**/**//** /**/** /**/** /**/**//** ** /** 28 | /** /**/** //*** /**/** //** /**/** /**/** /**/** //*** /** 29 | /** /**/** //* /**/** //**/**/** /**/** /**/** //* /** 30 | //** ** /** / /**/** //****/** /**/** /**/** / /** 31 | //******* /** /**/** //***//******* //******* /** /** 32 | /////// // // // /// /////// /////// // // 33 | `); 34 | 35 | await compile({ force: true, quiet: true }); 36 | 37 | const chainName = await getChainName(); 38 | 39 | // prepare deploy result directory structure 40 | await mkdir('./scripts/deployments/deployResults/managers', { recursive: true }); 41 | await mkdir('./scripts/deployments/deployResults/subgraphManifest', { recursive: true }); 42 | 43 | let provider; 44 | if (gasPrices) { 45 | // Wrap the provider so we can override fee data. 46 | provider = new ethers.providers.FallbackProvider([ethers.provider], 1); 47 | const FEE_DATA = { 48 | maxFeePerGas: ethers.utils.parseUnits(gasPrices.maxFeePerGas, 'gwei'), 49 | maxPriorityFeePerGas: ethers.utils.parseUnits(gasPrices.maxPriorityFeePerGas, 'gwei'), 50 | }; 51 | provider.getFeeData = async () => FEE_DATA; 52 | } else { 53 | provider = await getRPCProvider(ethers.provider); 54 | } 55 | 56 | const OmnuumDeploySigner = 57 | chainName === 'localhost' 58 | ? (await ethers.getSigners())[0] 59 | : await new ethers.Wallet(deployerPK || process.env.OMNUUM_DEPLOYER_PRIVATE_KEY, provider); 60 | 61 | const walletOwnerAccounts = createWalletOwnerAccounts( 62 | chainName === 'localhost' ? (await ethers.getSigners()).slice(1, 6).map((x) => x.address) : DEP_CONSTANTS.wallet.ownerAddresses, 63 | DEP_CONSTANTS.wallet.ownerLevels, 64 | ); 65 | 66 | const deployStartTime = new Date(); 67 | 68 | console.log(`${chalk.blueBright(`START DEPLOYMENT to ${chainName} at ${deployStartTime}`)}`); 69 | 70 | const deploy_metadata = { 71 | deployer: OmnuumDeploySigner.address, 72 | solidity: { 73 | version: config.solidity.compilers[0].version, 74 | }, 75 | }; 76 | 77 | // write tmp history file for restore already deployed history 78 | await tryCatch( 79 | () => access(prev_history_file_path), 80 | () => writeFile(prev_history_file_path, JSON.stringify(deploy_metadata)), 81 | ); 82 | 83 | const { nft, nftFactory, vrfManager, mintManager, caManager, exchange, ticketManager, senderVerifier, revealManager, wallet } = 84 | await deployManagers({ deploySigner: OmnuumDeploySigner, walletOwnerAccounts, signatureSignerAddress: signerAddress }); 85 | 86 | const resultData = { 87 | network: chainName, 88 | deployStartAt: deployStartTime.toLocaleTimeString(), 89 | deployer: OmnuumDeploySigner.address, 90 | caManager: structurizeProxyData(caManager), 91 | mintManager: structurizeProxyData(mintManager), 92 | exchange: structurizeProxyData(exchange), 93 | wallet: structurizeContractData(wallet), 94 | ticketManager: structurizeContractData(ticketManager), 95 | vrfManager: structurizeContractData(vrfManager), 96 | revealManager: structurizeContractData(revealManager), 97 | senderVerifier: structurizeContractData(senderVerifier), 98 | nft721: { 99 | impl: nft.implAddress, 100 | beacon: nft.beacon.address, 101 | address: nft.beacon.address, 102 | }, 103 | nftFactory: structurizeContractData(nftFactory), 104 | }; 105 | 106 | const subgraphManifestData = { 107 | network: chainName, 108 | deployStartAt: deployStartTime.toLocaleTimeString(), 109 | deployer: OmnuumDeploySigner.address, 110 | caManager: { 111 | address: caManager.proxyContract.address, 112 | startBlock: `${caManager.blockNumber}`, 113 | }, 114 | mintManager: { 115 | address: mintManager.proxyContract.address, 116 | startBlock: `${mintManager.blockNumber}`, 117 | }, 118 | exchange: { 119 | address: exchange.proxyContract.address, 120 | startBlock: `${exchange.blockNumber}`, 121 | }, 122 | wallet: { 123 | address: wallet.contract.address, 124 | startBlock: `${wallet.blockNumber}`, 125 | }, 126 | ticketManager: { 127 | address: ticketManager.contract.address, 128 | startBlock: `${ticketManager.blockNumber}`, 129 | }, 130 | vrfManager: { 131 | address: vrfManager.contract.address, 132 | startBlock: `${vrfManager.blockNumber}`, 133 | }, 134 | revealManager: { 135 | address: revealManager.contract.address, 136 | startBlock: `${revealManager.blockNumber}`, 137 | }, 138 | senderVerifier: { 139 | address: senderVerifier.contract.address, 140 | startBlock: `${senderVerifier.blockNumber}`, 141 | }, 142 | nft721: { 143 | impl: nft.implAddress, 144 | beacon: nft.beacon.address, 145 | startBlock: `${nft.blockNumber}`, 146 | }, 147 | nftFactory: { 148 | address: nftFactory.contract.address, 149 | startBlock: `${nftFactory.blockNumber}`, 150 | }, 151 | }; 152 | 153 | const filename = `${chainName}_${getDateSuffix()}.json`; 154 | 155 | await rm(prev_history_file_path); // delete tmp deploy history file 156 | 157 | if (localSave) { 158 | await writeFile(`./scripts/deployments/deployResults/managers/${filename}`, JSON.stringify(resultData), 'utf8'); 159 | await writeFile(`./scripts/deployments/deployResults/subgraphManifest/${filename}`, JSON.stringify(subgraphManifestData), 'utf-8'); 160 | } 161 | if (s3Save) { 162 | await s3Upload({ 163 | bucketName: 'omnuum-prod-website-resources', 164 | keyName: `contracts/deployments/${chainName}/${filename}`, 165 | fileBuffer: Buffer.from(JSON.stringify(resultData)), 166 | }); 167 | await s3Upload({ 168 | bucketName: 'omnuum-prod-website-resources', 169 | keyName: `contracts/deployments/subgraphManifests/${chainName}/${filename}`, 170 | fileBuffer: Buffer.from(JSON.stringify(subgraphManifestData)), 171 | }); 172 | } 173 | 174 | return resultData; 175 | } catch (e) { 176 | console.error('\n 🚨 ==== ERROR ==== 🚨 \n', e); 177 | return null; 178 | } 179 | } 180 | 181 | // main(); 182 | 183 | module.exports = main; 184 | -------------------------------------------------------------------------------- /resources/OmnuumNFT1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.10; 3 | 4 | import '@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol'; 5 | import '@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol'; 6 | import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; 7 | import '../utils/OwnableUpgradeable.sol'; 8 | import './SenderVerifier.sol'; 9 | import './OmnuumMintManager.sol'; 10 | import './OmnuumCAManager.sol'; 11 | import './TicketManager.sol'; 12 | import './OmnuumWallet.sol'; 13 | 14 | /// @title OmnuumNFT1155 - nft contract written based on ERC1155 15 | /// @author Omnuum Dev Team - 16 | /// @notice Omnuum specific nft contract which pays mint fee to omnuum but can utilize omnuum protocol 17 | contract OmnuumNFT1155 is ERC1155Upgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable { 18 | using AddressUpgradeable for address; 19 | using AddressUpgradeable for address payable; 20 | using CountersUpgradeable for CountersUpgradeable.Counter; 21 | CountersUpgradeable.Counter public _tokenIdCounter; 22 | 23 | OmnuumCAManager private caManager; 24 | OmnuumMintManager private mintManager; 25 | 26 | /// @notice max amount can be minted 27 | uint32 public maxSupply; 28 | 29 | /// @notice whether revealed or not 30 | bool public isRevealed; 31 | string private coverUri; 32 | address private omA; 33 | 34 | event Uri(address indexed nftContract, string uri); 35 | event FeePaid(address indexed payer, uint256 amount); 36 | event TransferBalance(uint256 value, address indexed receiver); 37 | event EtherReceived(address indexed sender); 38 | 39 | /// @notice constructor function for upgradeable 40 | /// @param _caManagerAddress ca manager address 41 | /// @param _omA omnuum company address 42 | /// @param _maxSupply max amount can be minted 43 | /// @param _coverUri metadata uri for before reveal 44 | /// @param _prjOwner project owner address to transfer ownership 45 | function initialize( 46 | address _caManagerAddress, 47 | address _omA, // omnuum signer 48 | uint32 _maxSupply, 49 | string calldata _coverUri, 50 | address _prjOwner 51 | ) public initializer { 52 | /// @custom:error (AE1) - Zero address not acceptable 53 | require(_caManagerAddress != address(0), 'AE1'); 54 | require(_omA != address(0), 'AE1'); 55 | require(_prjOwner != address(0), 'AE1'); 56 | 57 | __ERC1155_init(''); 58 | __ReentrancyGuard_init(); 59 | __Ownable_init(); 60 | 61 | maxSupply = _maxSupply; 62 | omA = _omA; 63 | 64 | caManager = OmnuumCAManager(_caManagerAddress); 65 | mintManager = OmnuumMintManager(caManager.getContract('MINTMANAGER')); 66 | coverUri = _coverUri; 67 | } 68 | 69 | /// @notice public minting function 70 | /// @param _quantity minting quantity 71 | /// @param _groupId public minting schedule id 72 | /// @param _payload payload for authenticate that mint call happen through omnuum server to guarantee exact schedule time 73 | function publicMint( 74 | uint32 _quantity, 75 | uint256 _groupId, 76 | SenderVerifier.Payload calldata _payload 77 | ) external payable nonReentrant { 78 | /// @custom:error (MT9) - Minter cannot be CA 79 | require(!msg.sender.isContract(), 'MT9'); 80 | 81 | SenderVerifier(caManager.getContract('VERIFIER')).verify(omA, msg.sender, 'MINT', _groupId, _payload); 82 | mintManager.preparePublicMint(_groupId, _quantity, msg.value, msg.sender); 83 | 84 | mintLoop(msg.sender, _quantity); 85 | sendFee(_quantity); 86 | } 87 | 88 | /// @notice ticket minting function 89 | /// @param _quantity minting quantity 90 | /// @param _ticket ticket struct which proves authority to mint 91 | /// @param _payload payload for authenticate that mint call happen through omnuum server to guarantee exact schedule time 92 | function ticketMint( 93 | uint32 _quantity, 94 | TicketManager.Ticket calldata _ticket, 95 | SenderVerifier.Payload calldata _payload 96 | ) external payable nonReentrant { 97 | /// @custom:error (MT9) - Minter cannot be CA 98 | require(!msg.sender.isContract(), 'MT9'); 99 | 100 | /// @custom:error (MT5) - Not enough money 101 | require(_ticket.price * _quantity <= msg.value, 'MT5'); 102 | 103 | SenderVerifier(caManager.getContract('VERIFIER')).verify(omA, msg.sender, 'TICKET', _ticket.groupId, _payload); 104 | TicketManager(caManager.getContract('TICKET')).useTicket(omA, msg.sender, _quantity, _ticket); 105 | 106 | mintLoop(msg.sender, _quantity); 107 | sendFee(_quantity); 108 | } 109 | 110 | /// @notice direct mint, neither public nor ticket 111 | /// @param _to mint destination address 112 | /// @param _quantity minting quantity 113 | function mintDirect(address _to, uint256 _quantity) external { 114 | /// @custom:error (OO3) - Only Omnuum or owner can change 115 | require(msg.sender == address(mintManager), 'OO3'); 116 | mintLoop(_to, _quantity); 117 | } 118 | 119 | /// @notice minting utility function, manage token id 120 | /// @param _to mint destination address 121 | /// @param _quantity minting quantity 122 | function mintLoop(address _to, uint256 _quantity) internal { 123 | /// @custom:error (MT3) - Remaining token count is not enough 124 | require(_tokenIdCounter.current() + _quantity <= maxSupply, 'MT3'); 125 | for (uint256 i = 0; i < _quantity; i++) { 126 | _tokenIdCounter.increment(); 127 | _mint(_to, _tokenIdCounter.current(), 1, ''); 128 | } 129 | } 130 | 131 | /// @notice set uri for reveal 132 | /// @param __uri uri of revealed metadata 133 | function setUri(string memory __uri) external onlyOwner { 134 | _setURI(__uri); 135 | isRevealed = true; 136 | emit Uri(address(this), __uri); 137 | } 138 | 139 | /// @notice get current metadata uri 140 | function uri(uint256) public view override returns (string memory) { 141 | return !isRevealed ? coverUri : super.uri(1); 142 | } 143 | 144 | /// @notice transfer balance of the contract to someone (maybe the project team member), including project owner him or herself 145 | /// @param _value The amount of value to transfer 146 | /// @param _to Receiver who receive the value 147 | function transferBalance(uint256 _value, address _to) external onlyOwner nonReentrant { 148 | /// @custom:error (NE4) - Insufficient balance 149 | require(_value <= address(this).balance, 'NE4'); 150 | (bool withdrawn, ) = payable(_to).call{ value: _value }(''); 151 | 152 | /// @custom:error (SE5) - Address: unable to send value, recipient may have reverted 153 | require(withdrawn, 'SE5'); 154 | 155 | emit TransferBalance(_value, _to); 156 | } 157 | 158 | /// @notice a function to donate to support the project owner. Hooray~! 159 | receive() external payable { 160 | emit EtherReceived(msg.sender); 161 | } 162 | 163 | /// @notice send fee to omnuum wallet 164 | /// @param _quantity Mint quantity 165 | function sendFee(uint256 _quantity) internal { 166 | uint8 rateDecimal = mintManager.rateDecimal(); 167 | uint256 minFee = mintManager.minFee(); 168 | uint256 feeRate = mintManager.getFeeRate(address(this)); 169 | uint256 calculatedFee = (msg.value * feeRate) / 10**rateDecimal; 170 | uint256 minimumFee = _quantity * minFee; 171 | 172 | uint256 feePayment = calculatedFee > minimumFee ? calculatedFee : minimumFee; 173 | 174 | OmnuumWallet(payable(caManager.getContract('WALLET'))).makePayment{ value: feePayment }('MINT_FEE', ''); 175 | 176 | emit FeePaid(msg.sender, feePayment); 177 | } 178 | } 179 | --------------------------------------------------------------------------------