├── contracts ├── .env.example ├── migrations │ ├── 1_initial_migration.js │ └── 2_starter_migration.js ├── contracts │ ├── Migrations.sol │ ├── SampleToken.sol │ └── MerkleStarter.sol ├── truffle-config.js └── package.json ├── server ├── backup.sh ├── .gitignore ├── restore.sh ├── .env.example ├── constants │ ├── tranches.js │ └── index.js ├── operator │ ├── 0.csv │ ├── import.js │ ├── seed.js │ └── seedThenImport.js ├── README.md ├── models │ └── Whitelist.js ├── routes │ ├── list.js │ ├── status.js │ └── proof.js ├── package.json ├── app.js ├── bin │ └── www ├── utils │ └── merkle.module.js └── contracts │ └── MerkleStarter.json ├── merkle-tree.png ├── client ├── .firebaserc ├── jsconfig.json ├── src │ ├── assets │ │ ├── images │ │ │ ├── app-store.png │ │ │ ├── header-bg-3.jpg │ │ │ └── menu.svg │ │ ├── fonts │ │ │ ├── 7Aulp_0qiz-aVz7u3PJLcUMYOFkQl0k30e0.ttf │ │ │ ├── 7Aulp_0qiz-aVz7u3PJLcUMYOFkpl0k30e0.ttf │ │ │ ├── 7Aulp_0qiz-aVz7u3PJLcUMYOFlOl0k30e0.ttf │ │ │ └── 7Aulp_0qiz-aVz7u3PJLcUMYOFnOkEk30e0.ttf │ │ └── scss │ │ │ └── style-landing-page.scss │ ├── components │ │ ├── Claim │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── Status │ │ │ ├── style.css │ │ │ └── index.js │ │ └── ConnectWallet │ │ │ ├── style.css │ │ │ └── index.js │ ├── store │ │ ├── index.js │ │ ├── reducers.js │ │ └── actions.js │ ├── setupTests.js │ ├── App.test.js │ ├── App.css │ ├── index.css │ ├── index.js │ ├── utils │ │ └── connectMetaMask.js │ ├── serviceWorker.js │ ├── App.js │ └── contracts │ │ ├── MerkleStarter.json │ │ └── IERC20.json ├── .env.example ├── firebase.json ├── .gitignore ├── package.json ├── public │ └── index.html └── .firebase │ └── hosting.YnVpbGQ.cache ├── merklestarter.png ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md └── Instructions.md /contracts/.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | TOKEN_ADDRESS= 3 | -------------------------------------------------------------------------------- /server/backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DB_NAME=$1 3 | mongodump -d $DB_NAME -o ./data 4 | -------------------------------------------------------------------------------- /merkle-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/merkle-tree.png -------------------------------------------------------------------------------- /client/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "merkle-starter" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /merklestarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/merklestarter.png -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /data 3 | package-lock.json 4 | yarn.lock 5 | 6 | .env 7 | -------------------------------------------------------------------------------- /server/restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DB_NAME=$1 3 | mongorestore --drop -d $DB_NAME ./data/$DB_NAME 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | package-lock.json 5 | yarn.lock 6 | 7 | .env 8 | 9 | build -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | MONGODB_URI= 2 | NETWORK_ID= 3 | PRIVATE_KEY= 4 | INFURA_ID= 5 | TOKEN_ADDRESS= 6 | STARTER_ADDRESS= -------------------------------------------------------------------------------- /client/src/assets/images/app-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/client/src/assets/images/app-store.png -------------------------------------------------------------------------------- /client/src/assets/images/header-bg-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/client/src/assets/images/header-bg-3.jpg -------------------------------------------------------------------------------- /server/constants/tranches.js: -------------------------------------------------------------------------------- 1 | const BN = require('bn.js'); 2 | // tranche and default amount claim for user 3 | 4 | module.exports.CURRENT_TRANCHE = '0'; 5 | -------------------------------------------------------------------------------- /client/src/components/Claim/style.css: -------------------------------------------------------------------------------- 1 | ol.add-token { 2 | padding-left: 0; 3 | margin-top: 20px; 4 | } 5 | 6 | ol.add-token li { 7 | font-size: 14px; 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesContractsDirectory": "", 3 | "solidity.packageDefaultDependenciesDirectory": "contracts/node_modules", 4 | } -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK= 2 | REACT_APP_SERVER_URL= 3 | REACT_APP_NETWORK_ID= 4 | REACT_APP_TOKEN_ADDRESS= 5 | REACT_APP_MERKLE_STARTER_ADDRESS= 6 | REACT_APP_INFURA_ID= -------------------------------------------------------------------------------- /client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFkQl0k30e0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFkQl0k30e0.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFkpl0k30e0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFkpl0k30e0.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFlOl0k30e0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFlOl0k30e0.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFnOkEk30e0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxdaniel197/merklestarter/HEAD/client/src/assets/fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFnOkEk30e0.ttf -------------------------------------------------------------------------------- /contracts/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require('./Migrations.sol'); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /server/constants/index.js: -------------------------------------------------------------------------------- 1 | const deadline = new Date(2222, 12, 31, 00, 0, 0, 0); 2 | const amount = 10000000000000000000; 3 | 4 | exports.AmountToken = amount; 5 | exports.DeadLine = deadline; 6 | -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import rootReducer from './reducers'; 2 | import thunk from 'redux-thunk'; 3 | import { createStore, applyMiddleware } from 'redux'; 4 | 5 | const store = createStore(rootReducer, applyMiddleware(thunk)); 6 | 7 | export default store; 8 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /server/operator/0.csv: -------------------------------------------------------------------------------- 1 | 0xC3a005E15Cb35689380d9C1318e981BcA9339942,20 2 | 0x46F39dd0F287FC3E46C6dd2F75eDC9184031F201,41 3 | 0x293a4037296D188a24F167f36924afF05FDF9eee,75 4 | 0x061e5E421b1843695b204D56334a0CEc5Ce10886,10 5 | 0xF0dC2eA8Cd4469197eF6226addEEF2276aA037Fe,23 6 | 0xC8c1467Ca1c536B00BD8B6ED0C18440fDAF29a41,45 -------------------------------------------------------------------------------- /client/src/components/Status/style.css: -------------------------------------------------------------------------------- 1 | span.title-status { 2 | display: inline-block; 3 | width: 350px; 4 | font-size: 18px; 5 | } 6 | 7 | div.progress-status { 8 | margin-bottom: 15px; 9 | } 10 | 11 | p.balance { 12 | text-align: left; 13 | font-size: 30px; 14 | font-weight: bold; 15 | } 16 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | ## Init Backup and Restore 2 | 3 | ```bash 4 | mkdir data 5 | sudo chmod +x ./backup.sh ./restore.sh 6 | ``` 7 | 8 | ## Backup Database 9 | 10 | ```bash 11 | ./backup.sh [database name] 12 | ``` 13 | 14 | ## Restore Database 15 | 16 | ```bash 17 | ./restore.sh [database name] 18 | ``` 19 | -------------------------------------------------------------------------------- /client/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/migrations/2_starter_migration.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const MerkleStarter = artifacts.require('./MerkleStarter.sol'); 3 | 4 | module.exports = async function (deployer) { 5 | const tokenAddress = process.env.TOKEN_ADDRESS; 6 | await deployer.deploy(MerkleStarter, tokenAddress, '2000000000000000'); 7 | }; 8 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | 3 | .App { 4 | text-align: center; 5 | } 6 | 7 | .ant-spin-spinning { 8 | width: 100vw; 9 | height: 100vh; 10 | z-index: 99999; 11 | position: absolute; 12 | top: 35% !important; 13 | } 14 | 15 | .ant-spin-dot-item { 16 | color: #fff !important; 17 | } 18 | 19 | .ant-spin-text { 20 | color: #fff !important; 21 | font-size: 20px; 22 | } 23 | -------------------------------------------------------------------------------- /client/src/components/ConnectWallet/style.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: #ffffff; 3 | text-align: right; 4 | font-size: 1.1rem; 5 | } 6 | 7 | button { 8 | background-color: #fff !important; 9 | border-color: #fff !important; 10 | } 11 | 12 | button span { 13 | color: #023073; 14 | } 15 | 16 | button:hover { 17 | background-color: #023073 !important; 18 | } 19 | 20 | button span:hover { 21 | color: #fff; 22 | } 23 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import 'bootstrap/dist/css/bootstrap.min.css'; 5 | import App from './App'; 6 | import store from './store'; 7 | import { Provider } from 'react-redux'; 8 | import 'antd/dist/antd.css'; 9 | 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | .env -------------------------------------------------------------------------------- /contracts/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.25 <0.7.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint256 public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint256 completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/contracts/SampleToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | 6 | contract SampleToken is ERC20("SampleToken", "SPTK"), Ownable { 7 | function mint(address _to, uint256 _amount) public onlyOwner { 8 | _mint(_to, _amount); 9 | } 10 | 11 | function burn(address _account, uint256 _amount) public onlyOwner { 12 | _burn(_account, _amount); 13 | } 14 | } -------------------------------------------------------------------------------- /server/models/Whitelist.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { Schema } = mongoose; 3 | 4 | const WhitelistSchema = new Schema( 5 | { 6 | tranche: { 7 | type: String, 8 | required: true, 9 | }, 10 | address: { 11 | type: String, 12 | required: true, 13 | }, 14 | amount: { 15 | type: String, 16 | required: true, 17 | }, 18 | }, 19 | { collection: 'addresses' } 20 | ); 21 | 22 | const Whitelist = mongoose.model('Whitelist', WhitelistSchema); 23 | 24 | module.exports = Whitelist; 25 | -------------------------------------------------------------------------------- /contracts/truffle-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 3 | 4 | module.exports = { 5 | networks: { 6 | development: { 7 | provider: () => 8 | new HDWalletProvider( 9 | process.env.PRIVATE_KEY, 10 | 'https://data-seed-prebsc-2-s1.binance.org:8545' 11 | ), 12 | network_id: 97, 13 | confirmations: 10, 14 | timeoutBlocks: 200, 15 | skipDryRun: true, 16 | }, 17 | }, 18 | compilers: { 19 | solc: { 20 | version: '0.6.6', 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "merklestarter-contracts", 3 | "version": "1.0.0", 4 | "description": "contracts for merklestarter system", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "daniel", 9 | "license": "MIT", 10 | "dependencies": { 11 | "@openzeppelin/contracts": "^3.2.0", 12 | "@openzeppelin/test-helpers": "^0.5.6", 13 | "@truffle/hdwallet-provider": "^1.1.0", 14 | "bn.js": "^5.1.3", 15 | "csv-parser": "^3.0.0", 16 | "dotenv": "^8.2.0", 17 | "ethers": "^5.0.25", 18 | "solc": "0.6.6", 19 | "truffle": "^5.1.48" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/routes/list.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const { validationResult, check } = require('express-validator'); 3 | const Whitelist = require('../models/Whitelist'); 4 | require('dotenv').config(); 5 | 6 | router.get('/:trancheId', async (req, res) => { 7 | try { 8 | let result = await Whitelist.find({ tranche: req.params.trancheId }); 9 | 10 | let list = []; 11 | 12 | for (let i = 0; i < result.length; i++) { 13 | list.push('' + result[i].address.toLowerCase()); 14 | } 15 | 16 | return res.status(200).json({ whitelist: list }); 17 | } catch (error) { 18 | return res.status(500).json({ msg: 'Internal Server Error' }); 19 | } 20 | }); 21 | 22 | module.exports = router; 23 | -------------------------------------------------------------------------------- /client/src/components/ConnectWallet/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import './style.css'; 4 | import { useSelector } from 'react-redux'; 5 | import { connectMetaMask } from 'utils/connectMetaMask'; 6 | 7 | export default function ConnectWallet() { 8 | const web3 = useSelector(state => state.web3); 9 | const address = useSelector(state => state.address); 10 | 11 | const connect = () => { 12 | connectMetaMask(); 13 | }; 14 | 15 | const splitAddress = address => { 16 | let head = address.slice(0, 6); 17 | let tail = address.slice(-4); 18 | address = `${head}...${tail}`; 19 | return address; 20 | }; 21 | 22 | return ( 23 |
24 | {web3 && address ? ( 25 |

{splitAddress(address)}

26 | ) : ( 27 | 28 | )} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "merklestarter-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon ./bin/www" 9 | }, 10 | "author": "daniel", 11 | "license": "MIT", 12 | "dependencies": { 13 | "bn.js": "^5.1.3", 14 | "body-parser": "^1.19.0", 15 | "cookie-parser": "^1.4.5", 16 | "cors": "^2.8.5", 17 | "csv-parser": "^3.0.0", 18 | "dotenv": "^8.2.0", 19 | "ethers": "^5.0.25", 20 | "express": "^4.17.1", 21 | "express-rate-limit": "^5.1.3", 22 | "express-session": "^1.17.1", 23 | "express-validator": "^6.6.1", 24 | "fast-csv": "^4.3.6", 25 | "mongodb": "^3.6.3", 26 | "mongoose": "^5.10.9", 27 | "morgan": "^1.10.0", 28 | "nodemon": "^2.0.5", 29 | "web3": "^1.3.0", 30 | "yargs": "^16.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/routes/status.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const { validationResult, check } = require('express-validator'); 3 | const Whitelist = require('../models/Whitelist'); 4 | const { ethers } = require('ethers'); 5 | require('dotenv').config(); 6 | 7 | router.get('/:tranche/:address', async (req, res) => { 8 | try { 9 | let { tranche, address } = req.params; 10 | if (!ethers.utils.isAddress(address)) { 11 | return res.status(400).json({ msg: 'Address is invalid' }); 12 | } 13 | 14 | const result = await Whitelist.findOne({ tranche, address }); 15 | 16 | if (!result) { 17 | return res.status(404).json({ msg: 'Address has not registered' }); 18 | } 19 | 20 | const { amount } = result; 21 | 22 | return res.status(200).json({ 23 | tranche, 24 | address, 25 | amount, 26 | }); 27 | } catch (error) { 28 | return res.status(500).json({ msg: 'Internal Server Error' }); 29 | } 30 | }); 31 | 32 | module.exports = router; 33 | -------------------------------------------------------------------------------- /client/src/components/Status/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import './style.css'; 3 | import { useSelector } from 'react-redux'; 4 | import BN from 'bn.js'; 5 | 6 | export default function Status() { 7 | const address = useSelector((state) => state.address); 8 | const token = useSelector((state) => state.token); 9 | const claimableAmount = useSelector((state) => state.claimableAmount); 10 | const [balance, setBalance] = useState(0); 11 | 12 | useEffect(() => { 13 | async function getBalance() { 14 | if (token && address) { 15 | const unit = new BN('1000000000000000000'); 16 | let result = await token.methods.balanceOf(address).call(); 17 | result = new BN(result); 18 | 19 | result = result.div(unit); 20 | result = parseInt(result); 21 | setBalance(result); 22 | } 23 | } 24 | 25 | getBalance(); 26 | }); 27 | 28 | return ( 29 |
30 |

Balance: {balance}

31 |

Claimable Amount: {claimableAmount}

32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Max Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "merklestarter-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@metamask/detect-provider": "^1.1.0", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "@walletconnect/web3-provider": "^1.3.3", 11 | "antd": "^4.7.0", 12 | "bn.js": "^5.1.3", 13 | "body-scroll-lock": "^3.1.3", 14 | "bootstrap": "^4.5.3", 15 | "dotenv": "^8.2.0", 16 | "node-sass": "^4.14.1", 17 | "prettier": "^2.0.5", 18 | "react": "^16.14.0", 19 | "react-dom": "^16.14.0", 20 | "react-redux": "^7.2.0", 21 | "react-scripts": "3.4.3", 22 | "reactstrap": "^8.6.0", 23 | "redux": "^4.0.5", 24 | "redux-thunk": "^2.3.0", 25 | "styled-components": "^4.0.0", 26 | "web3": "^1.3.0", 27 | "web3modal": "^1.9.3" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "test": "react-scripts test", 33 | "eject": "react-scripts eject" 34 | }, 35 | "eslintConfig": { 36 | "extends": "react-app" 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const express = require('express'); 4 | const logger = require('morgan'); 5 | const bodyParser = require('body-parser'); 6 | const cookieParser = require('cookie-parser'); 7 | const cors = require('cors'); 8 | const mongoose = require('mongoose'); 9 | 10 | require('dotenv').config(); 11 | 12 | const list = require('./routes/list'); 13 | const status = require('./routes/status'); 14 | const proof = require('./routes/proof'); 15 | 16 | // Connect database 17 | mongoose.connect( 18 | process.env.MONGODB_URI + '/merklestarter', 19 | { useUnifiedTopology: true, useNewUrlParser: true }, 20 | (error) => { 21 | if (error) console.log(error); 22 | } 23 | ); 24 | 25 | mongoose.set('useCreateIndex', true); 26 | 27 | let app = express(); 28 | 29 | app.use(express.json({ limit: '5mb' })); 30 | 31 | // show log 32 | app.use(logger('dev')); 33 | 34 | app.use(bodyParser.json()); 35 | app.use(bodyParser.urlencoded({ extended: true })); 36 | app.use(cookieParser()); 37 | 38 | // set up cors to allow us to accept requests from our client 39 | app.use( 40 | cors({ 41 | origin: '*', // allow to server to accept request from different origin 42 | methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', 43 | credentials: true, // allow session cookie from browser to pass through 44 | }) 45 | ); 46 | 47 | app.use('/list', list); 48 | app.use('/status', status); 49 | app.use('/proof', proof); 50 | 51 | module.exports = app; 52 | -------------------------------------------------------------------------------- /server/routes/proof.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const BN = require('bn.js'); 3 | const TRANCHES = require('../constants/tranches').TRANCHES; 4 | const Whitelist = require('../models/Whitelist'); 5 | const Merkle = require('../utils/merkle.module'); 6 | const { ethers } = require('ethers'); 7 | 8 | require('dotenv').config(); 9 | 10 | router.get('/:trancheId/:address', async (req, res) => { 11 | try { 12 | if (!ethers.utils.isAddress(req.params.address)) { 13 | return res.status(400).json({ msg: 'Address is invalid' }); 14 | } 15 | 16 | let { trancheId, address } = req.params; 17 | console.log(trancheId, address); 18 | const info = await Whitelist.findOne({ tranche: trancheId, address }); 19 | console.log(info); 20 | if (!info) return res.status(500).json({ msg: 'Internal Server Error' }); 21 | let { amount } = info; 22 | amount = new BN(amount.toString() + '000000000000000000'); 23 | let result = await Whitelist.find({ tranche: trancheId }); 24 | let list = []; 25 | for (let i = 0; i < result.length; i++) { 26 | list.push([result[i].address, new BN(result[i].amount.toString() + '000000000000000000')]); 27 | } 28 | let tranche = await Merkle.getTranche(...list); 29 | let tree = await Merkle.createTree(tranche); 30 | 31 | let proof = await Merkle.getAccountBalanceProof(tree.tree, address, amount); 32 | 33 | return res.status(200).json({ proof: proof }); 34 | } catch (error) { 35 | console.log(error); 36 | return res.status(500).json({ msg: 'Internal Server Error' }); 37 | } 38 | }); 39 | 40 | module.exports = router; 41 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | Whitelist Claim 22 | 23 | 24 | 25 |
26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /client/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import * as connect from './actions'; 2 | 3 | const initialState = { 4 | web3: null, 5 | address: null, 6 | loading: false, 7 | starter: null, 8 | token: null, 9 | tranche: 0, 10 | tranches: [], 11 | proofs: [], 12 | balances: [], 13 | claimableAmount: 0, 14 | }; 15 | 16 | const rootReducer = (state = initialState, action) => { 17 | switch (action.type) { 18 | case connect.SET_WEB3: 19 | return { 20 | ...state, 21 | web3: action.web3, 22 | }; 23 | case connect.SET_ADDRESS: 24 | return { 25 | ...state, 26 | address: action.address, 27 | }; 28 | case connect.SET_LOADING: 29 | return { 30 | ...state, 31 | loading: action.loading, 32 | }; 33 | case connect.SET_STARTER: 34 | return { 35 | ...state, 36 | starter: action.starter, 37 | }; 38 | case connect.SET_TOKEN: 39 | return { 40 | ...state, 41 | token: action.token, 42 | }; 43 | case connect.SET_TRANCHE: 44 | return { 45 | ...state, 46 | tranche: action.tranche, 47 | }; 48 | case connect.SET_CLAIMABLE_AMOUNT: 49 | return { 50 | ...state, 51 | claimableAmount: action.claimableAmount, 52 | }; 53 | case connect.SET_TRANCHES: 54 | return { 55 | ...state, 56 | tranches: action.tranches, 57 | }; 58 | case connect.SET_PROOFS: 59 | return { 60 | ...state, 61 | proofs: action.proofs, 62 | }; 63 | case connect.SET_BALANCES: 64 | return { 65 | ...state, 66 | balances: action.balances, 67 | }; 68 | default: 69 | return state; 70 | } 71 | }; 72 | 73 | export default rootReducer; 74 | -------------------------------------------------------------------------------- /client/src/store/actions.js: -------------------------------------------------------------------------------- 1 | export const SET_WEB3 = 'SET_WEB3'; 2 | export const setWeb3 = (web3) => async (dispatch) => { 3 | dispatch({ 4 | type: SET_WEB3, 5 | web3, 6 | }); 7 | }; 8 | 9 | export const SET_ADDRESS = 'SET_ADDRESS'; 10 | export const setAddress = (address) => (dispatch) => { 11 | dispatch({ 12 | type: SET_ADDRESS, 13 | address, 14 | }); 15 | }; 16 | 17 | export const SET_STARTER = 'SET_STARTER'; 18 | export const setStarter = (starter) => async (dispatch) => { 19 | dispatch({ 20 | type: SET_STARTER, 21 | starter, 22 | }); 23 | }; 24 | 25 | export const SET_TOKEN = 'SET_TOKEN'; 26 | export const setToken = (token) => async (dispatch) => { 27 | dispatch({ 28 | type: SET_TOKEN, 29 | token, 30 | }); 31 | }; 32 | 33 | export const SET_LOADING = 'SET_LOADING'; 34 | export const setLoading = (loading) => (dispatch) => { 35 | dispatch({ 36 | type: SET_LOADING, 37 | loading, 38 | }); 39 | }; 40 | 41 | export const SET_TRANCHE = 'SET_TRANCHE'; 42 | export const setTranche = (tranche) => (dispatch) => { 43 | dispatch({ 44 | type: SET_TRANCHE, 45 | tranche, 46 | }); 47 | }; 48 | 49 | export const SET_CLAIMABLE_AMOUNT = 'SET_CLAIMABLE_AMOUNT'; 50 | export const setClaimableAmount = (claimableAmount) => (dispatch) => { 51 | dispatch({ 52 | type: SET_CLAIMABLE_AMOUNT, 53 | claimableAmount, 54 | }); 55 | }; 56 | 57 | export const SET_TRANCHES = 'SET_TRANCHES'; 58 | export const setTranches = (tranches) => (dispatch) => { 59 | dispatch({ 60 | type: SET_TRANCHES, 61 | tranches, 62 | }); 63 | }; 64 | 65 | export const SET_PROOFS = 'SET_PROOFS'; 66 | export const setProofs = (proofs) => (dispatch) => { 67 | dispatch({ 68 | type: SET_PROOFS, 69 | proofs, 70 | }); 71 | }; 72 | 73 | export const SET_BALANCES = 'SET_BALANCES'; 74 | export const setBalances = (balances) => (dispatch) => { 75 | dispatch({ 76 | type: SET_BALANCES, 77 | balances, 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('backend:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '8000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port, () => console.log(`Server is running on port ${port}!`)); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; 62 | 63 | // handle specific listen errors with friendly messages 64 | switch (error.code) { 65 | case 'EACCES': 66 | console.error(bind + ' requires elevated privileges'); 67 | process.exit(1); 68 | break; 69 | case 'EADDRINUSE': 70 | console.error(bind + ' is already in use'); 71 | process.exit(1); 72 | break; 73 | default: 74 | throw error; 75 | } 76 | } 77 | 78 | /** 79 | * Event listener for HTTP server "listening" event. 80 | */ 81 | 82 | function onListening() { 83 | var addr = server.address(); 84 | var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; 85 | debug('Listening on ' + bind); 86 | } 87 | -------------------------------------------------------------------------------- /client/src/components/Claim/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { message } from 'antd'; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | import * as actions from 'store/actions'; 5 | import BN from 'bn.js'; 6 | import './style.css'; 7 | 8 | export default function Claim() { 9 | const dispatch = useDispatch(); 10 | const token = process.env.REACT_APP_TOKEN_ADDRESS; 11 | const address = useSelector((state) => state.address); 12 | const tranche = useSelector((state) => state.tranche); 13 | const starter = useSelector((state) => state.starter); 14 | 15 | const tranches = useSelector((state) => state.tranches); 16 | const proofs = useSelector((state) => state.proofs); 17 | const balances = useSelector((state) => state.balances); 18 | const claimableAmount = useSelector((state) => state.claimableAmount); 19 | 20 | const claim = async () => { 21 | try { 22 | if (!address) { 23 | message.error('Please connect wallet first.'); 24 | return; 25 | } 26 | if (claimableAmount === 0) { 27 | message.error('You have nothing to claim.'); 28 | return; 29 | } 30 | 31 | dispatch(actions.setLoading(true)); 32 | const result = await starter.methods 33 | .multiClaim(address, tranches, balances, proofs) 34 | .send({ from: address }); 35 | if (result.status) { 36 | message.success('Receive Token successfully'); 37 | dispatch(actions.setClaimableAmount(0)); 38 | } 39 | dispatch(actions.setLoading(false)); 40 | } catch (err) { 41 | dispatch(actions.setLoading(false)); 42 | if (err.code === 4001) { 43 | return; 44 | } 45 | message.error('Claim has failed'); 46 | } 47 | }; 48 | 49 | return ( 50 |
51 | claim()}> 52 | Claim starter tokens 53 | 54 | 55 |
    56 |
  1. 57 | Open MetaMask, in Assets tab, click Add Token 58 |
  2. 59 |
  3. 60 | Choose Custom Token 61 |
  4. 62 |
  5. Fill Token address: {token}
  6. 63 |
64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | logo 4 |
5 | Merkle Starter 6 |
7 |

8 | 9 |

IDO platform, built on BSC with Merkle tree structure.

10 | 11 | ## Features 12 | 13 | - Faster. 14 | - Cheaper. 15 | - Whitelist with thousands of users. 16 | - Cost less transactions. 17 | 18 | ## Motivation 19 | Why we create Merklestarter ? recently we were trying to join some IDO on Polkastarter, and got angry. The site is too slow, no way to join only 300 people in whitelist. Going deeper in contract interaction, it shows very limited function because each time we can only add 100 people to receiver list. Furthermore, user must send 2 transaction to get token, swap and claim, which cost gas too much. 20 | Merklestarter will let funder easier to raise fund, once for thousand people, and user only need to claim by sending only 1 transaction. 21 | 22 | 23 | ## How Merklestarer works? 24 | 25 | ![](merklestarter.png) 26 | 27 | ### IDO Operator 28 | - Step 1: Prepare whitelist data, it can add thousands user at once (incomparison with 100 of Polkastarter) 29 | - Step2: there is no operator page now (will be developed next), so it can be done by run script `server/operator/seedThenImport.js` script to do 2 things: first calculate Merkle root, then seed that Merkle Root to MerkleStarter contract; second is import list of whitelist users into MongoDB, which will be used to build proofs. 30 | 31 | ### User 32 | - Go to client page, claim their tokens. For more detail, first users address will be send to server to check whitelist status in MongoDB, then if valid, server will response a Merkle Proof of that address, which will be submited to MerkleStater contract to verify, then claim their tokens, all on 1 transaction only. 33 | 34 | ## Curren Development Progress 35 | 36 | - [x] Smart Contracts: Done 37 | - [x] Server: Done 38 | - [x] Client: Done 39 | - [ ] Operator Page: To be developed, now run by scripts only 40 | - [ ] Crosschain: To be developed 41 | 42 | ## Instructions 43 | 44 | See [Instructions](Instructions.md) for more details. 45 | 46 | ## LICENSE 47 | 48 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge)](./LICENSE) 49 | 50 | 51 | -------------------------------------------------------------------------------- /client/.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | asset-manifest.json,1610508946756,f47c6fa39a2a27d25e0a5bca21af2490268dbc1de9d728c7343e9fe6fc4ef864 2 | precache-manifest.1f8873e491cce8780ffba54b8169ed4d.js,1610508946756,92817a326f257ade3973b2ceeb0533f7aa2ad19b21335a6cfc41514ebcd71b31 3 | index.html,1610508946756,5157057a8bbbfa0d8d2a9976c4fa1eb23f546a272d57423771da7e8d5eb9f61b 4 | service-worker.js,1610508946756,a546aeed0c18b3811dcb73658d56be256922ebd337e765ca8c7887277d1e8035 5 | static/css/main.dafd85ff.chunk.css,1610508946667,d0529a5fef59e0c76038e8ff5c40f7b5a92c452d34c3db8c07c8faae62646dd6 6 | static/css/main.dafd85ff.chunk.css.map,1610508946695,670c7527dfa4db1afc1dabd511b2728e0fb3b9ec556611cd1acd17dda2e276d9 7 | static/js/2.4da30442.chunk.js.LICENSE.txt,1610508946694,292dd323470de2d39ed399325a31eb010cb1a555ee48c4df6252999851bf202b 8 | static/js/main.320f6abd.chunk.js.map,1610508946695,e98c6b391f7c021711665f03d8b31d8f3075c0c5e5bbbd7bc93624e14e460f0e 9 | static/js/runtime-main.9161165c.js,1610508946693,582b91dcc76ba26adea36a847895f782eb5f4ca97d5caf45f4042500ade2fab9 10 | static/js/runtime-main.9161165c.js.map,1610508946695,c7e953f74234bd092b30d186f7fa08298460488c883a1e62555495a47cf2236a 11 | static/media/menu.7bb0a35d.svg,1610508946693,75d588c8300874716f0e799cf321a5e57015f7642416aa46a9013ac46f8afc14 12 | static/media/7Aulp_0qiz-aVz7u3PJLcUMYOFkQl0k30e0.2704adef.ttf,1610508946693,e3ae2bfa1dd4ecd0dfb9cd8404ca240071d20cf597788eece2454e2457b80069 13 | static/media/7Aulp_0qiz-aVz7u3PJLcUMYOFkpl0k30e0.6508d61b.ttf,1610508946693,7676d6080d318f2bb4257e81ea1f2189b897fa2a603e4b4c1d2b8b7e67bd913b 14 | static/media/7Aulp_0qiz-aVz7u3PJLcUMYOFnOkEk30e0.5fe52d61.ttf,1610508946666,3bbfd491c4bc80029008eb8cdc1188729457070455281037ea2a46f638c99d52 15 | static/media/7Aulp_0qiz-aVz7u3PJLcUMYOFlOl0k30e0.35b762e0.ttf,1610508946693,29c7b86333e951e9a81683a938c8a23604eb9d084adbbaa07cad4b43549652db 16 | static/js/main.320f6abd.chunk.js,1610508946679,2ff5221afa4c7e8783d7478b1218f34452895e5691a1ab30628c1b9b5776b5e8 17 | static/css/2.ba22b5ba.chunk.css,1610508946694,36800d4fc7307afd7a0b7f3281130fc55eb5a943c22caf3fdcad3256713a36a3 18 | static/js/2.4da30442.chunk.js,1610508946696,deffa36d19057c08c18a9b902abede4b05a3f5914c5a9a2eb6a6da8d0f438a5f 19 | static/css/2.ba22b5ba.chunk.css.map,1610508946695,d66ba14697d2c1fcbb59eaf19958e1f92e6819ddc3c9205c54cdd997388c6a5f 20 | static/js/2.4da30442.chunk.js.map,1610508946769,520e66df9df24b39b5fbc27dd8d058f56dd6512140752319385ec6fcdc38c757 21 | -------------------------------------------------------------------------------- /client/src/assets/images/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /server/operator/import.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const fs = require('fs'); 3 | const fastcsv = require('fast-csv'); 4 | const mongodb = require('mongodb').MongoClient; 5 | const path = require('path'); 6 | const { ethers } = require('ethers'); 7 | const { Contract } = ethers; 8 | const yargs = require('yargs/yargs'); 9 | const { hideBin } = require('yargs/helpers'); 10 | const argv = yargs(hideBin(process.argv)).argv; 11 | 12 | async function insertToDB(tranche, dataFile) { 13 | let stream = fs.createReadStream(path.resolve(__dirname, dataFile)); 14 | let csvData = []; 15 | let csvStream = fastcsv 16 | .parse({ headers: false }) 17 | .on('data', function (data) { 18 | csvData.push({ 19 | tranche: tranche.toString(), 20 | address: data[0].toLowerCase(), 21 | amount: parseInt(data[1]).toString(), 22 | }); 23 | }) 24 | .on('end', function () { 25 | if (csvData.length < 2) { 26 | csvData.push({ 27 | tranche: tranche.toString(), 28 | address: '0x0000000000000000000000000000000000000000', 29 | amount: '0', 30 | }); 31 | } 32 | mongodb.connect( 33 | process.env.MONGODB_URI, 34 | { useNewUrlParser: true, useUnifiedTopology: true }, 35 | (err, client) => { 36 | if (err) throw err; 37 | client 38 | .db('merklestarter') 39 | .collection('listeners') 40 | .insertMany(csvData, (err, res) => { 41 | if (err) throw err; 42 | console.log(`Inserted: ${res.insertedCount} rows`); 43 | client.close(); 44 | }); 45 | } 46 | ); 47 | }); 48 | 49 | stream.pipe(csvStream); 50 | } 51 | 52 | async function main() { 53 | try { 54 | const tranche = argv.tranche; 55 | if (tranche !== parseInt(tranche, 10)) { 56 | console.error('Please provide a valid tranche by flag --tranche'); 57 | process.exit(1); 58 | } 59 | 60 | const starterAddress = process.env.STARTER_ADDRESS; 61 | const starterABI = require(path.resolve(__dirname, '..', 'contracts', 'MerkleStarter.json')) 62 | .abi; 63 | const provider = new ethers.providers.InfuraProvider('rinkeby', process.env.INFURA_ID); 64 | const starter = new Contract(starterAddress, starterABI, provider); 65 | const currentTranche = (await starter.tranches()).toNumber() - 1; 66 | if (tranche < 0 || tranche > currentTranche) { 67 | console.log(`tranche must be 0 <= tranche <= ${currentTranche}`); 68 | process.exit(1); 69 | } 70 | 71 | const dataFile = argv.dataFile; 72 | if (!dataFile) { 73 | console.error('Please provide data file by flag --dataFile'); 74 | process.exit(1); 75 | } 76 | await insertToDB(tranche, dataFile); 77 | } catch (err) { 78 | console.log('Something went wrong! Please try again.'); 79 | } 80 | } 81 | 82 | main(); 83 | -------------------------------------------------------------------------------- /server/operator/seed.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const Merkle = require('../utils/merkle.module'); 3 | const csv = require('csv-parser'); 4 | const fs = require('fs'); 5 | const BN = require('bn.js'); 6 | const { ethers } = require('ethers'); 7 | const { Contract, Wallet } = ethers; 8 | const path = require('path'); 9 | const yargs = require('yargs/yargs'); 10 | const { hideBin } = require('yargs/helpers'); 11 | const argv = yargs(hideBin(process.argv)).argv; 12 | 13 | var provider = new ethers.providers.InfuraProvider('rinkeby', process.env.INFURA_ID); 14 | var wallet = new Wallet(process.env.PRIVATE_KEY, provider); 15 | 16 | async function seedNewAllocations(dataFile) { 17 | try { 18 | const listeners = []; 19 | let totalAllocation = new BN('0'); 20 | const s = fs.createReadStream(path.resolve(__dirname, dataFile)).pipe(csv({ headers: false })); 21 | for await (const row of s) { 22 | const values = Object.values(row); 23 | const user = values[0].toLowerCase(); 24 | const alloc = new BN(values[1] + '000000000000000000'); 25 | totalAllocation = totalAllocation.add(alloc); 26 | listeners.push([user, alloc]); 27 | } 28 | 29 | if (listeners.length < 2) { 30 | listeners.push(['0x0000000000000000000000000000000000000000', new BN('0')]); 31 | } 32 | 33 | let tranche = await Merkle.getTranche(...listeners); 34 | 35 | let tree = await Merkle.createTree(tranche); 36 | 37 | let treeRoot = Merkle.root(tree.tree.layers); 38 | let merkleRoot = Merkle.hexRoot(treeRoot); 39 | 40 | totalAllocation = totalAllocation.toString(); 41 | console.log('merkleRoot', merkleRoot); 42 | console.log('totalAllocation', totalAllocation); 43 | 44 | /** 45 | * Node environment 46 | */ 47 | const tokenAddress = process.env.TOKEN_ADDRESS; 48 | const erc20ABI = require(path.resolve(__dirname, '..', 'contracts', 'IERC20.json')).abi; 49 | const starterAddress = process.env.STARTER_ADDRESS; 50 | const starterABI = require(path.resolve(__dirname, '..', 'contracts', 'MerkleStarter.json')) 51 | .abi; 52 | 53 | let token = new Contract(tokenAddress, erc20ABI, provider).connect(wallet); 54 | let starter = new Contract(starterAddress, starterABI, provider).connect(wallet); 55 | 56 | let tx; 57 | tx = await token.approve(starterAddress, totalAllocation); 58 | await tx.wait(); 59 | console.log('approve done !'); 60 | 61 | tx = await starter.seedNewAllocations(merkleRoot, totalAllocation); 62 | await tx.wait(); 63 | 64 | console.log('Seed new allocation done !'); 65 | process.exit(0); 66 | } catch (err) { 67 | console.log(err); 68 | process.exit(1); 69 | } 70 | } 71 | 72 | async function main() { 73 | const dataFile = argv.dataFile; 74 | if (!dataFile) { 75 | console.error('Please provide data file by flag --dataFile'); 76 | process.exit(1); 77 | } 78 | await seedNewAllocations(dataFile); 79 | } 80 | 81 | main(); 82 | -------------------------------------------------------------------------------- /server/utils/merkle.module.js: -------------------------------------------------------------------------------- 1 | const Web3Util = require('web3-utils'); 2 | const EthUtil = require('ethereumjs-util'); 3 | const BN = require('bn.js'); 4 | 5 | const getTranche = function (...accountBalances) { 6 | return accountBalances.reduce( 7 | (prev, [account, balance, claimed]) => ({ 8 | ...prev, 9 | [account]: { balance, claimed }, 10 | }), 11 | {} 12 | ); 13 | }; 14 | 15 | const bufArrToHexArr = function (arr) { 16 | if (arr.some(el => !Buffer.isBuffer(el))) { 17 | throw new Error('Array is not an array of buffers'); 18 | } 19 | 20 | return arr.map(el => `0x${el.toString('hex')}`); 21 | }; 22 | 23 | const bufIndexOf = function (el, arr) { 24 | let hash; 25 | 26 | if (el.length !== 32 || !Buffer.isBuffer(el)) { 27 | hash = keccakFromString(el.toString()); 28 | } else { 29 | hash = el; 30 | } 31 | 32 | for (let i = 0; i < arr.length; i++) { 33 | if (hash.equals(arr[i])) { 34 | return i; 35 | } 36 | } 37 | 38 | return -1; 39 | }; 40 | 41 | const createTreeWithAccounts = async function (accounts) { 42 | const elements = Object.entries(accounts).map(([account, { balance }]) => 43 | Web3Util.soliditySha3(account.toLowerCase(), balance.toString()) 44 | ); 45 | 46 | return createMerkleTree(elements); 47 | }; 48 | 49 | const createMerkleTree = function (_elements) { 50 | let elements = _elements.filter(el => el).map(el => Buffer.from(Web3Util.hexToBytes(el))); 51 | 52 | elements = elements.sort(Buffer.compare); 53 | 54 | elements = bufDedup(elements); 55 | 56 | let layers = getLayers(elements); 57 | 58 | let MerkleTree = { 59 | elements: elements, 60 | layers: layers, 61 | }; 62 | 63 | return MerkleTree; 64 | }; 65 | 66 | const bufDedup = function (_elements) { 67 | return _elements.filter((el, idx) => { 68 | return idx === 0 || !_elements[idx - 1].equals(el); 69 | }); 70 | }; 71 | 72 | const getLayers = function (_elements) { 73 | if (_elements.length === 0) { 74 | return [['']]; 75 | } 76 | 77 | let layers = []; 78 | layers.push(_elements); 79 | 80 | while (layers[layers.length - 1].length > 1) { 81 | layers.push(getNextLayer(layers[layers.length - 1])); 82 | } 83 | return layers; 84 | }; 85 | 86 | const getNextLayer = function (_elements) { 87 | return _elements.reduce((layer, el, idx, arr) => { 88 | if (idx % 2 === 0) { 89 | layer.push(combinedHash(el, arr[idx + 1])); 90 | } 91 | return layer; 92 | }, []); 93 | }; 94 | 95 | const combinedHash = function (first, second) { 96 | if (!first) { 97 | return second; 98 | } 99 | if (!second) { 100 | return first; 101 | } 102 | 103 | return EthUtil.keccak256(sortAndConcat(first, second)); 104 | }; 105 | 106 | const sortAndConcat = function (...args) { 107 | return Buffer.concat([...args].sort(Buffer.compare)); 108 | }; 109 | 110 | const root = function (layers) { 111 | return layers[layers.length - 1][0]; 112 | }; 113 | 114 | const hexRoot = function (root) { 115 | return EthUtil.bufferToHex(root); 116 | }; 117 | 118 | const getAccountBalanceProof = function (tree, account, balance) { 119 | const element = Web3Util.soliditySha3(account.toLowerCase(), balance.toString()); 120 | return getHexProof(element, tree.elements, tree.layers); 121 | }; 122 | 123 | const getHexProof = function (element, elements, layers) { 124 | const el = Buffer.from(Web3Util.hexToBytes(element)); 125 | 126 | const proof = getProof(el, elements, layers); 127 | return bufArrToHexArr(proof); 128 | }; 129 | 130 | const getProof = function (el, elements, layers) { 131 | let idx = bufIndexOf(el, elements); 132 | 133 | if (idx === -1) { 134 | throw new Error('Element does not exist in Merkle tree'); 135 | } 136 | 137 | return layers.reduce((proof, layer) => { 138 | const pairElement = getPairElement(idx, layer); 139 | 140 | if (pairElement) { 141 | proof.push(pairElement); 142 | } 143 | 144 | idx = Math.floor(idx / 2); 145 | 146 | return proof; 147 | }, []); 148 | }; 149 | 150 | const getPairElement = function (idx, layer) { 151 | const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; 152 | 153 | if (pairIdx < layer.length) { 154 | return Buffer(layer[pairIdx]); 155 | } 156 | return null; 157 | }; 158 | 159 | const createTree = async function (tranche) { 160 | try { 161 | const tree = await createTreeWithAccounts(tranche); 162 | 163 | const totalAmount = Object.values(tranche).reduce( 164 | (prev, current) => prev.add(current.balance), 165 | new BN(0) 166 | ); 167 | return { tree: tree, balances: tranche, totalAmount: totalAmount }; 168 | } catch (err) { 169 | console.log(err); 170 | throw err; 171 | } 172 | }; 173 | 174 | module.exports = { getTranche, getAccountBalanceProof, createTree, root, hexRoot }; 175 | -------------------------------------------------------------------------------- /server/operator/seedThenImport.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const yargs = require('yargs/yargs'); 3 | const { hideBin } = require('yargs/helpers'); 4 | const argv = yargs(hideBin(process.argv)).argv; 5 | const BN = require('bn.js'); 6 | const fs = require('fs'); 7 | const Merkle = require('../utils/merkle.module'); 8 | const fastcsv = require('fast-csv'); 9 | const csv = require('csv-parser'); 10 | const mongodb = require('mongodb').MongoClient; 11 | const { ethers } = require('ethers'); 12 | const { Contract, Wallet } = ethers; 13 | const path = require('path'); 14 | 15 | async function seedNewAllocations(dataFile) { 16 | try { 17 | var provider = new ethers.providers.InfuraProvider('rinkeby', process.env.INFURA_ID); 18 | var wallet = new Wallet(process.env.PRIVATE_KEY, provider); 19 | const listeners = []; 20 | let totalAllocation = new BN('0'); 21 | const s = fs.createReadStream(path.resolve(__dirname, dataFile)).pipe(csv({ headers: false })); 22 | for await (const row of s) { 23 | const values = Object.values(row); 24 | const user = values[0].toLowerCase(); 25 | const alloc = new BN(values[1] + '000000000000000000'); 26 | totalAllocation = totalAllocation.add(alloc); 27 | listeners.push([user, alloc]); 28 | } 29 | 30 | if (listeners.length < 2) { 31 | listeners.push(['0x0000000000000000000000000000000000000000', new BN('0')]); 32 | } 33 | 34 | let tranche = await Merkle.getTranche(...listeners); 35 | let tree = await Merkle.createTree(tranche); 36 | let treeRoot = Merkle.root(tree.tree.layers); 37 | let merkleRoot = Merkle.hexRoot(treeRoot); 38 | 39 | totalAllocation = totalAllocation.toString(); 40 | console.log('merkleRoot', merkleRoot); 41 | console.log('totalAllocation', totalAllocation); 42 | 43 | const tokenAddress = process.env.TOKEN_ADDRESS; 44 | const erc20ABI = require(path.resolve(__dirname, '..', 'contracts', 'IERC20.json')).abi; 45 | const starterAddress = process.env.STARTER_ADDRESS; 46 | const starterABI = require(path.resolve(__dirname, '..', 'contracts', 'MerkleStarter.json')) 47 | .abi; 48 | let token = new Contract(tokenAddress, erc20ABI, provider).connect(wallet); 49 | let starter = new Contract(starterAddress, starterABI, provider).connect(wallet); 50 | 51 | let tx; 52 | tx = await token.approve(starterAddress, totalAllocation); 53 | await tx.wait(); 54 | console.log('approve done !'); 55 | 56 | tx = await starter.seedNewAllocations(merkleRoot, totalAllocation); 57 | await tx.wait(); 58 | 59 | console.log('Seed new allocation done !'); 60 | } catch (err) { 61 | console.log(err); 62 | process.exit(1); 63 | } 64 | } 65 | 66 | async function insertToDB(dataFile) { 67 | try { 68 | var provider = new ethers.providers.InfuraProvider('rinkeby', process.env.INFURA_ID); 69 | const starterAddress = process.env.STARTER_ADDRESS; 70 | const starterABI = require(path.resolve(__dirname, '..', 'contracts', 'MerkleStarter.json')) 71 | .abi; 72 | let starter = new Contract(starterAddress, starterABI, provider); 73 | 74 | const currentTranche = (await starter.tranches()).toNumber() - 1; 75 | 76 | let stream = fs.createReadStream(path.resolve(__dirname, dataFile)); 77 | let csvData = []; 78 | let csvStream = fastcsv 79 | .parse() 80 | .on('data', function (data) { 81 | csvData.push({ 82 | tranche: currentTranche.toString(), 83 | address: data[0].toLowerCase(), 84 | amount: parseInt(data[1]).toString(), 85 | }); 86 | }) 87 | .on('end', function () { 88 | if (csvData.length < 2) { 89 | csvData.push({ 90 | tranche: currentTranche.toString(), 91 | address: '0x0000000000000000000000000000000000000000', 92 | amount: '0', 93 | }); 94 | } 95 | mongodb.connect( 96 | 'mongodb://localhost:27017', 97 | { useNewUrlParser: true, useUnifiedTopology: true }, 98 | (err, client) => { 99 | if (err) throw err; 100 | client 101 | .db('starter') 102 | .collection('listeners') 103 | .insertMany(csvData, (err, res) => { 104 | if (err) throw err; 105 | console.log(`Inserted: ${res.insertedCount} rows`); 106 | client.close(); 107 | }); 108 | } 109 | ); 110 | }); 111 | 112 | stream.pipe(csvStream); 113 | } catch (err) { 114 | console.log(err); 115 | process.exit(1); 116 | } 117 | } 118 | 119 | async function main() { 120 | const dataFile = argv.dataFile; 121 | if (!dataFile) { 122 | console.error('Please provide data file by flag --dataFile'); 123 | process.exit(1); 124 | } 125 | await seedNewAllocations(dataFile); 126 | await insertToDB(dataFile); 127 | } 128 | 129 | main(); 130 | -------------------------------------------------------------------------------- /client/src/utils/connectMetaMask.js: -------------------------------------------------------------------------------- 1 | import detectEthereumProvider from '@metamask/detect-provider'; 2 | import Web3 from 'web3'; 3 | import store from 'store'; 4 | import { 5 | setWeb3, 6 | setAddress, 7 | setToken, 8 | setStarter, 9 | setTranche, 10 | setClaimableAmount, 11 | } from 'store/actions.js'; 12 | 13 | import MerkleStarter from '../contracts/MerkleStarter.json'; 14 | import IERC20 from '../contracts/IERC20.json'; 15 | 16 | const starterAddress = process.env.REACT_APP_STARTER_ADDRESS; 17 | const tokenAddress = process.env.REACT_APP_TOKEN_ADDRESS; 18 | 19 | export const connectMetaMask = async () => { 20 | // this returns the provider, or null if it wasn't detected 21 | const provider = await detectEthereumProvider(); 22 | const ethereum = window.ethereum; 23 | if (provider) { 24 | startApp(provider); // Initialize your app 25 | } else { 26 | console.log('Please install MetaMask!'); 27 | } 28 | 29 | async function startApp(provider) { 30 | // If the provider returned by detectEthereumProvider is not the same as 31 | // window.ethereum, something is overwriting it, perhaps another wallet. 32 | if (provider !== window.ethereum) { 33 | console.error('Do you have multiple wallets installed?'); 34 | } else { 35 | const web3 = new Web3(window.ethereum); 36 | const starter = new web3.eth.Contract(MerkleStarter.abi, starterAddress); 37 | const token = new web3.eth.Contract(IERC20.abi, tokenAddress); 38 | 39 | let tranche = await starter.methods.tranches().call(); 40 | tranche -= 1; 41 | 42 | store.dispatch(setStarter(starter)); 43 | store.dispatch(setToken(token)); 44 | store.dispatch(setWeb3(web3)); 45 | store.dispatch(setTranche(tranche)); 46 | connect(); 47 | } 48 | // Access the decentralized web! 49 | } 50 | 51 | /**********************************************************/ 52 | /* Handle chain (network) and chainChanged (per EIP-1193) */ 53 | /**********************************************************/ 54 | 55 | // Normally, we would recommend the 'eth_chainId' RPC method, but it currently 56 | // returns incorrectly formatted chain ID values. 57 | 58 | ethereum.on('chainChanged', handleChainChanged); 59 | 60 | function handleChainChanged(_chainId) { 61 | // We recommend reloading the page, unless you must do otherwise 62 | window.location.reload(); 63 | } 64 | 65 | /***********************************************************/ 66 | /* Handle user accounts and accountsChanged (per EIP-1193) */ 67 | /***********************************************************/ 68 | 69 | let currentAccount = null; 70 | ethereum 71 | .request({ method: 'eth_accounts' }) 72 | .then(handleAccountsChanged) 73 | .catch((err) => { 74 | // Some unexpected error. 75 | // For backwards compatibility reasons, if no accounts are available, 76 | // eth_accounts will return an empty array. 77 | console.error(err); 78 | }); 79 | 80 | // Note that this event is emitted on page load. 81 | // If the array of accounts is non-empty, you're already 82 | // connected. 83 | ethereum.on('accountsChanged', handleAccountsChanged); 84 | 85 | // For now, 'eth_accounts' will continue to always return an array 86 | async function handleAccountsChanged(accounts) { 87 | if (accounts.length === 0) { 88 | // MetaMask is locked or the user has not connected any accounts 89 | console.log('Please connect to MetaMask.'); 90 | store.dispatch(setAddress(null)); 91 | } else if (accounts[0] !== currentAccount) { 92 | const web3 = new Web3(window.ethereum); 93 | currentAccount = web3.utils.toChecksumAddress(accounts[0]); 94 | store.dispatch(setAddress(currentAccount)); 95 | 96 | const starter = new web3.eth.Contract(MerkleStarter.abi, starterAddress); 97 | let tranche = await starter.methods.tranches().call(); 98 | let claimableAmount = 0; 99 | for (let i = 0; i < tranche; i++) { 100 | const isClaimed = await starter.methods.claimed(i, currentAccount).call(); 101 | if (isClaimed) { 102 | continue; 103 | } 104 | let res = await fetch(`${process.env.REACT_APP_SERVER_URL}/status/${i}/${currentAccount}`); 105 | if (res.status === 200) { 106 | let { amount } = await res.json(); 107 | claimableAmount += parseInt(amount); 108 | } 109 | } 110 | store.dispatch(setClaimableAmount(claimableAmount)); 111 | } 112 | } 113 | 114 | /*********************************************/ 115 | /* Access the user's accounts (per EIP-1102) */ 116 | /*********************************************/ 117 | 118 | // You should only attempt to request the user's accounts in response to user 119 | // interaction, such as a button click. 120 | // Otherwise, you popup-spam the user like it's 1999. 121 | // If you fail to retrieve the user's account(s), you should encourage the user 122 | // to initiate the attempt. 123 | 124 | function connect() { 125 | ethereum 126 | .request({ method: 'eth_requestAccounts' }) 127 | .then(handleAccountsChanged) 128 | .catch((err) => { 129 | if (err.code === 4001) { 130 | // EIP-1193 userRejectedRequest error 131 | // If this happens, the user rejected the connection request. 132 | console.log('Please connect to MetaMask.'); 133 | } else { 134 | console.error(err); 135 | } 136 | }); 137 | } 138 | }; 139 | -------------------------------------------------------------------------------- /client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Collapse, Navbar, NavbarToggler, NavbarBrand, Nav, NavItem } from 'reactstrap'; 3 | import ConnectWallet from 'components/ConnectWallet'; 4 | import Status from 'components/Status'; 5 | import Claim from 'components/Claim'; 6 | import { BackTop, Spin } from 'antd'; 7 | import { connectMetaMask } from 'utils/connectMetaMask'; 8 | import { useSelector } from 'react-redux'; 9 | import Web3Modal from 'web3modal'; 10 | import WalletConnectProvider from '@walletconnect/web3-provider'; 11 | import './App.css'; 12 | import { Button } from 'antd'; 13 | import Web3 from 'web3'; 14 | import store from 'store'; 15 | import BN from 'bn.js'; 16 | 17 | import { 18 | setWeb3, 19 | setAddress, 20 | setToken, 21 | setStarter, 22 | setTranche, 23 | setClaimableAmount, 24 | setLoading, 25 | setTranches, 26 | setProofs, 27 | setBalances, 28 | } from 'store/actions.js'; 29 | 30 | import MerkleStarter from 'contracts/MerkleStarter.json'; 31 | import IERC20 from 'contracts/IERC20.json'; 32 | import './assets/scss/style-landing-page.scss'; 33 | 34 | const starterAddress = process.env.REACT_APP_STARTER_ADDRESS; 35 | const tokenAddress = process.env.REACT_APP_TOKEN_ADDRESS; 36 | 37 | function App() { 38 | const loading = useSelector((state) => state.loading); 39 | const address = useSelector((state) => state.address); 40 | const [isOpen, setIsOpen] = useState(false); 41 | let web3 = null; 42 | const toggle = () => setIsOpen(!isOpen); 43 | let [mainmenuArea, setMainmenuArea] = useState(''); 44 | 45 | const providerOptions = { 46 | walletconnect: { 47 | package: WalletConnectProvider, 48 | options: { 49 | infuraId: process.env.REACT_APP_INFURA_ID, 50 | }, 51 | }, 52 | }; 53 | const web3Modal = new Web3Modal({ 54 | network: process.env.REACT_APP_NETWORK_ID, 55 | cacheProvider: true, 56 | providerOptions, 57 | }); 58 | 59 | const onConnect = async () => { 60 | await web3Modal.clearCachedProvider(); 61 | const provider = await web3Modal.connect(); 62 | store.dispatch(setLoading(true)); 63 | await subscribeProvider(provider); 64 | web3 = new Web3(provider); 65 | const accounts = await web3.eth.getAccounts(); 66 | const currentAccount = accounts[0].toLowerCase(); 67 | store.dispatch(setWeb3(web3)); 68 | store.dispatch(setAddress(currentAccount)); 69 | 70 | let starter = new web3.eth.Contract(MerkleStarter.abi, starterAddress); 71 | const token = new web3.eth.Contract(IERC20.abi, tokenAddress); 72 | 73 | let tranche = await starter.methods.tranches().call({ from: currentAccount }); 74 | 75 | let claimableAmount = 0; 76 | const tranches = []; 77 | const proofs = []; 78 | const balances = []; 79 | 80 | for (let i = 0; i < tranche; i++) { 81 | const isClaimed = await starter.methods.claimed(i, currentAccount).call(); 82 | if (isClaimed) { 83 | continue; 84 | } 85 | let res; 86 | res = await fetch(`${process.env.REACT_APP_SERVER_URL}/proof/${i}/${currentAccount}`); 87 | if (res.status !== 200) continue; 88 | let { proof } = await res.json(); 89 | res = await fetch(`${process.env.REACT_APP_SERVER_URL}/status/${i}/${currentAccount}`); 90 | if (res.status !== 200) continue; 91 | let { amount } = await res.json(); 92 | claimableAmount += parseInt(amount); 93 | amount = new BN(amount + '000000000000000000'); 94 | tranches.push(i); 95 | proofs.push(proof); 96 | balances.push(amount); 97 | } 98 | 99 | store.dispatch(setClaimableAmount(claimableAmount)); 100 | store.dispatch(setStarter(starter)); 101 | store.dispatch(setToken(token)); 102 | store.dispatch(setTranche(tranche)); 103 | store.dispatch(setTranches(tranches)); 104 | store.dispatch(setProofs(proofs)); 105 | store.dispatch(setBalances(balances)); 106 | store.dispatch(setLoading(false)); 107 | }; 108 | 109 | const subscribeProvider = async (provider) => { 110 | if (!provider.on) { 111 | return; 112 | } 113 | provider.on('close', async () => { 114 | window.location.reload(); 115 | }); 116 | provider.on('accountsChanged', async (accounts) => { 117 | console.log('on accountsChanged'); 118 | window.location.reload(); 119 | }); 120 | provider.on('chainChanged', async (chainId) => { 121 | console.log('on chainChanged'); 122 | window.location.reload(); 123 | }); 124 | provider.on('networkChanged', async (networkId) => { 125 | console.log('on networkChanged', networkId); 126 | window.location.reload(); 127 | }); 128 | }; 129 | 130 | return ( 131 |
132 | 133 | 134 |
135 | 136 | 137 | 142 | {/* */} 143 | {address ?

{address}

: } 144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | 153 |
154 | 155 |
156 |
157 |
158 |
159 |
160 | 161 |
162 | ); 163 | } 164 | 165 | export default App; 166 | -------------------------------------------------------------------------------- /contracts/contracts/MerkleStarter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "@openzeppelin/contracts/cryptography/MerkleProof.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 8 | import "@openzeppelin/contracts/math/SafeMath.sol"; 9 | import "@openzeppelin/contracts/proxy/Initializable.sol"; 10 | import "@openzeppelin/contracts/access/Ownable.sol"; 11 | 12 | contract MerkleStarter is Ownable { 13 | using SafeERC20 for IERC20; 14 | using SafeMath for uint256; 15 | 16 | event Claimed(address claimant, uint256 Month, uint256 balance); 17 | event TrancheAdded( 18 | uint256 tranche, 19 | bytes32 merkleRoot, 20 | uint256 totalAmount 21 | ); 22 | 23 | uint public rate; 24 | 25 | event TrancheExpired(uint256 tranche); 26 | 27 | IERC20 public token; 28 | 29 | mapping(uint256 => bytes32) public merkleRoots; 30 | mapping(uint256 => mapping(address => bool)) public claimed; 31 | uint256 public tranches; 32 | 33 | constructor(IERC20 _token, uint256 _rate) public { 34 | token = _token; 35 | rate = _rate; 36 | } 37 | 38 | function seedNewAllocations(bytes32 _merkleRoot, uint256 _totalAllocation) 39 | public 40 | onlyOwner 41 | returns (uint256 trancheId) 42 | { 43 | token.safeTransferFrom(msg.sender, address(this), _totalAllocation); 44 | 45 | trancheId = tranches; 46 | merkleRoots[trancheId] = _merkleRoot; 47 | 48 | tranches = tranches.add(1); 49 | 50 | emit TrancheAdded(trancheId, _merkleRoot, _totalAllocation); 51 | } 52 | 53 | function expireTranche(uint256 _trancheId) public onlyOwner { 54 | merkleRoots[_trancheId] = bytes32(0); 55 | 56 | emit TrancheExpired(_trancheId); 57 | } 58 | 59 | function claim( 60 | address user, 61 | uint256 _tranche, 62 | uint256 _balance, 63 | bytes32[] memory _merkleProof 64 | ) public payable { 65 | require(msg.value >= _balance * rate); 66 | _claim(user, _tranche, _balance, _merkleProof); 67 | _disburse(user, _balance); 68 | } 69 | 70 | function multiClaim( 71 | address user, 72 | uint256[] memory _tranches, 73 | uint256[] memory _balances, 74 | bytes32[][] memory _merkleProofs 75 | ) public { 76 | uint256 len = _tranches.length; 77 | require( 78 | len == _balances.length && len == _merkleProofs.length, 79 | "Mismatching inputs" 80 | ); 81 | 82 | uint256 totalBalance = 0; 83 | for (uint256 i = 0; i < len; i++) { 84 | _claim(user, _tranches[i], _balances[i], _merkleProofs[i]); 85 | totalBalance = totalBalance.add(_balances[i]); 86 | } 87 | _disburse(user, totalBalance); 88 | } 89 | 90 | function verifyClaim( 91 | address user, 92 | uint256 _tranche, 93 | uint256 _balance, 94 | bytes32[] memory _merkleProof 95 | ) public view returns (bool valid) { 96 | return _verifyClaim(user, _tranche, _balance, _merkleProof); 97 | } 98 | 99 | function _claim( 100 | address user, 101 | uint256 _tranche, 102 | uint256 _balance, 103 | bytes32[] memory _merkleProof 104 | ) private { 105 | require(_tranche < tranches, "You cannot claim future token"); 106 | 107 | require(!claimed[_tranche][user], "you has already claimed"); 108 | require( 109 | _verifyClaim(user, _tranche, _balance, _merkleProof), 110 | "Incorrect merkle proof" 111 | ); 112 | 113 | claimed[_tranche][user] = true; 114 | 115 | emit Claimed(user, _tranche, _balance); 116 | } 117 | 118 | function _verifyClaim( 119 | address user, 120 | uint256 _tranche, 121 | uint256 _balance, 122 | bytes32[] memory _merkleProof 123 | ) private view returns (bool valid) { 124 | bytes32 leaf = keccak256(abi.encodePacked(user, _balance)); 125 | return MerkleProof.verify(_merkleProof, merkleRoots[_tranche], leaf); 126 | } 127 | 128 | function _disburse(address user, uint256 _balance) private { 129 | if (_balance > 0) { 130 | token.safeTransfer(user, _balance); 131 | } else { 132 | revert( 133 | "No balance would be transferred - not going to waste your gas" 134 | ); 135 | } 136 | } 137 | 138 | /** 139 | * @dev function withdraw ETH to account owner 140 | * @param _amount is amount withdraw 141 | */ 142 | function withdrawETH(uint256 _amount) external onlyOwner { 143 | require(_amount > 0, "_amount must be greater than 0"); 144 | require( 145 | address(this).balance >= _amount, 146 | "_amount must be less than the ETH balance of the contract" 147 | ); 148 | msg.sender.transfer(_amount); 149 | } 150 | 151 | /** 152 | * @dev function withdraw token to account owner 153 | * @param _amount is amount withdraw 154 | */ 155 | function withdrawToken(uint256 _amount) external onlyOwner { 156 | require(_amount > 0, "_amount must be greater than 0"); 157 | require( 158 | token.balanceOf(address(this)) >= _amount, 159 | "The balance is not enough!" 160 | ); 161 | token.transfer(msg.sender, _amount); 162 | } 163 | 164 | /** 165 | * @dev function withdraw ERC20 token to account owner 166 | * @param _token is address of the token 167 | * @param _amount is amount withdraw 168 | */ 169 | function withdrawERC20(address _token, uint256 _amount) external onlyOwner { 170 | require(_amount > 0, "_amount must be greater than 0"); 171 | require( 172 | IERC20(_token).balanceOf(address(this)) >= _amount, 173 | "The balance is not enough!" 174 | ); 175 | IERC20(_token).transfer(msg.sender, _amount); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Instructions.md: -------------------------------------------------------------------------------- 1 | # Merkle Starter 2 | 3 | ## Intro 4 | 5 | - **Merkle Starter**: Operator will create a mechanism to store whitelist information with a data structure called Merkle Tree. With this mechanism, operator now cost only 1 transaction each time they want to do whitelist. Distribute cost now are pushed to user, they must send a claim transaction to get their tokens. 6 | 7 | ## Repo structure 8 | 9 | Merkle Starter system contains 3 part: 10 | 11 | - `contracts`: write and deploy `MerkleStarter` contract. 12 | - `server`: Store all merkle whitelist data, imported by csv file. 13 | - `client`: connect wallet and claim data. 14 | 15 | ## Deployment 16 | 17 | > **IMPORTANT**: Don't post your private key in any where public, don't share to any body. If you lose your key, you will lose you money. 18 | 19 | ### Contract 20 | 21 | Step 1: install dependencies: 22 | 23 | ```js 24 | npm install 25 | ``` 26 | 27 | Step 2: Copy `.env.example` to a new `.env` file and fill your environment variables here: 28 | 29 | ```js 30 | INFURA_ID= 31 | PRIVATE_KEY= 32 | TOKEN_ADDRESS= 33 | ``` 34 | 35 | if you not have a infura id, go to `https://infura.io/` to register one. 36 | 37 | Step 3: run deployment with a specific network 38 | 39 | ```js 40 | truffle migrate --reset --network 41 | ``` 42 | 43 | for example 44 | 45 | ```js 46 | truffle migrate --reset --network rinkeby 47 | ``` 48 | 49 | if you want to config more network, you can add/change it in `truffle-config.js`, example: 50 | 51 | ```js 52 | rinkeby: { 53 | provider: new HDWalletProvider( 54 | process.env.PRIVATE_KEY, 55 | 'https://rinkeby.infura.io/v3/' + process.env.INFURA_ID 56 | ), 57 | network_id: '4', 58 | }, 59 | ``` 60 | 61 | at end of migration, you can see deployed `MerkleStarter` contract address in terminal. Or you can get address in `contracts/build/contracts/MerkleStarter.json` file. 62 | 63 | ### Server 64 | 65 | Server is the most important part in Merkle Starter. 66 | 67 | - Step 1: prepare your server, i recommend use a Ubuntu server. 68 | - Step 2: Install mongodb and run it on: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/ 69 | - Step 3: Install node and npm if needed. I'm using node v12.16.1. I recommend to use `nvm` to install node. 70 | - Step 4: Go to `server` then install dependencies: 71 | 72 | ```js 73 | npm install 74 | ``` 75 | 76 | - Step 5: Copy `.env.example` to a new `.env` file and fill your environment variables here: 77 | 78 | ```js 79 | MONGODB_URI= 80 | NETWORK_ID= 81 | PRIVATE_KEY= 82 | INFURA_ID= 83 | TOKEN_ADDRESS= 84 | STARTER_ADDRESS= 85 | ``` 86 | 87 | - Step 5: Prepare your csv file, place it in `operator` folder. For example `0.csv` 88 | - Step 6: seed merkle root to `MerkleStarter` contract. Run: 89 | 90 | ```js 91 | node operator/seed --dataFile= 92 | ``` 93 | 94 | for example 95 | 96 | ```js 97 | node operator/seed --dataFile=0.csv 98 | ``` 99 | 100 | > IMPORTANT: once data go to contract, it can't be reversed. So please check your csv file properly before start whitelist. 101 | 102 | - Step 7: import Merkle data to mongodb. 103 | 104 | > Note: unlike step 6, here is insert to your mongodb, which can be edited if you face some errors. 105 | 106 | Run: 107 | 108 | ```js 109 | node operator/import.js --tranche= --dataFile= 110 | ``` 111 | 112 | for example 113 | 114 | ```js 115 | node operator/import.js --tranche=0 --dataFile=0.csv 116 | ``` 117 | 118 | > IMPORTANT: tranche must be exactly the turn that you seeded to MerkleStarter contract. So to be clear, i recommend you to name the csv data file to be same as the whitelist turn, for example `0.csv` for first turn, `1.csv` for second turn. 119 | 120 | - Step 8.0: if you want to run a local version, just run: 121 | 122 | ```js 123 | npm start 124 | ``` 125 | 126 | your server now running on `http://localhost:8000` 127 | 128 | - Step 8.1: run server online 129 | 130 | I recommend to use `pm2` to run server as a daemon. Install `pm2` by: 131 | 132 | ```js 133 | npm install pm2@latest -g 134 | ``` 135 | 136 | Then start server: 137 | 138 | ```js 139 | pm2 start bin/www --name whitelist-v2 140 | ``` 141 | 142 | Your server now run at `serverIP:8000`. 143 | 144 | ### Client 145 | 146 | - Step 1: go to client, install dependencies: 147 | 148 | ```js 149 | npm install 150 | ``` 151 | 152 | - Step 2: Copy `.env.example` to a new `.env` file and fill your environment variables here: 153 | 154 | ```js 155 | SKIP_PREFLIGHT_CHECK=true 156 | REACT_APP_SERVER_URL= 157 | REACT_APP_NETWORK_ID= 158 | REACT_APP_TOKEN_ADDRESS= 159 | STARTER_ADDRESS= 160 | REACT_APP_INFURA_ID= 161 | ``` 162 | 163 | - Step 3.0: if you want to run local version, just run: 164 | 165 | ```js 166 | npm start 167 | ``` 168 | 169 | - Step 3.1: if you want to deploy a live version, you can use `Firebase hosting` for easy page end with `web.app` 170 | 171 | fist build your client 172 | 173 | ```js 174 | npm run build 175 | ``` 176 | 177 | install firebase hosting:https://firebase.google.com/docs/hosting/quickstart 178 | 179 | then init firebase: 180 | 181 | ```js 182 | firebase init hosting 183 | ``` 184 | 185 | then deploy to firebase host 186 | 187 | ```js 188 | firebase deploy --only hosting 189 | ``` 190 | 191 | Now your web live at `your-web-name.web.app` 192 | 193 | ## Deploy use your own domain 194 | 195 | For https and run with your domain, i recommend add your domain to Cloudflare, point a DNS to your server. 196 | Then in your server, you setup nginx to redirect domain name your server/client URL. 197 | 198 | ## LICENSE 199 | 200 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge)](./LICENSE) 201 | 202 | 203 | -------------------------------------------------------------------------------- /client/src/contracts/MerkleStarter.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "MerkleStarter", 3 | "abi": [ 4 | { 5 | "inputs": [ 6 | { 7 | "internalType": "contract IERC20", 8 | "name": "_token", 9 | "type": "address" 10 | } 11 | ], 12 | "stateMutability": "nonpayable", 13 | "type": "constructor" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": false, 20 | "internalType": "address", 21 | "name": "claimant", 22 | "type": "address" 23 | }, 24 | { 25 | "indexed": false, 26 | "internalType": "uint256", 27 | "name": "Month", 28 | "type": "uint256" 29 | }, 30 | { 31 | "indexed": false, 32 | "internalType": "uint256", 33 | "name": "balance", 34 | "type": "uint256" 35 | } 36 | ], 37 | "name": "Claimed", 38 | "type": "event" 39 | }, 40 | { 41 | "anonymous": false, 42 | "inputs": [ 43 | { 44 | "indexed": true, 45 | "internalType": "address", 46 | "name": "previousOwner", 47 | "type": "address" 48 | }, 49 | { 50 | "indexed": true, 51 | "internalType": "address", 52 | "name": "newOwner", 53 | "type": "address" 54 | } 55 | ], 56 | "name": "OwnershipTransferred", 57 | "type": "event" 58 | }, 59 | { 60 | "anonymous": false, 61 | "inputs": [ 62 | { 63 | "indexed": false, 64 | "internalType": "uint256", 65 | "name": "tranche", 66 | "type": "uint256" 67 | }, 68 | { 69 | "indexed": false, 70 | "internalType": "bytes32", 71 | "name": "merkleRoot", 72 | "type": "bytes32" 73 | }, 74 | { 75 | "indexed": false, 76 | "internalType": "uint256", 77 | "name": "totalAmount", 78 | "type": "uint256" 79 | } 80 | ], 81 | "name": "TrancheAdded", 82 | "type": "event" 83 | }, 84 | { 85 | "anonymous": false, 86 | "inputs": [ 87 | { 88 | "indexed": false, 89 | "internalType": "uint256", 90 | "name": "tranche", 91 | "type": "uint256" 92 | } 93 | ], 94 | "name": "TrancheExpired", 95 | "type": "event" 96 | }, 97 | { 98 | "inputs": [ 99 | { 100 | "internalType": "uint256", 101 | "name": "", 102 | "type": "uint256" 103 | }, 104 | { 105 | "internalType": "address", 106 | "name": "", 107 | "type": "address" 108 | } 109 | ], 110 | "name": "claimed", 111 | "outputs": [ 112 | { 113 | "internalType": "bool", 114 | "name": "", 115 | "type": "bool" 116 | } 117 | ], 118 | "stateMutability": "view", 119 | "type": "function", 120 | "constant": true 121 | }, 122 | { 123 | "inputs": [ 124 | { 125 | "internalType": "uint256", 126 | "name": "", 127 | "type": "uint256" 128 | } 129 | ], 130 | "name": "merkleRoots", 131 | "outputs": [ 132 | { 133 | "internalType": "bytes32", 134 | "name": "", 135 | "type": "bytes32" 136 | } 137 | ], 138 | "stateMutability": "view", 139 | "type": "function", 140 | "constant": true 141 | }, 142 | { 143 | "inputs": [], 144 | "name": "owner", 145 | "outputs": [ 146 | { 147 | "internalType": "address", 148 | "name": "", 149 | "type": "address" 150 | } 151 | ], 152 | "stateMutability": "view", 153 | "type": "function", 154 | "constant": true 155 | }, 156 | { 157 | "inputs": [], 158 | "name": "renounceOwnership", 159 | "outputs": [], 160 | "stateMutability": "nonpayable", 161 | "type": "function" 162 | }, 163 | { 164 | "inputs": [], 165 | "name": "token", 166 | "outputs": [ 167 | { 168 | "internalType": "contract IERC20", 169 | "name": "", 170 | "type": "address" 171 | } 172 | ], 173 | "stateMutability": "view", 174 | "type": "function", 175 | "constant": true 176 | }, 177 | { 178 | "inputs": [], 179 | "name": "tranches", 180 | "outputs": [ 181 | { 182 | "internalType": "uint256", 183 | "name": "", 184 | "type": "uint256" 185 | } 186 | ], 187 | "stateMutability": "view", 188 | "type": "function", 189 | "constant": true 190 | }, 191 | { 192 | "inputs": [ 193 | { 194 | "internalType": "address", 195 | "name": "newOwner", 196 | "type": "address" 197 | } 198 | ], 199 | "name": "transferOwnership", 200 | "outputs": [], 201 | "stateMutability": "nonpayable", 202 | "type": "function" 203 | }, 204 | { 205 | "inputs": [ 206 | { 207 | "internalType": "bytes32", 208 | "name": "_merkleRoot", 209 | "type": "bytes32" 210 | }, 211 | { 212 | "internalType": "uint256", 213 | "name": "_totalAllocation", 214 | "type": "uint256" 215 | } 216 | ], 217 | "name": "seedNewAllocations", 218 | "outputs": [ 219 | { 220 | "internalType": "uint256", 221 | "name": "trancheId", 222 | "type": "uint256" 223 | } 224 | ], 225 | "stateMutability": "nonpayable", 226 | "type": "function" 227 | }, 228 | { 229 | "inputs": [ 230 | { 231 | "internalType": "uint256", 232 | "name": "_trancheId", 233 | "type": "uint256" 234 | } 235 | ], 236 | "name": "expireTranche", 237 | "outputs": [], 238 | "stateMutability": "nonpayable", 239 | "type": "function" 240 | }, 241 | { 242 | "inputs": [ 243 | { 244 | "internalType": "address", 245 | "name": "user", 246 | "type": "address" 247 | }, 248 | { 249 | "internalType": "uint256", 250 | "name": "_tranche", 251 | "type": "uint256" 252 | }, 253 | { 254 | "internalType": "uint256", 255 | "name": "_balance", 256 | "type": "uint256" 257 | }, 258 | { 259 | "internalType": "bytes32[]", 260 | "name": "_merkleProof", 261 | "type": "bytes32[]" 262 | } 263 | ], 264 | "name": "claim", 265 | "outputs": [], 266 | "stateMutability": "nonpayable", 267 | "type": "function" 268 | }, 269 | { 270 | "inputs": [ 271 | { 272 | "internalType": "address", 273 | "name": "user", 274 | "type": "address" 275 | }, 276 | { 277 | "internalType": "uint256[]", 278 | "name": "_tranches", 279 | "type": "uint256[]" 280 | }, 281 | { 282 | "internalType": "uint256[]", 283 | "name": "_balances", 284 | "type": "uint256[]" 285 | }, 286 | { 287 | "internalType": "bytes32[][]", 288 | "name": "_merkleProofs", 289 | "type": "bytes32[][]" 290 | } 291 | ], 292 | "name": "multiClaim", 293 | "outputs": [], 294 | "stateMutability": "nonpayable", 295 | "type": "function" 296 | }, 297 | { 298 | "inputs": [ 299 | { 300 | "internalType": "address", 301 | "name": "user", 302 | "type": "address" 303 | }, 304 | { 305 | "internalType": "uint256", 306 | "name": "_tranche", 307 | "type": "uint256" 308 | }, 309 | { 310 | "internalType": "uint256", 311 | "name": "_balance", 312 | "type": "uint256" 313 | }, 314 | { 315 | "internalType": "bytes32[]", 316 | "name": "_merkleProof", 317 | "type": "bytes32[]" 318 | } 319 | ], 320 | "name": "verifyClaim", 321 | "outputs": [ 322 | { 323 | "internalType": "bool", 324 | "name": "valid", 325 | "type": "bool" 326 | } 327 | ], 328 | "stateMutability": "view", 329 | "type": "function", 330 | "constant": true 331 | }, 332 | { 333 | "inputs": [ 334 | { 335 | "internalType": "uint256", 336 | "name": "_amount", 337 | "type": "uint256" 338 | } 339 | ], 340 | "name": "withdrawETH", 341 | "outputs": [], 342 | "stateMutability": "nonpayable", 343 | "type": "function" 344 | }, 345 | { 346 | "inputs": [ 347 | { 348 | "internalType": "uint256", 349 | "name": "_amount", 350 | "type": "uint256" 351 | } 352 | ], 353 | "name": "withdrawToken", 354 | "outputs": [], 355 | "stateMutability": "nonpayable", 356 | "type": "function" 357 | }, 358 | { 359 | "inputs": [ 360 | { 361 | "internalType": "address", 362 | "name": "_token", 363 | "type": "address" 364 | }, 365 | { 366 | "internalType": "uint256", 367 | "name": "_amount", 368 | "type": "uint256" 369 | } 370 | ], 371 | "name": "withdrawERC20", 372 | "outputs": [], 373 | "stateMutability": "nonpayable", 374 | "type": "function" 375 | } 376 | ] 377 | } -------------------------------------------------------------------------------- /server/contracts/MerkleStarter.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "MerkleStarter", 3 | "abi": [ 4 | { 5 | "inputs": [ 6 | { 7 | "internalType": "contract IERC20", 8 | "name": "_token", 9 | "type": "address" 10 | } 11 | ], 12 | "stateMutability": "nonpayable", 13 | "type": "constructor" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": false, 20 | "internalType": "address", 21 | "name": "claimant", 22 | "type": "address" 23 | }, 24 | { 25 | "indexed": false, 26 | "internalType": "uint256", 27 | "name": "Month", 28 | "type": "uint256" 29 | }, 30 | { 31 | "indexed": false, 32 | "internalType": "uint256", 33 | "name": "balance", 34 | "type": "uint256" 35 | } 36 | ], 37 | "name": "Claimed", 38 | "type": "event" 39 | }, 40 | { 41 | "anonymous": false, 42 | "inputs": [ 43 | { 44 | "indexed": true, 45 | "internalType": "address", 46 | "name": "previousOwner", 47 | "type": "address" 48 | }, 49 | { 50 | "indexed": true, 51 | "internalType": "address", 52 | "name": "newOwner", 53 | "type": "address" 54 | } 55 | ], 56 | "name": "OwnershipTransferred", 57 | "type": "event" 58 | }, 59 | { 60 | "anonymous": false, 61 | "inputs": [ 62 | { 63 | "indexed": false, 64 | "internalType": "uint256", 65 | "name": "tranche", 66 | "type": "uint256" 67 | }, 68 | { 69 | "indexed": false, 70 | "internalType": "bytes32", 71 | "name": "merkleRoot", 72 | "type": "bytes32" 73 | }, 74 | { 75 | "indexed": false, 76 | "internalType": "uint256", 77 | "name": "totalAmount", 78 | "type": "uint256" 79 | } 80 | ], 81 | "name": "TrancheAdded", 82 | "type": "event" 83 | }, 84 | { 85 | "anonymous": false, 86 | "inputs": [ 87 | { 88 | "indexed": false, 89 | "internalType": "uint256", 90 | "name": "tranche", 91 | "type": "uint256" 92 | } 93 | ], 94 | "name": "TrancheExpired", 95 | "type": "event" 96 | }, 97 | { 98 | "inputs": [ 99 | { 100 | "internalType": "uint256", 101 | "name": "", 102 | "type": "uint256" 103 | }, 104 | { 105 | "internalType": "address", 106 | "name": "", 107 | "type": "address" 108 | } 109 | ], 110 | "name": "claimed", 111 | "outputs": [ 112 | { 113 | "internalType": "bool", 114 | "name": "", 115 | "type": "bool" 116 | } 117 | ], 118 | "stateMutability": "view", 119 | "type": "function", 120 | "constant": true 121 | }, 122 | { 123 | "inputs": [ 124 | { 125 | "internalType": "uint256", 126 | "name": "", 127 | "type": "uint256" 128 | } 129 | ], 130 | "name": "merkleRoots", 131 | "outputs": [ 132 | { 133 | "internalType": "bytes32", 134 | "name": "", 135 | "type": "bytes32" 136 | } 137 | ], 138 | "stateMutability": "view", 139 | "type": "function", 140 | "constant": true 141 | }, 142 | { 143 | "inputs": [], 144 | "name": "owner", 145 | "outputs": [ 146 | { 147 | "internalType": "address", 148 | "name": "", 149 | "type": "address" 150 | } 151 | ], 152 | "stateMutability": "view", 153 | "type": "function", 154 | "constant": true 155 | }, 156 | { 157 | "inputs": [], 158 | "name": "renounceOwnership", 159 | "outputs": [], 160 | "stateMutability": "nonpayable", 161 | "type": "function" 162 | }, 163 | { 164 | "inputs": [], 165 | "name": "token", 166 | "outputs": [ 167 | { 168 | "internalType": "contract IERC20", 169 | "name": "", 170 | "type": "address" 171 | } 172 | ], 173 | "stateMutability": "view", 174 | "type": "function", 175 | "constant": true 176 | }, 177 | { 178 | "inputs": [], 179 | "name": "tranches", 180 | "outputs": [ 181 | { 182 | "internalType": "uint256", 183 | "name": "", 184 | "type": "uint256" 185 | } 186 | ], 187 | "stateMutability": "view", 188 | "type": "function", 189 | "constant": true 190 | }, 191 | { 192 | "inputs": [ 193 | { 194 | "internalType": "address", 195 | "name": "newOwner", 196 | "type": "address" 197 | } 198 | ], 199 | "name": "transferOwnership", 200 | "outputs": [], 201 | "stateMutability": "nonpayable", 202 | "type": "function" 203 | }, 204 | { 205 | "inputs": [ 206 | { 207 | "internalType": "bytes32", 208 | "name": "_merkleRoot", 209 | "type": "bytes32" 210 | }, 211 | { 212 | "internalType": "uint256", 213 | "name": "_totalAllocation", 214 | "type": "uint256" 215 | } 216 | ], 217 | "name": "seedNewAllocations", 218 | "outputs": [ 219 | { 220 | "internalType": "uint256", 221 | "name": "trancheId", 222 | "type": "uint256" 223 | } 224 | ], 225 | "stateMutability": "nonpayable", 226 | "type": "function" 227 | }, 228 | { 229 | "inputs": [ 230 | { 231 | "internalType": "uint256", 232 | "name": "_trancheId", 233 | "type": "uint256" 234 | } 235 | ], 236 | "name": "expireTranche", 237 | "outputs": [], 238 | "stateMutability": "nonpayable", 239 | "type": "function" 240 | }, 241 | { 242 | "inputs": [ 243 | { 244 | "internalType": "address", 245 | "name": "user", 246 | "type": "address" 247 | }, 248 | { 249 | "internalType": "uint256", 250 | "name": "_tranche", 251 | "type": "uint256" 252 | }, 253 | { 254 | "internalType": "uint256", 255 | "name": "_balance", 256 | "type": "uint256" 257 | }, 258 | { 259 | "internalType": "bytes32[]", 260 | "name": "_merkleProof", 261 | "type": "bytes32[]" 262 | } 263 | ], 264 | "name": "claim", 265 | "outputs": [], 266 | "stateMutability": "nonpayable", 267 | "type": "function" 268 | }, 269 | { 270 | "inputs": [ 271 | { 272 | "internalType": "address", 273 | "name": "user", 274 | "type": "address" 275 | }, 276 | { 277 | "internalType": "uint256[]", 278 | "name": "_tranches", 279 | "type": "uint256[]" 280 | }, 281 | { 282 | "internalType": "uint256[]", 283 | "name": "_balances", 284 | "type": "uint256[]" 285 | }, 286 | { 287 | "internalType": "bytes32[][]", 288 | "name": "_merkleProofs", 289 | "type": "bytes32[][]" 290 | } 291 | ], 292 | "name": "multiClaim", 293 | "outputs": [], 294 | "stateMutability": "nonpayable", 295 | "type": "function" 296 | }, 297 | { 298 | "inputs": [ 299 | { 300 | "internalType": "address", 301 | "name": "user", 302 | "type": "address" 303 | }, 304 | { 305 | "internalType": "uint256", 306 | "name": "_tranche", 307 | "type": "uint256" 308 | }, 309 | { 310 | "internalType": "uint256", 311 | "name": "_balance", 312 | "type": "uint256" 313 | }, 314 | { 315 | "internalType": "bytes32[]", 316 | "name": "_merkleProof", 317 | "type": "bytes32[]" 318 | } 319 | ], 320 | "name": "verifyClaim", 321 | "outputs": [ 322 | { 323 | "internalType": "bool", 324 | "name": "valid", 325 | "type": "bool" 326 | } 327 | ], 328 | "stateMutability": "view", 329 | "type": "function", 330 | "constant": true 331 | }, 332 | { 333 | "inputs": [ 334 | { 335 | "internalType": "uint256", 336 | "name": "_amount", 337 | "type": "uint256" 338 | } 339 | ], 340 | "name": "withdrawETH", 341 | "outputs": [], 342 | "stateMutability": "nonpayable", 343 | "type": "function" 344 | }, 345 | { 346 | "inputs": [ 347 | { 348 | "internalType": "uint256", 349 | "name": "_amount", 350 | "type": "uint256" 351 | } 352 | ], 353 | "name": "withdrawToken", 354 | "outputs": [], 355 | "stateMutability": "nonpayable", 356 | "type": "function" 357 | }, 358 | { 359 | "inputs": [ 360 | { 361 | "internalType": "address", 362 | "name": "_token", 363 | "type": "address" 364 | }, 365 | { 366 | "internalType": "uint256", 367 | "name": "_amount", 368 | "type": "uint256" 369 | } 370 | ], 371 | "name": "withdrawERC20", 372 | "outputs": [], 373 | "stateMutability": "nonpayable", 374 | "type": "function" 375 | } 376 | ] 377 | } -------------------------------------------------------------------------------- /client/src/assets/scss/style-landing-page.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @font-face { 3 | font-family: 'Muli'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url(../fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFnOkEk30e0.ttf) format('truetype'); 7 | } 8 | @font-face { 9 | font-family: 'Muli'; 10 | font-style: normal; 11 | font-weight: 600; 12 | src: url(../fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFkQl0k30e0.ttf) format('truetype'); 13 | } 14 | @font-face { 15 | font-family: 'Muli'; 16 | font-style: normal; 17 | font-weight: 700; 18 | src: url(../fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFkpl0k30e0.ttf) format('truetype'); 19 | } 20 | @font-face { 21 | font-family: 'Muli'; 22 | font-style: normal; 23 | font-weight: 800; 24 | src: url(../fonts/7Aulp_0qiz-aVz7u3PJLcUMYOFlOl0k30e0.ttf) format('truetype'); 25 | } 26 | 27 | body { 28 | background-color: rgb(231, 85, 107); 29 | font-family: Muli, sans-serif !important; 30 | font-weight: 400; 31 | line-height: 1.6em; 32 | } 33 | html, 34 | body { 35 | height: 100%; 36 | } 37 | 38 | .navbar-menu { 39 | position: fixed !important; 40 | left: 0; 41 | top: 0; 42 | width: 100%; 43 | height: auto; 44 | border-radius: 0; 45 | border-bottom: 1px solid transparent; 46 | z-index: 99999; 47 | padding-top: 15px; 48 | padding-bottom: 15px; 49 | background-color: #fff; 50 | -webkit-transition: 0.3s; 51 | -o-transition: 0.3s; 52 | transition: 0.3s; 53 | background: 0 0; 54 | 55 | .menu-landing-page { 56 | margin: 0 auto; 57 | } 58 | .nav-item { 59 | background: 0 0; 60 | font-weight: 600; 61 | font-size: 1.2em; 62 | padding: 5px 15px; 63 | a { 64 | color: #fff !important; 65 | &:hover { 66 | color: #1ea9fe !important; 67 | } 68 | } 69 | } 70 | .style-logo { 71 | width: 20vw; 72 | } 73 | } 74 | 75 | .mainmenu-area { 76 | background-color: #003073; 77 | position: fixed; 78 | left: 0; 79 | top: 0; 80 | width: 100%; 81 | height: auto; 82 | border-radius: 0; 83 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 84 | z-index: 99999; 85 | -webkit-transition: 0.3s; 86 | -o-transition: 0.3s; 87 | transition: 0.3s; 88 | .style-logo { 89 | width: 15vw; 90 | } 91 | } 92 | 93 | .header-area { 94 | // background: url(../images/header-bg-3.jpg) no-repeat scroll center left/auto 100%; 95 | background-size: cover; 96 | z-index: 1; 97 | position: relative; 98 | height: 100%; 99 | width: 100%; 100 | padding: 120px 0; 101 | &:before { 102 | content: ''; 103 | position: absolute; 104 | left: 0; 105 | top: 0; 106 | width: 100%; 107 | height: 100%; 108 | z-index: -1; 109 | } 110 | color: #fff; 111 | 112 | .vcenter { 113 | .row-middle { 114 | font-family: Muli, sans-serif !important; 115 | display: -webkit-box; 116 | display: -ms-flexbox; 117 | display: flex; 118 | -webkit-box-align: center; 119 | -ms-flex-align: center; 120 | align-items: center; 121 | -ms-flex-wrap: wrap; 122 | flex-wrap: wrap; 123 | text-align: start; 124 | } 125 | .headline { 126 | color: #fff; 127 | font-size: 2.8em; 128 | font-weight: 800; 129 | margin-bottom: 0.75em; 130 | } 131 | .home-mobile { 132 | width: 36vw; 133 | } 134 | [class|='space'] { 135 | display: block; 136 | width: 100%; 137 | overflow: hidden; 138 | } 139 | } 140 | } 141 | 142 | .space-30 { 143 | height: 30px; 144 | } 145 | .space-60 { 146 | height: 60px; 147 | } 148 | .space-50 { 149 | height: 50px; 150 | } 151 | 152 | .left-header-area { 153 | .btn-icon { 154 | padding: 1.2em 2.5em; 155 | background-color: #22233b; 156 | color: #fff; 157 | display: inline-block; 158 | border-radius: 5px; 159 | margin-right: 10px; 160 | -webkit-box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.4); 161 | box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.4); 162 | &:hover { 163 | text-decoration: none; 164 | background-color: rgba(2, 1, 2, 0.88); 165 | box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.4); 166 | color: #fff; 167 | } 168 | .btn-content { 169 | position: relative; 170 | padding-left: 55px; 171 | display: block; 172 | } 173 | .icon { 174 | display: block; 175 | position: absolute; 176 | left: 0; 177 | bottom: 5px; 178 | font-size: 1.8em; 179 | width: 50px; 180 | img { 181 | width: 100%; 182 | } 183 | } 184 | strong { 185 | display: block; 186 | font-size: 1.1em; 187 | } 188 | } 189 | } 190 | 191 | .navbar-toggler { 192 | &:focus { 193 | outline: 0; 194 | } 195 | border: none !important; 196 | } 197 | .navbar-toggler-icon { 198 | background-image: url('../images/menu.svg') !important; 199 | } 200 | 201 | .section-padding-top { 202 | padding-top: 120px; 203 | } 204 | 205 | .text-center { 206 | text-align: center; 207 | } 208 | 209 | .feature-box .feature-icon { 210 | width: 100%; 211 | height: 80px; 212 | background-repeat: no-repeat; 213 | background-size: auto 100%; 214 | background-position: center center; 215 | display: inline-block; 216 | font-size: 25px; 217 | line-height: 70px; 218 | color: #fff; 219 | margin-bottom: 30px; 220 | } 221 | 222 | .feature-box { 223 | cursor: pointer; 224 | text-align: center; 225 | h3 { 226 | margin: 0 0 15px; 227 | font-weight: 700; 228 | line-height: 1.4em; 229 | color: #22233b; 230 | } 231 | } 232 | 233 | .feature-area { 234 | color: #56576e !important; 235 | background-color: #fff; 236 | .page-title { 237 | font-weight: 700; 238 | font-size: 36px; 239 | h2 { 240 | margin: 0 0 15px; 241 | font-weight: 700; 242 | line-height: 1.4em; 243 | color: #22233b; 244 | } 245 | } 246 | .service-box-two { 247 | text-align: center; 248 | padding: 40px 30px; 249 | h3 { 250 | margin: 0 0 15px; 251 | font-weight: 700; 252 | line-height: 1.4em; 253 | color: #22233b; 254 | font-size: 1.5em; 255 | } 256 | .service-icon { 257 | width: 60px; 258 | height: 60px; 259 | line-height: 50px; 260 | border-radius: 100%; 261 | display: inline-block; 262 | color: #fff; 263 | background-color: #2d5baf; 264 | margin-top: 15px; 265 | text-align: center; 266 | font-size: 20px; 267 | } 268 | } 269 | .service-box-two, 270 | .service-box { 271 | padding: 60px 30px; 272 | margin: 0 0 30px; 273 | border-radius: 6px; 274 | // -webkit-box-shadow: 0 0 30px -12px #c9c9c9; 275 | // box-shadow: 0 0 30px -12px #c9c9c9; 276 | -webkit-transform: translateY(0); 277 | -ms-transform: translateY(0); 278 | transform: translateY(0); 279 | -webkit-transition: 0.3s; 280 | -o-transition: 0.3s; 281 | transition: 0.3s; 282 | } 283 | .service-box-two:hover, 284 | .service-box:hover { 285 | -webkit-box-shadow: 0 0 30px -5px #c9c9c9; 286 | box-shadow: 0 0 30px -5px #c9c9c9; 287 | -webkit-transform: translateY(-5px); 288 | -ms-transform: translateY(-5px); 289 | transform: translateY(-5px); 290 | } 291 | } 292 | 293 | .footer { 294 | padding: 3em 0 1em 0; 295 | background-color: #fff; 296 | .icons { 297 | float: right; 298 | .icon { 299 | margin-left: 2em; 300 | & img:hover { 301 | transition: 0.3s; 302 | box-shadow: 0 0 30px -5px; 303 | transform: translateY(-5px); 304 | border-radius: 100%; 305 | } 306 | } 307 | } 308 | .footer-copyright { 309 | color: dimgray; 310 | text-align: start; 311 | } 312 | .about-style { 313 | a { 314 | text-decoration: none; 315 | } 316 | } 317 | } 318 | 319 | .ant-back-top { 320 | right: 50px; 321 | bottom: 85px; 322 | } 323 | 324 | .a-opacity-1 { 325 | opacity: 1; 326 | transform: translate(0); 327 | transition: opacity 1s cubic-bezier(0.39, 0.575, 0.565, 1), 328 | transform 1s cubic-bezier(0.39, 0.575, 0.565, 1); 329 | } 330 | 331 | @media (max-width: 767px) { 332 | .hidden-xs { 333 | display: none !important; 334 | } 335 | .header-area .vcenter .headline { 336 | font-size: 1.8em; 337 | } 338 | .btn-icon { 339 | padding: 1em 1.5em; 340 | strong { 341 | display: block; 342 | font-size: 1em; 343 | } 344 | } 345 | 346 | .header-area .vcenter .home-mobile { 347 | margin-top: 3em; 348 | width: 60vw; 349 | } 350 | .header-area .vcenter .row-middle { 351 | text-align: center; 352 | } 353 | 354 | .footer { 355 | .icons { 356 | float: none; 357 | text-align: center; 358 | .icon { 359 | margin-left: 2em; 360 | &:first-child { 361 | margin-left: 0; 362 | } 363 | } 364 | } 365 | .footer-copyright { 366 | text-align: center; 367 | } 368 | .name-team { 369 | margin-bottom: 1em; 370 | } 371 | } 372 | .navbar-menu { 373 | .style-logo { 374 | width: 50vw; 375 | } 376 | } 377 | 378 | .mainmenu-area { 379 | .style-logo { 380 | width: 40vw; 381 | } 382 | } 383 | } 384 | 385 | @media (min-width: 1200px) { 386 | .container, 387 | .container-lg, 388 | .container-md, 389 | .container-sm, 390 | .container-xl { 391 | max-width: 1170px; 392 | } 393 | } 394 | 395 | .animated { 396 | -webkit-animation-duration: 1s; 397 | animation-duration: 1s; 398 | -webkit-animation-fill-mode: both; 399 | animation-fill-mode: both; 400 | } 401 | .animated.infinite { 402 | -webkit-animation-iteration-count: infinite; 403 | animation-iteration-count: infinite; 404 | } 405 | .animated.hinge { 406 | -webkit-animation-duration: 2s; 407 | animation-duration: 2s; 408 | } 409 | 410 | @-webkit-keyframes fadeInUp { 411 | 0% { 412 | opacity: 0; 413 | -webkit-transform: translateY(20px); 414 | transform: translateY(20px); 415 | } 416 | 100% { 417 | opacity: 1; 418 | -webkit-transform: translateY(0); 419 | transform: translateY(0); 420 | } 421 | } 422 | @keyframes fadeInUp { 423 | 0% { 424 | opacity: 0; 425 | -webkit-transform: translateY(20px); 426 | transform: translateY(20px); 427 | } 428 | 100% { 429 | opacity: 1; 430 | -webkit-transform: translateY(0); 431 | transform: translateY(0); 432 | } 433 | } 434 | .fadeInUp { 435 | -webkit-animation-name: fadeInUp; 436 | animation-name: fadeInUp; 437 | } 438 | 439 | @-webkit-keyframes fadeIn { 440 | 0% { 441 | opacity: 0; 442 | } 443 | 100% { 444 | opacity: 1; 445 | } 446 | } 447 | @keyframes fadeIn { 448 | 0% { 449 | opacity: 0; 450 | } 451 | 100% { 452 | opacity: 1; 453 | } 454 | } 455 | .fadeIn { 456 | -webkit-animation-name: fadeIn; 457 | animation-name: fadeIn; 458 | } 459 | 460 | .claimableBalance { 461 | color: white; 462 | } 463 | -------------------------------------------------------------------------------- /client/src/contracts/IERC20.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "IERC20", 3 | "abi": [ 4 | { 5 | "anonymous": false, 6 | "inputs": [ 7 | { 8 | "indexed": true, 9 | "internalType": "address", 10 | "name": "owner", 11 | "type": "address" 12 | }, 13 | { 14 | "indexed": true, 15 | "internalType": "address", 16 | "name": "spender", 17 | "type": "address" 18 | }, 19 | { 20 | "indexed": false, 21 | "internalType": "uint256", 22 | "name": "value", 23 | "type": "uint256" 24 | } 25 | ], 26 | "name": "Approval", 27 | "type": "event" 28 | }, 29 | { 30 | "anonymous": false, 31 | "inputs": [ 32 | { 33 | "indexed": true, 34 | "internalType": "address", 35 | "name": "from", 36 | "type": "address" 37 | }, 38 | { 39 | "indexed": true, 40 | "internalType": "address", 41 | "name": "to", 42 | "type": "address" 43 | }, 44 | { 45 | "indexed": false, 46 | "internalType": "uint256", 47 | "name": "value", 48 | "type": "uint256" 49 | } 50 | ], 51 | "name": "Transfer", 52 | "type": "event" 53 | }, 54 | { 55 | "inputs": [], 56 | "name": "totalSupply", 57 | "outputs": [ 58 | { 59 | "internalType": "uint256", 60 | "name": "", 61 | "type": "uint256" 62 | } 63 | ], 64 | "stateMutability": "view", 65 | "type": "function" 66 | }, 67 | { 68 | "inputs": [ 69 | { 70 | "internalType": "address", 71 | "name": "account", 72 | "type": "address" 73 | } 74 | ], 75 | "name": "balanceOf", 76 | "outputs": [ 77 | { 78 | "internalType": "uint256", 79 | "name": "", 80 | "type": "uint256" 81 | } 82 | ], 83 | "stateMutability": "view", 84 | "type": "function" 85 | }, 86 | { 87 | "inputs": [ 88 | { 89 | "internalType": "address", 90 | "name": "recipient", 91 | "type": "address" 92 | }, 93 | { 94 | "internalType": "uint256", 95 | "name": "amount", 96 | "type": "uint256" 97 | } 98 | ], 99 | "name": "transfer", 100 | "outputs": [ 101 | { 102 | "internalType": "bool", 103 | "name": "", 104 | "type": "bool" 105 | } 106 | ], 107 | "stateMutability": "nonpayable", 108 | "type": "function" 109 | }, 110 | { 111 | "inputs": [ 112 | { 113 | "internalType": "address", 114 | "name": "owner", 115 | "type": "address" 116 | }, 117 | { 118 | "internalType": "address", 119 | "name": "spender", 120 | "type": "address" 121 | } 122 | ], 123 | "name": "allowance", 124 | "outputs": [ 125 | { 126 | "internalType": "uint256", 127 | "name": "", 128 | "type": "uint256" 129 | } 130 | ], 131 | "stateMutability": "view", 132 | "type": "function" 133 | }, 134 | { 135 | "inputs": [ 136 | { 137 | "internalType": "address", 138 | "name": "spender", 139 | "type": "address" 140 | }, 141 | { 142 | "internalType": "uint256", 143 | "name": "amount", 144 | "type": "uint256" 145 | } 146 | ], 147 | "name": "approve", 148 | "outputs": [ 149 | { 150 | "internalType": "bool", 151 | "name": "", 152 | "type": "bool" 153 | } 154 | ], 155 | "stateMutability": "nonpayable", 156 | "type": "function" 157 | }, 158 | { 159 | "inputs": [ 160 | { 161 | "internalType": "address", 162 | "name": "sender", 163 | "type": "address" 164 | }, 165 | { 166 | "internalType": "address", 167 | "name": "recipient", 168 | "type": "address" 169 | }, 170 | { 171 | "internalType": "uint256", 172 | "name": "amount", 173 | "type": "uint256" 174 | } 175 | ], 176 | "name": "transferFrom", 177 | "outputs": [ 178 | { 179 | "internalType": "bool", 180 | "name": "", 181 | "type": "bool" 182 | } 183 | ], 184 | "stateMutability": "nonpayable", 185 | "type": "function" 186 | } 187 | ], 188 | "metadata": "{\"compiler\":{\"version\":\"0.6.6+commit.6c089d02\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Interface of the ERC20 standard as defined in the EIP.\",\"methods\":{\"allowance(address,address)\":{\"details\":\"Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. * This value changes when {approve} or {transferFrom} are called.\"},\"approve(address,uint256)\":{\"details\":\"Sets `amount` as the allowance of `spender` over the caller's tokens. * Returns a boolean value indicating whether the operation succeeded. * IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * Emits an {Approval} event.\"},\"balanceOf(address)\":{\"details\":\"Returns the amount of tokens owned by `account`.\"},\"totalSupply()\":{\"details\":\"Returns the amount of tokens in existence.\"},\"transfer(address,uint256)\":{\"details\":\"Moves `amount` tokens from the caller's account to `recipient`. * Returns a boolean value indicating whether the operation succeeded. * Emits a {Transfer} event.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. * Returns a boolean value indicating whether the operation succeeded. * Emits a {Transfer} event.\"}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":\"IERC20\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5f02220344881ce43204ae4a6281145a67bc52c2bb1290a791857df3d19d78f5\",\"urls\":[\"bzz-raw://24427744bd3e6cb73c17010119af12a318289c0253a4d9acb8576c9fb3797b08\",\"dweb:/ipfs/QmTLDqpKRBuxGxRAmjgXt9AkXyACW3MtKzi7PYjm5iMfGC\"]}},\"version\":1}", 189 | "bytecode": "0x", 190 | "deployedBytecode": "0x", 191 | "immutableReferences": {}, 192 | "sourceMap": "", 193 | "deployedSourceMap": "", 194 | "source": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n", 195 | "sourcePath": "@openzeppelin/contracts/token/ERC20/IERC20.sol", 196 | "ast": { 197 | "absolutePath": "@openzeppelin/contracts/token/ERC20/IERC20.sol", 198 | "exportedSymbols": { 199 | "IERC20": [ 200 | 949 201 | ] 202 | }, 203 | "id": 950, 204 | "nodeType": "SourceUnit", 205 | "nodes": [ 206 | { 207 | "id": 873, 208 | "literals": [ 209 | "solidity", 210 | ">=", 211 | "0.6", 212 | ".0", 213 | "<", 214 | "0.8", 215 | ".0" 216 | ], 217 | "nodeType": "PragmaDirective", 218 | "src": "33:31:7" 219 | }, 220 | { 221 | "abstract": false, 222 | "baseContracts": [], 223 | "contractDependencies": [], 224 | "contractKind": "interface", 225 | "documentation": { 226 | "id": 874, 227 | "nodeType": "StructuredDocumentation", 228 | "src": "66:70:7", 229 | "text": "@dev Interface of the ERC20 standard as defined in the EIP." 230 | }, 231 | "fullyImplemented": false, 232 | "id": 949, 233 | "linearizedBaseContracts": [ 234 | 949 235 | ], 236 | "name": "IERC20", 237 | "nodeType": "ContractDefinition", 238 | "nodes": [ 239 | { 240 | "body": null, 241 | "documentation": { 242 | "id": 875, 243 | "nodeType": "StructuredDocumentation", 244 | "src": "160:66:7", 245 | "text": "@dev Returns the amount of tokens in existence." 246 | }, 247 | "functionSelector": "18160ddd", 248 | "id": 880, 249 | "implemented": false, 250 | "kind": "function", 251 | "modifiers": [], 252 | "name": "totalSupply", 253 | "nodeType": "FunctionDefinition", 254 | "overrides": null, 255 | "parameters": { 256 | "id": 876, 257 | "nodeType": "ParameterList", 258 | "parameters": [], 259 | "src": "251:2:7" 260 | }, 261 | "returnParameters": { 262 | "id": 879, 263 | "nodeType": "ParameterList", 264 | "parameters": [ 265 | { 266 | "constant": false, 267 | "id": 878, 268 | "mutability": "mutable", 269 | "name": "", 270 | "nodeType": "VariableDeclaration", 271 | "overrides": null, 272 | "scope": 880, 273 | "src": "277:7:7", 274 | "stateVariable": false, 275 | "storageLocation": "default", 276 | "typeDescriptions": { 277 | "typeIdentifier": "t_uint256", 278 | "typeString": "uint256" 279 | }, 280 | "typeName": { 281 | "id": 877, 282 | "name": "uint256", 283 | "nodeType": "ElementaryTypeName", 284 | "src": "277:7:7", 285 | "typeDescriptions": { 286 | "typeIdentifier": "t_uint256", 287 | "typeString": "uint256" 288 | } 289 | }, 290 | "value": null, 291 | "visibility": "internal" 292 | } 293 | ], 294 | "src": "276:9:7" 295 | }, 296 | "scope": 949, 297 | "src": "231:55:7", 298 | "stateMutability": "view", 299 | "virtual": false, 300 | "visibility": "external" 301 | }, 302 | { 303 | "body": null, 304 | "documentation": { 305 | "id": 881, 306 | "nodeType": "StructuredDocumentation", 307 | "src": "292:72:7", 308 | "text": "@dev Returns the amount of tokens owned by `account`." 309 | }, 310 | "functionSelector": "70a08231", 311 | "id": 888, 312 | "implemented": false, 313 | "kind": "function", 314 | "modifiers": [], 315 | "name": "balanceOf", 316 | "nodeType": "FunctionDefinition", 317 | "overrides": null, 318 | "parameters": { 319 | "id": 884, 320 | "nodeType": "ParameterList", 321 | "parameters": [ 322 | { 323 | "constant": false, 324 | "id": 883, 325 | "mutability": "mutable", 326 | "name": "account", 327 | "nodeType": "VariableDeclaration", 328 | "overrides": null, 329 | "scope": 888, 330 | "src": "388:15:7", 331 | "stateVariable": false, 332 | "storageLocation": "default", 333 | "typeDescriptions": { 334 | "typeIdentifier": "t_address", 335 | "typeString": "address" 336 | }, 337 | "typeName": { 338 | "id": 882, 339 | "name": "address", 340 | "nodeType": "ElementaryTypeName", 341 | "src": "388:7:7", 342 | "stateMutability": "nonpayable", 343 | "typeDescriptions": { 344 | "typeIdentifier": "t_address", 345 | "typeString": "address" 346 | } 347 | }, 348 | "value": null, 349 | "visibility": "internal" 350 | } 351 | ], 352 | "src": "387:17:7" 353 | }, 354 | "returnParameters": { 355 | "id": 887, 356 | "nodeType": "ParameterList", 357 | "parameters": [ 358 | { 359 | "constant": false, 360 | "id": 886, 361 | "mutability": "mutable", 362 | "name": "", 363 | "nodeType": "VariableDeclaration", 364 | "overrides": null, 365 | "scope": 888, 366 | "src": "428:7:7", 367 | "stateVariable": false, 368 | "storageLocation": "default", 369 | "typeDescriptions": { 370 | "typeIdentifier": "t_uint256", 371 | "typeString": "uint256" 372 | }, 373 | "typeName": { 374 | "id": 885, 375 | "name": "uint256", 376 | "nodeType": "ElementaryTypeName", 377 | "src": "428:7:7", 378 | "typeDescriptions": { 379 | "typeIdentifier": "t_uint256", 380 | "typeString": "uint256" 381 | } 382 | }, 383 | "value": null, 384 | "visibility": "internal" 385 | } 386 | ], 387 | "src": "427:9:7" 388 | }, 389 | "scope": 949, 390 | "src": "369:68:7", 391 | "stateMutability": "view", 392 | "virtual": false, 393 | "visibility": "external" 394 | }, 395 | { 396 | "body": null, 397 | "documentation": { 398 | "id": 889, 399 | "nodeType": "StructuredDocumentation", 400 | "src": "443:209:7", 401 | "text": "@dev Moves `amount` tokens from the caller's account to `recipient`.\n * Returns a boolean value indicating whether the operation succeeded.\n * Emits a {Transfer} event." 402 | }, 403 | "functionSelector": "a9059cbb", 404 | "id": 898, 405 | "implemented": false, 406 | "kind": "function", 407 | "modifiers": [], 408 | "name": "transfer", 409 | "nodeType": "FunctionDefinition", 410 | "overrides": null, 411 | "parameters": { 412 | "id": 894, 413 | "nodeType": "ParameterList", 414 | "parameters": [ 415 | { 416 | "constant": false, 417 | "id": 891, 418 | "mutability": "mutable", 419 | "name": "recipient", 420 | "nodeType": "VariableDeclaration", 421 | "overrides": null, 422 | "scope": 898, 423 | "src": "675:17:7", 424 | "stateVariable": false, 425 | "storageLocation": "default", 426 | "typeDescriptions": { 427 | "typeIdentifier": "t_address", 428 | "typeString": "address" 429 | }, 430 | "typeName": { 431 | "id": 890, 432 | "name": "address", 433 | "nodeType": "ElementaryTypeName", 434 | "src": "675:7:7", 435 | "stateMutability": "nonpayable", 436 | "typeDescriptions": { 437 | "typeIdentifier": "t_address", 438 | "typeString": "address" 439 | } 440 | }, 441 | "value": null, 442 | "visibility": "internal" 443 | }, 444 | { 445 | "constant": false, 446 | "id": 893, 447 | "mutability": "mutable", 448 | "name": "amount", 449 | "nodeType": "VariableDeclaration", 450 | "overrides": null, 451 | "scope": 898, 452 | "src": "694:14:7", 453 | "stateVariable": false, 454 | "storageLocation": "default", 455 | "typeDescriptions": { 456 | "typeIdentifier": "t_uint256", 457 | "typeString": "uint256" 458 | }, 459 | "typeName": { 460 | "id": 892, 461 | "name": "uint256", 462 | "nodeType": "ElementaryTypeName", 463 | "src": "694:7:7", 464 | "typeDescriptions": { 465 | "typeIdentifier": "t_uint256", 466 | "typeString": "uint256" 467 | } 468 | }, 469 | "value": null, 470 | "visibility": "internal" 471 | } 472 | ], 473 | "src": "674:35:7" 474 | }, 475 | "returnParameters": { 476 | "id": 897, 477 | "nodeType": "ParameterList", 478 | "parameters": [ 479 | { 480 | "constant": false, 481 | "id": 896, 482 | "mutability": "mutable", 483 | "name": "", 484 | "nodeType": "VariableDeclaration", 485 | "overrides": null, 486 | "scope": 898, 487 | "src": "728:4:7", 488 | "stateVariable": false, 489 | "storageLocation": "default", 490 | "typeDescriptions": { 491 | "typeIdentifier": "t_bool", 492 | "typeString": "bool" 493 | }, 494 | "typeName": { 495 | "id": 895, 496 | "name": "bool", 497 | "nodeType": "ElementaryTypeName", 498 | "src": "728:4:7", 499 | "typeDescriptions": { 500 | "typeIdentifier": "t_bool", 501 | "typeString": "bool" 502 | } 503 | }, 504 | "value": null, 505 | "visibility": "internal" 506 | } 507 | ], 508 | "src": "727:6:7" 509 | }, 510 | "scope": 949, 511 | "src": "657:77:7", 512 | "stateMutability": "nonpayable", 513 | "virtual": false, 514 | "visibility": "external" 515 | }, 516 | { 517 | "body": null, 518 | "documentation": { 519 | "id": 899, 520 | "nodeType": "StructuredDocumentation", 521 | "src": "740:264:7", 522 | "text": "@dev Returns the remaining number of tokens that `spender` will be\nallowed to spend on behalf of `owner` through {transferFrom}. This is\nzero by default.\n * This value changes when {approve} or {transferFrom} are called." 523 | }, 524 | "functionSelector": "dd62ed3e", 525 | "id": 908, 526 | "implemented": false, 527 | "kind": "function", 528 | "modifiers": [], 529 | "name": "allowance", 530 | "nodeType": "FunctionDefinition", 531 | "overrides": null, 532 | "parameters": { 533 | "id": 904, 534 | "nodeType": "ParameterList", 535 | "parameters": [ 536 | { 537 | "constant": false, 538 | "id": 901, 539 | "mutability": "mutable", 540 | "name": "owner", 541 | "nodeType": "VariableDeclaration", 542 | "overrides": null, 543 | "scope": 908, 544 | "src": "1028:13:7", 545 | "stateVariable": false, 546 | "storageLocation": "default", 547 | "typeDescriptions": { 548 | "typeIdentifier": "t_address", 549 | "typeString": "address" 550 | }, 551 | "typeName": { 552 | "id": 900, 553 | "name": "address", 554 | "nodeType": "ElementaryTypeName", 555 | "src": "1028:7:7", 556 | "stateMutability": "nonpayable", 557 | "typeDescriptions": { 558 | "typeIdentifier": "t_address", 559 | "typeString": "address" 560 | } 561 | }, 562 | "value": null, 563 | "visibility": "internal" 564 | }, 565 | { 566 | "constant": false, 567 | "id": 903, 568 | "mutability": "mutable", 569 | "name": "spender", 570 | "nodeType": "VariableDeclaration", 571 | "overrides": null, 572 | "scope": 908, 573 | "src": "1043:15:7", 574 | "stateVariable": false, 575 | "storageLocation": "default", 576 | "typeDescriptions": { 577 | "typeIdentifier": "t_address", 578 | "typeString": "address" 579 | }, 580 | "typeName": { 581 | "id": 902, 582 | "name": "address", 583 | "nodeType": "ElementaryTypeName", 584 | "src": "1043:7:7", 585 | "stateMutability": "nonpayable", 586 | "typeDescriptions": { 587 | "typeIdentifier": "t_address", 588 | "typeString": "address" 589 | } 590 | }, 591 | "value": null, 592 | "visibility": "internal" 593 | } 594 | ], 595 | "src": "1027:32:7" 596 | }, 597 | "returnParameters": { 598 | "id": 907, 599 | "nodeType": "ParameterList", 600 | "parameters": [ 601 | { 602 | "constant": false, 603 | "id": 906, 604 | "mutability": "mutable", 605 | "name": "", 606 | "nodeType": "VariableDeclaration", 607 | "overrides": null, 608 | "scope": 908, 609 | "src": "1083:7:7", 610 | "stateVariable": false, 611 | "storageLocation": "default", 612 | "typeDescriptions": { 613 | "typeIdentifier": "t_uint256", 614 | "typeString": "uint256" 615 | }, 616 | "typeName": { 617 | "id": 905, 618 | "name": "uint256", 619 | "nodeType": "ElementaryTypeName", 620 | "src": "1083:7:7", 621 | "typeDescriptions": { 622 | "typeIdentifier": "t_uint256", 623 | "typeString": "uint256" 624 | } 625 | }, 626 | "value": null, 627 | "visibility": "internal" 628 | } 629 | ], 630 | "src": "1082:9:7" 631 | }, 632 | "scope": 949, 633 | "src": "1009:83:7", 634 | "stateMutability": "view", 635 | "virtual": false, 636 | "visibility": "external" 637 | }, 638 | { 639 | "body": null, 640 | "documentation": { 641 | "id": 909, 642 | "nodeType": "StructuredDocumentation", 643 | "src": "1098:642:7", 644 | "text": "@dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n * Returns a boolean value indicating whether the operation succeeded.\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\nthat someone may use both the old and the new allowance by unfortunate\ntransaction ordering. One possible solution to mitigate this race\ncondition is to first reduce the spender's allowance to 0 and set the\ndesired value afterwards:\nhttps://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n * Emits an {Approval} event." 645 | }, 646 | "functionSelector": "095ea7b3", 647 | "id": 918, 648 | "implemented": false, 649 | "kind": "function", 650 | "modifiers": [], 651 | "name": "approve", 652 | "nodeType": "FunctionDefinition", 653 | "overrides": null, 654 | "parameters": { 655 | "id": 914, 656 | "nodeType": "ParameterList", 657 | "parameters": [ 658 | { 659 | "constant": false, 660 | "id": 911, 661 | "mutability": "mutable", 662 | "name": "spender", 663 | "nodeType": "VariableDeclaration", 664 | "overrides": null, 665 | "scope": 918, 666 | "src": "1762:15:7", 667 | "stateVariable": false, 668 | "storageLocation": "default", 669 | "typeDescriptions": { 670 | "typeIdentifier": "t_address", 671 | "typeString": "address" 672 | }, 673 | "typeName": { 674 | "id": 910, 675 | "name": "address", 676 | "nodeType": "ElementaryTypeName", 677 | "src": "1762:7:7", 678 | "stateMutability": "nonpayable", 679 | "typeDescriptions": { 680 | "typeIdentifier": "t_address", 681 | "typeString": "address" 682 | } 683 | }, 684 | "value": null, 685 | "visibility": "internal" 686 | }, 687 | { 688 | "constant": false, 689 | "id": 913, 690 | "mutability": "mutable", 691 | "name": "amount", 692 | "nodeType": "VariableDeclaration", 693 | "overrides": null, 694 | "scope": 918, 695 | "src": "1779:14:7", 696 | "stateVariable": false, 697 | "storageLocation": "default", 698 | "typeDescriptions": { 699 | "typeIdentifier": "t_uint256", 700 | "typeString": "uint256" 701 | }, 702 | "typeName": { 703 | "id": 912, 704 | "name": "uint256", 705 | "nodeType": "ElementaryTypeName", 706 | "src": "1779:7:7", 707 | "typeDescriptions": { 708 | "typeIdentifier": "t_uint256", 709 | "typeString": "uint256" 710 | } 711 | }, 712 | "value": null, 713 | "visibility": "internal" 714 | } 715 | ], 716 | "src": "1761:33:7" 717 | }, 718 | "returnParameters": { 719 | "id": 917, 720 | "nodeType": "ParameterList", 721 | "parameters": [ 722 | { 723 | "constant": false, 724 | "id": 916, 725 | "mutability": "mutable", 726 | "name": "", 727 | "nodeType": "VariableDeclaration", 728 | "overrides": null, 729 | "scope": 918, 730 | "src": "1813:4:7", 731 | "stateVariable": false, 732 | "storageLocation": "default", 733 | "typeDescriptions": { 734 | "typeIdentifier": "t_bool", 735 | "typeString": "bool" 736 | }, 737 | "typeName": { 738 | "id": 915, 739 | "name": "bool", 740 | "nodeType": "ElementaryTypeName", 741 | "src": "1813:4:7", 742 | "typeDescriptions": { 743 | "typeIdentifier": "t_bool", 744 | "typeString": "bool" 745 | } 746 | }, 747 | "value": null, 748 | "visibility": "internal" 749 | } 750 | ], 751 | "src": "1812:6:7" 752 | }, 753 | "scope": 949, 754 | "src": "1745:74:7", 755 | "stateMutability": "nonpayable", 756 | "virtual": false, 757 | "visibility": "external" 758 | }, 759 | { 760 | "body": null, 761 | "documentation": { 762 | "id": 919, 763 | "nodeType": "StructuredDocumentation", 764 | "src": "1825:296:7", 765 | "text": "@dev Moves `amount` tokens from `sender` to `recipient` using the\nallowance mechanism. `amount` is then deducted from the caller's\nallowance.\n * Returns a boolean value indicating whether the operation succeeded.\n * Emits a {Transfer} event." 766 | }, 767 | "functionSelector": "23b872dd", 768 | "id": 930, 769 | "implemented": false, 770 | "kind": "function", 771 | "modifiers": [], 772 | "name": "transferFrom", 773 | "nodeType": "FunctionDefinition", 774 | "overrides": null, 775 | "parameters": { 776 | "id": 926, 777 | "nodeType": "ParameterList", 778 | "parameters": [ 779 | { 780 | "constant": false, 781 | "id": 921, 782 | "mutability": "mutable", 783 | "name": "sender", 784 | "nodeType": "VariableDeclaration", 785 | "overrides": null, 786 | "scope": 930, 787 | "src": "2148:14:7", 788 | "stateVariable": false, 789 | "storageLocation": "default", 790 | "typeDescriptions": { 791 | "typeIdentifier": "t_address", 792 | "typeString": "address" 793 | }, 794 | "typeName": { 795 | "id": 920, 796 | "name": "address", 797 | "nodeType": "ElementaryTypeName", 798 | "src": "2148:7:7", 799 | "stateMutability": "nonpayable", 800 | "typeDescriptions": { 801 | "typeIdentifier": "t_address", 802 | "typeString": "address" 803 | } 804 | }, 805 | "value": null, 806 | "visibility": "internal" 807 | }, 808 | { 809 | "constant": false, 810 | "id": 923, 811 | "mutability": "mutable", 812 | "name": "recipient", 813 | "nodeType": "VariableDeclaration", 814 | "overrides": null, 815 | "scope": 930, 816 | "src": "2164:17:7", 817 | "stateVariable": false, 818 | "storageLocation": "default", 819 | "typeDescriptions": { 820 | "typeIdentifier": "t_address", 821 | "typeString": "address" 822 | }, 823 | "typeName": { 824 | "id": 922, 825 | "name": "address", 826 | "nodeType": "ElementaryTypeName", 827 | "src": "2164:7:7", 828 | "stateMutability": "nonpayable", 829 | "typeDescriptions": { 830 | "typeIdentifier": "t_address", 831 | "typeString": "address" 832 | } 833 | }, 834 | "value": null, 835 | "visibility": "internal" 836 | }, 837 | { 838 | "constant": false, 839 | "id": 925, 840 | "mutability": "mutable", 841 | "name": "amount", 842 | "nodeType": "VariableDeclaration", 843 | "overrides": null, 844 | "scope": 930, 845 | "src": "2183:14:7", 846 | "stateVariable": false, 847 | "storageLocation": "default", 848 | "typeDescriptions": { 849 | "typeIdentifier": "t_uint256", 850 | "typeString": "uint256" 851 | }, 852 | "typeName": { 853 | "id": 924, 854 | "name": "uint256", 855 | "nodeType": "ElementaryTypeName", 856 | "src": "2183:7:7", 857 | "typeDescriptions": { 858 | "typeIdentifier": "t_uint256", 859 | "typeString": "uint256" 860 | } 861 | }, 862 | "value": null, 863 | "visibility": "internal" 864 | } 865 | ], 866 | "src": "2147:51:7" 867 | }, 868 | "returnParameters": { 869 | "id": 929, 870 | "nodeType": "ParameterList", 871 | "parameters": [ 872 | { 873 | "constant": false, 874 | "id": 928, 875 | "mutability": "mutable", 876 | "name": "", 877 | "nodeType": "VariableDeclaration", 878 | "overrides": null, 879 | "scope": 930, 880 | "src": "2217:4:7", 881 | "stateVariable": false, 882 | "storageLocation": "default", 883 | "typeDescriptions": { 884 | "typeIdentifier": "t_bool", 885 | "typeString": "bool" 886 | }, 887 | "typeName": { 888 | "id": 927, 889 | "name": "bool", 890 | "nodeType": "ElementaryTypeName", 891 | "src": "2217:4:7", 892 | "typeDescriptions": { 893 | "typeIdentifier": "t_bool", 894 | "typeString": "bool" 895 | } 896 | }, 897 | "value": null, 898 | "visibility": "internal" 899 | } 900 | ], 901 | "src": "2216:6:7" 902 | }, 903 | "scope": 949, 904 | "src": "2126:97:7", 905 | "stateMutability": "nonpayable", 906 | "virtual": false, 907 | "visibility": "external" 908 | }, 909 | { 910 | "anonymous": false, 911 | "documentation": { 912 | "id": 931, 913 | "nodeType": "StructuredDocumentation", 914 | "src": "2229:158:7", 915 | "text": "@dev Emitted when `value` tokens are moved from one account (`from`) to\nanother (`to`).\n * Note that `value` may be zero." 916 | }, 917 | "id": 939, 918 | "name": "Transfer", 919 | "nodeType": "EventDefinition", 920 | "parameters": { 921 | "id": 938, 922 | "nodeType": "ParameterList", 923 | "parameters": [ 924 | { 925 | "constant": false, 926 | "id": 933, 927 | "indexed": true, 928 | "mutability": "mutable", 929 | "name": "from", 930 | "nodeType": "VariableDeclaration", 931 | "overrides": null, 932 | "scope": 939, 933 | "src": "2407:20:7", 934 | "stateVariable": false, 935 | "storageLocation": "default", 936 | "typeDescriptions": { 937 | "typeIdentifier": "t_address", 938 | "typeString": "address" 939 | }, 940 | "typeName": { 941 | "id": 932, 942 | "name": "address", 943 | "nodeType": "ElementaryTypeName", 944 | "src": "2407:7:7", 945 | "stateMutability": "nonpayable", 946 | "typeDescriptions": { 947 | "typeIdentifier": "t_address", 948 | "typeString": "address" 949 | } 950 | }, 951 | "value": null, 952 | "visibility": "internal" 953 | }, 954 | { 955 | "constant": false, 956 | "id": 935, 957 | "indexed": true, 958 | "mutability": "mutable", 959 | "name": "to", 960 | "nodeType": "VariableDeclaration", 961 | "overrides": null, 962 | "scope": 939, 963 | "src": "2429:18:7", 964 | "stateVariable": false, 965 | "storageLocation": "default", 966 | "typeDescriptions": { 967 | "typeIdentifier": "t_address", 968 | "typeString": "address" 969 | }, 970 | "typeName": { 971 | "id": 934, 972 | "name": "address", 973 | "nodeType": "ElementaryTypeName", 974 | "src": "2429:7:7", 975 | "stateMutability": "nonpayable", 976 | "typeDescriptions": { 977 | "typeIdentifier": "t_address", 978 | "typeString": "address" 979 | } 980 | }, 981 | "value": null, 982 | "visibility": "internal" 983 | }, 984 | { 985 | "constant": false, 986 | "id": 937, 987 | "indexed": false, 988 | "mutability": "mutable", 989 | "name": "value", 990 | "nodeType": "VariableDeclaration", 991 | "overrides": null, 992 | "scope": 939, 993 | "src": "2449:13:7", 994 | "stateVariable": false, 995 | "storageLocation": "default", 996 | "typeDescriptions": { 997 | "typeIdentifier": "t_uint256", 998 | "typeString": "uint256" 999 | }, 1000 | "typeName": { 1001 | "id": 936, 1002 | "name": "uint256", 1003 | "nodeType": "ElementaryTypeName", 1004 | "src": "2449:7:7", 1005 | "typeDescriptions": { 1006 | "typeIdentifier": "t_uint256", 1007 | "typeString": "uint256" 1008 | } 1009 | }, 1010 | "value": null, 1011 | "visibility": "internal" 1012 | } 1013 | ], 1014 | "src": "2406:57:7" 1015 | }, 1016 | "src": "2392:72:7" 1017 | }, 1018 | { 1019 | "anonymous": false, 1020 | "documentation": { 1021 | "id": 940, 1022 | "nodeType": "StructuredDocumentation", 1023 | "src": "2470:148:7", 1024 | "text": "@dev Emitted when the allowance of a `spender` for an `owner` is set by\na call to {approve}. `value` is the new allowance." 1025 | }, 1026 | "id": 948, 1027 | "name": "Approval", 1028 | "nodeType": "EventDefinition", 1029 | "parameters": { 1030 | "id": 947, 1031 | "nodeType": "ParameterList", 1032 | "parameters": [ 1033 | { 1034 | "constant": false, 1035 | "id": 942, 1036 | "indexed": true, 1037 | "mutability": "mutable", 1038 | "name": "owner", 1039 | "nodeType": "VariableDeclaration", 1040 | "overrides": null, 1041 | "scope": 948, 1042 | "src": "2638:21:7", 1043 | "stateVariable": false, 1044 | "storageLocation": "default", 1045 | "typeDescriptions": { 1046 | "typeIdentifier": "t_address", 1047 | "typeString": "address" 1048 | }, 1049 | "typeName": { 1050 | "id": 941, 1051 | "name": "address", 1052 | "nodeType": "ElementaryTypeName", 1053 | "src": "2638:7:7", 1054 | "stateMutability": "nonpayable", 1055 | "typeDescriptions": { 1056 | "typeIdentifier": "t_address", 1057 | "typeString": "address" 1058 | } 1059 | }, 1060 | "value": null, 1061 | "visibility": "internal" 1062 | }, 1063 | { 1064 | "constant": false, 1065 | "id": 944, 1066 | "indexed": true, 1067 | "mutability": "mutable", 1068 | "name": "spender", 1069 | "nodeType": "VariableDeclaration", 1070 | "overrides": null, 1071 | "scope": 948, 1072 | "src": "2661:23:7", 1073 | "stateVariable": false, 1074 | "storageLocation": "default", 1075 | "typeDescriptions": { 1076 | "typeIdentifier": "t_address", 1077 | "typeString": "address" 1078 | }, 1079 | "typeName": { 1080 | "id": 943, 1081 | "name": "address", 1082 | "nodeType": "ElementaryTypeName", 1083 | "src": "2661:7:7", 1084 | "stateMutability": "nonpayable", 1085 | "typeDescriptions": { 1086 | "typeIdentifier": "t_address", 1087 | "typeString": "address" 1088 | } 1089 | }, 1090 | "value": null, 1091 | "visibility": "internal" 1092 | }, 1093 | { 1094 | "constant": false, 1095 | "id": 946, 1096 | "indexed": false, 1097 | "mutability": "mutable", 1098 | "name": "value", 1099 | "nodeType": "VariableDeclaration", 1100 | "overrides": null, 1101 | "scope": 948, 1102 | "src": "2686:13:7", 1103 | "stateVariable": false, 1104 | "storageLocation": "default", 1105 | "typeDescriptions": { 1106 | "typeIdentifier": "t_uint256", 1107 | "typeString": "uint256" 1108 | }, 1109 | "typeName": { 1110 | "id": 945, 1111 | "name": "uint256", 1112 | "nodeType": "ElementaryTypeName", 1113 | "src": "2686:7:7", 1114 | "typeDescriptions": { 1115 | "typeIdentifier": "t_uint256", 1116 | "typeString": "uint256" 1117 | } 1118 | }, 1119 | "value": null, 1120 | "visibility": "internal" 1121 | } 1122 | ], 1123 | "src": "2637:63:7" 1124 | }, 1125 | "src": "2623:78:7" 1126 | } 1127 | ], 1128 | "scope": 950, 1129 | "src": "137:2566:7" 1130 | } 1131 | ], 1132 | "src": "33:2671:7" 1133 | }, 1134 | "legacyAST": { 1135 | "absolutePath": "@openzeppelin/contracts/token/ERC20/IERC20.sol", 1136 | "exportedSymbols": { 1137 | "IERC20": [ 1138 | 949 1139 | ] 1140 | }, 1141 | "id": 950, 1142 | "nodeType": "SourceUnit", 1143 | "nodes": [ 1144 | { 1145 | "id": 873, 1146 | "literals": [ 1147 | "solidity", 1148 | ">=", 1149 | "0.6", 1150 | ".0", 1151 | "<", 1152 | "0.8", 1153 | ".0" 1154 | ], 1155 | "nodeType": "PragmaDirective", 1156 | "src": "33:31:7" 1157 | }, 1158 | { 1159 | "abstract": false, 1160 | "baseContracts": [], 1161 | "contractDependencies": [], 1162 | "contractKind": "interface", 1163 | "documentation": { 1164 | "id": 874, 1165 | "nodeType": "StructuredDocumentation", 1166 | "src": "66:70:7", 1167 | "text": "@dev Interface of the ERC20 standard as defined in the EIP." 1168 | }, 1169 | "fullyImplemented": false, 1170 | "id": 949, 1171 | "linearizedBaseContracts": [ 1172 | 949 1173 | ], 1174 | "name": "IERC20", 1175 | "nodeType": "ContractDefinition", 1176 | "nodes": [ 1177 | { 1178 | "body": null, 1179 | "documentation": { 1180 | "id": 875, 1181 | "nodeType": "StructuredDocumentation", 1182 | "src": "160:66:7", 1183 | "text": "@dev Returns the amount of tokens in existence." 1184 | }, 1185 | "functionSelector": "18160ddd", 1186 | "id": 880, 1187 | "implemented": false, 1188 | "kind": "function", 1189 | "modifiers": [], 1190 | "name": "totalSupply", 1191 | "nodeType": "FunctionDefinition", 1192 | "overrides": null, 1193 | "parameters": { 1194 | "id": 876, 1195 | "nodeType": "ParameterList", 1196 | "parameters": [], 1197 | "src": "251:2:7" 1198 | }, 1199 | "returnParameters": { 1200 | "id": 879, 1201 | "nodeType": "ParameterList", 1202 | "parameters": [ 1203 | { 1204 | "constant": false, 1205 | "id": 878, 1206 | "mutability": "mutable", 1207 | "name": "", 1208 | "nodeType": "VariableDeclaration", 1209 | "overrides": null, 1210 | "scope": 880, 1211 | "src": "277:7:7", 1212 | "stateVariable": false, 1213 | "storageLocation": "default", 1214 | "typeDescriptions": { 1215 | "typeIdentifier": "t_uint256", 1216 | "typeString": "uint256" 1217 | }, 1218 | "typeName": { 1219 | "id": 877, 1220 | "name": "uint256", 1221 | "nodeType": "ElementaryTypeName", 1222 | "src": "277:7:7", 1223 | "typeDescriptions": { 1224 | "typeIdentifier": "t_uint256", 1225 | "typeString": "uint256" 1226 | } 1227 | }, 1228 | "value": null, 1229 | "visibility": "internal" 1230 | } 1231 | ], 1232 | "src": "276:9:7" 1233 | }, 1234 | "scope": 949, 1235 | "src": "231:55:7", 1236 | "stateMutability": "view", 1237 | "virtual": false, 1238 | "visibility": "external" 1239 | }, 1240 | { 1241 | "body": null, 1242 | "documentation": { 1243 | "id": 881, 1244 | "nodeType": "StructuredDocumentation", 1245 | "src": "292:72:7", 1246 | "text": "@dev Returns the amount of tokens owned by `account`." 1247 | }, 1248 | "functionSelector": "70a08231", 1249 | "id": 888, 1250 | "implemented": false, 1251 | "kind": "function", 1252 | "modifiers": [], 1253 | "name": "balanceOf", 1254 | "nodeType": "FunctionDefinition", 1255 | "overrides": null, 1256 | "parameters": { 1257 | "id": 884, 1258 | "nodeType": "ParameterList", 1259 | "parameters": [ 1260 | { 1261 | "constant": false, 1262 | "id": 883, 1263 | "mutability": "mutable", 1264 | "name": "account", 1265 | "nodeType": "VariableDeclaration", 1266 | "overrides": null, 1267 | "scope": 888, 1268 | "src": "388:15:7", 1269 | "stateVariable": false, 1270 | "storageLocation": "default", 1271 | "typeDescriptions": { 1272 | "typeIdentifier": "t_address", 1273 | "typeString": "address" 1274 | }, 1275 | "typeName": { 1276 | "id": 882, 1277 | "name": "address", 1278 | "nodeType": "ElementaryTypeName", 1279 | "src": "388:7:7", 1280 | "stateMutability": "nonpayable", 1281 | "typeDescriptions": { 1282 | "typeIdentifier": "t_address", 1283 | "typeString": "address" 1284 | } 1285 | }, 1286 | "value": null, 1287 | "visibility": "internal" 1288 | } 1289 | ], 1290 | "src": "387:17:7" 1291 | }, 1292 | "returnParameters": { 1293 | "id": 887, 1294 | "nodeType": "ParameterList", 1295 | "parameters": [ 1296 | { 1297 | "constant": false, 1298 | "id": 886, 1299 | "mutability": "mutable", 1300 | "name": "", 1301 | "nodeType": "VariableDeclaration", 1302 | "overrides": null, 1303 | "scope": 888, 1304 | "src": "428:7:7", 1305 | "stateVariable": false, 1306 | "storageLocation": "default", 1307 | "typeDescriptions": { 1308 | "typeIdentifier": "t_uint256", 1309 | "typeString": "uint256" 1310 | }, 1311 | "typeName": { 1312 | "id": 885, 1313 | "name": "uint256", 1314 | "nodeType": "ElementaryTypeName", 1315 | "src": "428:7:7", 1316 | "typeDescriptions": { 1317 | "typeIdentifier": "t_uint256", 1318 | "typeString": "uint256" 1319 | } 1320 | }, 1321 | "value": null, 1322 | "visibility": "internal" 1323 | } 1324 | ], 1325 | "src": "427:9:7" 1326 | }, 1327 | "scope": 949, 1328 | "src": "369:68:7", 1329 | "stateMutability": "view", 1330 | "virtual": false, 1331 | "visibility": "external" 1332 | }, 1333 | { 1334 | "body": null, 1335 | "documentation": { 1336 | "id": 889, 1337 | "nodeType": "StructuredDocumentation", 1338 | "src": "443:209:7", 1339 | "text": "@dev Moves `amount` tokens from the caller's account to `recipient`.\n * Returns a boolean value indicating whether the operation succeeded.\n * Emits a {Transfer} event." 1340 | }, 1341 | "functionSelector": "a9059cbb", 1342 | "id": 898, 1343 | "implemented": false, 1344 | "kind": "function", 1345 | "modifiers": [], 1346 | "name": "transfer", 1347 | "nodeType": "FunctionDefinition", 1348 | "overrides": null, 1349 | "parameters": { 1350 | "id": 894, 1351 | "nodeType": "ParameterList", 1352 | "parameters": [ 1353 | { 1354 | "constant": false, 1355 | "id": 891, 1356 | "mutability": "mutable", 1357 | "name": "recipient", 1358 | "nodeType": "VariableDeclaration", 1359 | "overrides": null, 1360 | "scope": 898, 1361 | "src": "675:17:7", 1362 | "stateVariable": false, 1363 | "storageLocation": "default", 1364 | "typeDescriptions": { 1365 | "typeIdentifier": "t_address", 1366 | "typeString": "address" 1367 | }, 1368 | "typeName": { 1369 | "id": 890, 1370 | "name": "address", 1371 | "nodeType": "ElementaryTypeName", 1372 | "src": "675:7:7", 1373 | "stateMutability": "nonpayable", 1374 | "typeDescriptions": { 1375 | "typeIdentifier": "t_address", 1376 | "typeString": "address" 1377 | } 1378 | }, 1379 | "value": null, 1380 | "visibility": "internal" 1381 | }, 1382 | { 1383 | "constant": false, 1384 | "id": 893, 1385 | "mutability": "mutable", 1386 | "name": "amount", 1387 | "nodeType": "VariableDeclaration", 1388 | "overrides": null, 1389 | "scope": 898, 1390 | "src": "694:14:7", 1391 | "stateVariable": false, 1392 | "storageLocation": "default", 1393 | "typeDescriptions": { 1394 | "typeIdentifier": "t_uint256", 1395 | "typeString": "uint256" 1396 | }, 1397 | "typeName": { 1398 | "id": 892, 1399 | "name": "uint256", 1400 | "nodeType": "ElementaryTypeName", 1401 | "src": "694:7:7", 1402 | "typeDescriptions": { 1403 | "typeIdentifier": "t_uint256", 1404 | "typeString": "uint256" 1405 | } 1406 | }, 1407 | "value": null, 1408 | "visibility": "internal" 1409 | } 1410 | ], 1411 | "src": "674:35:7" 1412 | }, 1413 | "returnParameters": { 1414 | "id": 897, 1415 | "nodeType": "ParameterList", 1416 | "parameters": [ 1417 | { 1418 | "constant": false, 1419 | "id": 896, 1420 | "mutability": "mutable", 1421 | "name": "", 1422 | "nodeType": "VariableDeclaration", 1423 | "overrides": null, 1424 | "scope": 898, 1425 | "src": "728:4:7", 1426 | "stateVariable": false, 1427 | "storageLocation": "default", 1428 | "typeDescriptions": { 1429 | "typeIdentifier": "t_bool", 1430 | "typeString": "bool" 1431 | }, 1432 | "typeName": { 1433 | "id": 895, 1434 | "name": "bool", 1435 | "nodeType": "ElementaryTypeName", 1436 | "src": "728:4:7", 1437 | "typeDescriptions": { 1438 | "typeIdentifier": "t_bool", 1439 | "typeString": "bool" 1440 | } 1441 | }, 1442 | "value": null, 1443 | "visibility": "internal" 1444 | } 1445 | ], 1446 | "src": "727:6:7" 1447 | }, 1448 | "scope": 949, 1449 | "src": "657:77:7", 1450 | "stateMutability": "nonpayable", 1451 | "virtual": false, 1452 | "visibility": "external" 1453 | }, 1454 | { 1455 | "body": null, 1456 | "documentation": { 1457 | "id": 899, 1458 | "nodeType": "StructuredDocumentation", 1459 | "src": "740:264:7", 1460 | "text": "@dev Returns the remaining number of tokens that `spender` will be\nallowed to spend on behalf of `owner` through {transferFrom}. This is\nzero by default.\n * This value changes when {approve} or {transferFrom} are called." 1461 | }, 1462 | "functionSelector": "dd62ed3e", 1463 | "id": 908, 1464 | "implemented": false, 1465 | "kind": "function", 1466 | "modifiers": [], 1467 | "name": "allowance", 1468 | "nodeType": "FunctionDefinition", 1469 | "overrides": null, 1470 | "parameters": { 1471 | "id": 904, 1472 | "nodeType": "ParameterList", 1473 | "parameters": [ 1474 | { 1475 | "constant": false, 1476 | "id": 901, 1477 | "mutability": "mutable", 1478 | "name": "owner", 1479 | "nodeType": "VariableDeclaration", 1480 | "overrides": null, 1481 | "scope": 908, 1482 | "src": "1028:13:7", 1483 | "stateVariable": false, 1484 | "storageLocation": "default", 1485 | "typeDescriptions": { 1486 | "typeIdentifier": "t_address", 1487 | "typeString": "address" 1488 | }, 1489 | "typeName": { 1490 | "id": 900, 1491 | "name": "address", 1492 | "nodeType": "ElementaryTypeName", 1493 | "src": "1028:7:7", 1494 | "stateMutability": "nonpayable", 1495 | "typeDescriptions": { 1496 | "typeIdentifier": "t_address", 1497 | "typeString": "address" 1498 | } 1499 | }, 1500 | "value": null, 1501 | "visibility": "internal" 1502 | }, 1503 | { 1504 | "constant": false, 1505 | "id": 903, 1506 | "mutability": "mutable", 1507 | "name": "spender", 1508 | "nodeType": "VariableDeclaration", 1509 | "overrides": null, 1510 | "scope": 908, 1511 | "src": "1043:15:7", 1512 | "stateVariable": false, 1513 | "storageLocation": "default", 1514 | "typeDescriptions": { 1515 | "typeIdentifier": "t_address", 1516 | "typeString": "address" 1517 | }, 1518 | "typeName": { 1519 | "id": 902, 1520 | "name": "address", 1521 | "nodeType": "ElementaryTypeName", 1522 | "src": "1043:7:7", 1523 | "stateMutability": "nonpayable", 1524 | "typeDescriptions": { 1525 | "typeIdentifier": "t_address", 1526 | "typeString": "address" 1527 | } 1528 | }, 1529 | "value": null, 1530 | "visibility": "internal" 1531 | } 1532 | ], 1533 | "src": "1027:32:7" 1534 | }, 1535 | "returnParameters": { 1536 | "id": 907, 1537 | "nodeType": "ParameterList", 1538 | "parameters": [ 1539 | { 1540 | "constant": false, 1541 | "id": 906, 1542 | "mutability": "mutable", 1543 | "name": "", 1544 | "nodeType": "VariableDeclaration", 1545 | "overrides": null, 1546 | "scope": 908, 1547 | "src": "1083:7:7", 1548 | "stateVariable": false, 1549 | "storageLocation": "default", 1550 | "typeDescriptions": { 1551 | "typeIdentifier": "t_uint256", 1552 | "typeString": "uint256" 1553 | }, 1554 | "typeName": { 1555 | "id": 905, 1556 | "name": "uint256", 1557 | "nodeType": "ElementaryTypeName", 1558 | "src": "1083:7:7", 1559 | "typeDescriptions": { 1560 | "typeIdentifier": "t_uint256", 1561 | "typeString": "uint256" 1562 | } 1563 | }, 1564 | "value": null, 1565 | "visibility": "internal" 1566 | } 1567 | ], 1568 | "src": "1082:9:7" 1569 | }, 1570 | "scope": 949, 1571 | "src": "1009:83:7", 1572 | "stateMutability": "view", 1573 | "virtual": false, 1574 | "visibility": "external" 1575 | }, 1576 | { 1577 | "body": null, 1578 | "documentation": { 1579 | "id": 909, 1580 | "nodeType": "StructuredDocumentation", 1581 | "src": "1098:642:7", 1582 | "text": "@dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n * Returns a boolean value indicating whether the operation succeeded.\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\nthat someone may use both the old and the new allowance by unfortunate\ntransaction ordering. One possible solution to mitigate this race\ncondition is to first reduce the spender's allowance to 0 and set the\ndesired value afterwards:\nhttps://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n * Emits an {Approval} event." 1583 | }, 1584 | "functionSelector": "095ea7b3", 1585 | "id": 918, 1586 | "implemented": false, 1587 | "kind": "function", 1588 | "modifiers": [], 1589 | "name": "approve", 1590 | "nodeType": "FunctionDefinition", 1591 | "overrides": null, 1592 | "parameters": { 1593 | "id": 914, 1594 | "nodeType": "ParameterList", 1595 | "parameters": [ 1596 | { 1597 | "constant": false, 1598 | "id": 911, 1599 | "mutability": "mutable", 1600 | "name": "spender", 1601 | "nodeType": "VariableDeclaration", 1602 | "overrides": null, 1603 | "scope": 918, 1604 | "src": "1762:15:7", 1605 | "stateVariable": false, 1606 | "storageLocation": "default", 1607 | "typeDescriptions": { 1608 | "typeIdentifier": "t_address", 1609 | "typeString": "address" 1610 | }, 1611 | "typeName": { 1612 | "id": 910, 1613 | "name": "address", 1614 | "nodeType": "ElementaryTypeName", 1615 | "src": "1762:7:7", 1616 | "stateMutability": "nonpayable", 1617 | "typeDescriptions": { 1618 | "typeIdentifier": "t_address", 1619 | "typeString": "address" 1620 | } 1621 | }, 1622 | "value": null, 1623 | "visibility": "internal" 1624 | }, 1625 | { 1626 | "constant": false, 1627 | "id": 913, 1628 | "mutability": "mutable", 1629 | "name": "amount", 1630 | "nodeType": "VariableDeclaration", 1631 | "overrides": null, 1632 | "scope": 918, 1633 | "src": "1779:14:7", 1634 | "stateVariable": false, 1635 | "storageLocation": "default", 1636 | "typeDescriptions": { 1637 | "typeIdentifier": "t_uint256", 1638 | "typeString": "uint256" 1639 | }, 1640 | "typeName": { 1641 | "id": 912, 1642 | "name": "uint256", 1643 | "nodeType": "ElementaryTypeName", 1644 | "src": "1779:7:7", 1645 | "typeDescriptions": { 1646 | "typeIdentifier": "t_uint256", 1647 | "typeString": "uint256" 1648 | } 1649 | }, 1650 | "value": null, 1651 | "visibility": "internal" 1652 | } 1653 | ], 1654 | "src": "1761:33:7" 1655 | }, 1656 | "returnParameters": { 1657 | "id": 917, 1658 | "nodeType": "ParameterList", 1659 | "parameters": [ 1660 | { 1661 | "constant": false, 1662 | "id": 916, 1663 | "mutability": "mutable", 1664 | "name": "", 1665 | "nodeType": "VariableDeclaration", 1666 | "overrides": null, 1667 | "scope": 918, 1668 | "src": "1813:4:7", 1669 | "stateVariable": false, 1670 | "storageLocation": "default", 1671 | "typeDescriptions": { 1672 | "typeIdentifier": "t_bool", 1673 | "typeString": "bool" 1674 | }, 1675 | "typeName": { 1676 | "id": 915, 1677 | "name": "bool", 1678 | "nodeType": "ElementaryTypeName", 1679 | "src": "1813:4:7", 1680 | "typeDescriptions": { 1681 | "typeIdentifier": "t_bool", 1682 | "typeString": "bool" 1683 | } 1684 | }, 1685 | "value": null, 1686 | "visibility": "internal" 1687 | } 1688 | ], 1689 | "src": "1812:6:7" 1690 | }, 1691 | "scope": 949, 1692 | "src": "1745:74:7", 1693 | "stateMutability": "nonpayable", 1694 | "virtual": false, 1695 | "visibility": "external" 1696 | }, 1697 | { 1698 | "body": null, 1699 | "documentation": { 1700 | "id": 919, 1701 | "nodeType": "StructuredDocumentation", 1702 | "src": "1825:296:7", 1703 | "text": "@dev Moves `amount` tokens from `sender` to `recipient` using the\nallowance mechanism. `amount` is then deducted from the caller's\nallowance.\n * Returns a boolean value indicating whether the operation succeeded.\n * Emits a {Transfer} event." 1704 | }, 1705 | "functionSelector": "23b872dd", 1706 | "id": 930, 1707 | "implemented": false, 1708 | "kind": "function", 1709 | "modifiers": [], 1710 | "name": "transferFrom", 1711 | "nodeType": "FunctionDefinition", 1712 | "overrides": null, 1713 | "parameters": { 1714 | "id": 926, 1715 | "nodeType": "ParameterList", 1716 | "parameters": [ 1717 | { 1718 | "constant": false, 1719 | "id": 921, 1720 | "mutability": "mutable", 1721 | "name": "sender", 1722 | "nodeType": "VariableDeclaration", 1723 | "overrides": null, 1724 | "scope": 930, 1725 | "src": "2148:14:7", 1726 | "stateVariable": false, 1727 | "storageLocation": "default", 1728 | "typeDescriptions": { 1729 | "typeIdentifier": "t_address", 1730 | "typeString": "address" 1731 | }, 1732 | "typeName": { 1733 | "id": 920, 1734 | "name": "address", 1735 | "nodeType": "ElementaryTypeName", 1736 | "src": "2148:7:7", 1737 | "stateMutability": "nonpayable", 1738 | "typeDescriptions": { 1739 | "typeIdentifier": "t_address", 1740 | "typeString": "address" 1741 | } 1742 | }, 1743 | "value": null, 1744 | "visibility": "internal" 1745 | }, 1746 | { 1747 | "constant": false, 1748 | "id": 923, 1749 | "mutability": "mutable", 1750 | "name": "recipient", 1751 | "nodeType": "VariableDeclaration", 1752 | "overrides": null, 1753 | "scope": 930, 1754 | "src": "2164:17:7", 1755 | "stateVariable": false, 1756 | "storageLocation": "default", 1757 | "typeDescriptions": { 1758 | "typeIdentifier": "t_address", 1759 | "typeString": "address" 1760 | }, 1761 | "typeName": { 1762 | "id": 922, 1763 | "name": "address", 1764 | "nodeType": "ElementaryTypeName", 1765 | "src": "2164:7:7", 1766 | "stateMutability": "nonpayable", 1767 | "typeDescriptions": { 1768 | "typeIdentifier": "t_address", 1769 | "typeString": "address" 1770 | } 1771 | }, 1772 | "value": null, 1773 | "visibility": "internal" 1774 | }, 1775 | { 1776 | "constant": false, 1777 | "id": 925, 1778 | "mutability": "mutable", 1779 | "name": "amount", 1780 | "nodeType": "VariableDeclaration", 1781 | "overrides": null, 1782 | "scope": 930, 1783 | "src": "2183:14:7", 1784 | "stateVariable": false, 1785 | "storageLocation": "default", 1786 | "typeDescriptions": { 1787 | "typeIdentifier": "t_uint256", 1788 | "typeString": "uint256" 1789 | }, 1790 | "typeName": { 1791 | "id": 924, 1792 | "name": "uint256", 1793 | "nodeType": "ElementaryTypeName", 1794 | "src": "2183:7:7", 1795 | "typeDescriptions": { 1796 | "typeIdentifier": "t_uint256", 1797 | "typeString": "uint256" 1798 | } 1799 | }, 1800 | "value": null, 1801 | "visibility": "internal" 1802 | } 1803 | ], 1804 | "src": "2147:51:7" 1805 | }, 1806 | "returnParameters": { 1807 | "id": 929, 1808 | "nodeType": "ParameterList", 1809 | "parameters": [ 1810 | { 1811 | "constant": false, 1812 | "id": 928, 1813 | "mutability": "mutable", 1814 | "name": "", 1815 | "nodeType": "VariableDeclaration", 1816 | "overrides": null, 1817 | "scope": 930, 1818 | "src": "2217:4:7", 1819 | "stateVariable": false, 1820 | "storageLocation": "default", 1821 | "typeDescriptions": { 1822 | "typeIdentifier": "t_bool", 1823 | "typeString": "bool" 1824 | }, 1825 | "typeName": { 1826 | "id": 927, 1827 | "name": "bool", 1828 | "nodeType": "ElementaryTypeName", 1829 | "src": "2217:4:7", 1830 | "typeDescriptions": { 1831 | "typeIdentifier": "t_bool", 1832 | "typeString": "bool" 1833 | } 1834 | }, 1835 | "value": null, 1836 | "visibility": "internal" 1837 | } 1838 | ], 1839 | "src": "2216:6:7" 1840 | }, 1841 | "scope": 949, 1842 | "src": "2126:97:7", 1843 | "stateMutability": "nonpayable", 1844 | "virtual": false, 1845 | "visibility": "external" 1846 | }, 1847 | { 1848 | "anonymous": false, 1849 | "documentation": { 1850 | "id": 931, 1851 | "nodeType": "StructuredDocumentation", 1852 | "src": "2229:158:7", 1853 | "text": "@dev Emitted when `value` tokens are moved from one account (`from`) to\nanother (`to`).\n * Note that `value` may be zero." 1854 | }, 1855 | "id": 939, 1856 | "name": "Transfer", 1857 | "nodeType": "EventDefinition", 1858 | "parameters": { 1859 | "id": 938, 1860 | "nodeType": "ParameterList", 1861 | "parameters": [ 1862 | { 1863 | "constant": false, 1864 | "id": 933, 1865 | "indexed": true, 1866 | "mutability": "mutable", 1867 | "name": "from", 1868 | "nodeType": "VariableDeclaration", 1869 | "overrides": null, 1870 | "scope": 939, 1871 | "src": "2407:20:7", 1872 | "stateVariable": false, 1873 | "storageLocation": "default", 1874 | "typeDescriptions": { 1875 | "typeIdentifier": "t_address", 1876 | "typeString": "address" 1877 | }, 1878 | "typeName": { 1879 | "id": 932, 1880 | "name": "address", 1881 | "nodeType": "ElementaryTypeName", 1882 | "src": "2407:7:7", 1883 | "stateMutability": "nonpayable", 1884 | "typeDescriptions": { 1885 | "typeIdentifier": "t_address", 1886 | "typeString": "address" 1887 | } 1888 | }, 1889 | "value": null, 1890 | "visibility": "internal" 1891 | }, 1892 | { 1893 | "constant": false, 1894 | "id": 935, 1895 | "indexed": true, 1896 | "mutability": "mutable", 1897 | "name": "to", 1898 | "nodeType": "VariableDeclaration", 1899 | "overrides": null, 1900 | "scope": 939, 1901 | "src": "2429:18:7", 1902 | "stateVariable": false, 1903 | "storageLocation": "default", 1904 | "typeDescriptions": { 1905 | "typeIdentifier": "t_address", 1906 | "typeString": "address" 1907 | }, 1908 | "typeName": { 1909 | "id": 934, 1910 | "name": "address", 1911 | "nodeType": "ElementaryTypeName", 1912 | "src": "2429:7:7", 1913 | "stateMutability": "nonpayable", 1914 | "typeDescriptions": { 1915 | "typeIdentifier": "t_address", 1916 | "typeString": "address" 1917 | } 1918 | }, 1919 | "value": null, 1920 | "visibility": "internal" 1921 | }, 1922 | { 1923 | "constant": false, 1924 | "id": 937, 1925 | "indexed": false, 1926 | "mutability": "mutable", 1927 | "name": "value", 1928 | "nodeType": "VariableDeclaration", 1929 | "overrides": null, 1930 | "scope": 939, 1931 | "src": "2449:13:7", 1932 | "stateVariable": false, 1933 | "storageLocation": "default", 1934 | "typeDescriptions": { 1935 | "typeIdentifier": "t_uint256", 1936 | "typeString": "uint256" 1937 | }, 1938 | "typeName": { 1939 | "id": 936, 1940 | "name": "uint256", 1941 | "nodeType": "ElementaryTypeName", 1942 | "src": "2449:7:7", 1943 | "typeDescriptions": { 1944 | "typeIdentifier": "t_uint256", 1945 | "typeString": "uint256" 1946 | } 1947 | }, 1948 | "value": null, 1949 | "visibility": "internal" 1950 | } 1951 | ], 1952 | "src": "2406:57:7" 1953 | }, 1954 | "src": "2392:72:7" 1955 | }, 1956 | { 1957 | "anonymous": false, 1958 | "documentation": { 1959 | "id": 940, 1960 | "nodeType": "StructuredDocumentation", 1961 | "src": "2470:148:7", 1962 | "text": "@dev Emitted when the allowance of a `spender` for an `owner` is set by\na call to {approve}. `value` is the new allowance." 1963 | }, 1964 | "id": 948, 1965 | "name": "Approval", 1966 | "nodeType": "EventDefinition", 1967 | "parameters": { 1968 | "id": 947, 1969 | "nodeType": "ParameterList", 1970 | "parameters": [ 1971 | { 1972 | "constant": false, 1973 | "id": 942, 1974 | "indexed": true, 1975 | "mutability": "mutable", 1976 | "name": "owner", 1977 | "nodeType": "VariableDeclaration", 1978 | "overrides": null, 1979 | "scope": 948, 1980 | "src": "2638:21:7", 1981 | "stateVariable": false, 1982 | "storageLocation": "default", 1983 | "typeDescriptions": { 1984 | "typeIdentifier": "t_address", 1985 | "typeString": "address" 1986 | }, 1987 | "typeName": { 1988 | "id": 941, 1989 | "name": "address", 1990 | "nodeType": "ElementaryTypeName", 1991 | "src": "2638:7:7", 1992 | "stateMutability": "nonpayable", 1993 | "typeDescriptions": { 1994 | "typeIdentifier": "t_address", 1995 | "typeString": "address" 1996 | } 1997 | }, 1998 | "value": null, 1999 | "visibility": "internal" 2000 | }, 2001 | { 2002 | "constant": false, 2003 | "id": 944, 2004 | "indexed": true, 2005 | "mutability": "mutable", 2006 | "name": "spender", 2007 | "nodeType": "VariableDeclaration", 2008 | "overrides": null, 2009 | "scope": 948, 2010 | "src": "2661:23:7", 2011 | "stateVariable": false, 2012 | "storageLocation": "default", 2013 | "typeDescriptions": { 2014 | "typeIdentifier": "t_address", 2015 | "typeString": "address" 2016 | }, 2017 | "typeName": { 2018 | "id": 943, 2019 | "name": "address", 2020 | "nodeType": "ElementaryTypeName", 2021 | "src": "2661:7:7", 2022 | "stateMutability": "nonpayable", 2023 | "typeDescriptions": { 2024 | "typeIdentifier": "t_address", 2025 | "typeString": "address" 2026 | } 2027 | }, 2028 | "value": null, 2029 | "visibility": "internal" 2030 | }, 2031 | { 2032 | "constant": false, 2033 | "id": 946, 2034 | "indexed": false, 2035 | "mutability": "mutable", 2036 | "name": "value", 2037 | "nodeType": "VariableDeclaration", 2038 | "overrides": null, 2039 | "scope": 948, 2040 | "src": "2686:13:7", 2041 | "stateVariable": false, 2042 | "storageLocation": "default", 2043 | "typeDescriptions": { 2044 | "typeIdentifier": "t_uint256", 2045 | "typeString": "uint256" 2046 | }, 2047 | "typeName": { 2048 | "id": 945, 2049 | "name": "uint256", 2050 | "nodeType": "ElementaryTypeName", 2051 | "src": "2686:7:7", 2052 | "typeDescriptions": { 2053 | "typeIdentifier": "t_uint256", 2054 | "typeString": "uint256" 2055 | } 2056 | }, 2057 | "value": null, 2058 | "visibility": "internal" 2059 | } 2060 | ], 2061 | "src": "2637:63:7" 2062 | }, 2063 | "src": "2623:78:7" 2064 | } 2065 | ], 2066 | "scope": 950, 2067 | "src": "137:2566:7" 2068 | } 2069 | ], 2070 | "src": "33:2671:7" 2071 | }, 2072 | "compiler": { 2073 | "name": "solc", 2074 | "version": "0.6.6+commit.6c089d02.Emscripten.clang" 2075 | }, 2076 | "networks": {}, 2077 | "schemaVersion": "3.3.2", 2078 | "updatedAt": "2021-01-08T09:06:04.980Z", 2079 | "devdoc": { 2080 | "details": "Interface of the ERC20 standard as defined in the EIP.", 2081 | "methods": { 2082 | "allowance(address,address)": { 2083 | "details": "Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. * This value changes when {approve} or {transferFrom} are called." 2084 | }, 2085 | "approve(address,uint256)": { 2086 | "details": "Sets `amount` as the allowance of `spender` over the caller's tokens. * Returns a boolean value indicating whether the operation succeeded. * IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * Emits an {Approval} event." 2087 | }, 2088 | "balanceOf(address)": { 2089 | "details": "Returns the amount of tokens owned by `account`." 2090 | }, 2091 | "totalSupply()": { 2092 | "details": "Returns the amount of tokens in existence." 2093 | }, 2094 | "transfer(address,uint256)": { 2095 | "details": "Moves `amount` tokens from the caller's account to `recipient`. * Returns a boolean value indicating whether the operation succeeded. * Emits a {Transfer} event." 2096 | }, 2097 | "transferFrom(address,address,uint256)": { 2098 | "details": "Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. * Returns a boolean value indicating whether the operation succeeded. * Emits a {Transfer} event." 2099 | } 2100 | } 2101 | }, 2102 | "userdoc": { 2103 | "methods": {} 2104 | } 2105 | } --------------------------------------------------------------------------------