├── .env.example ├── scripts ├── arguments.ts └── deploy.ts ├── tsconfig.json ├── package.json ├── hardhat.config.ts ├── LICENSE ├── README.md ├── contracts └── Token.sol ├── .gitignore └── test └── Token.test.ts /.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PUBLIC_KEY= 2 | WALLET_PRIVATE_KEY= 3 | INFURA_PROJECT_ID= 4 | ETHERSCAN_API_KEY= 5 | -------------------------------------------------------------------------------- /scripts/arguments.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { BigNumber } from "ethers"; 3 | 4 | const name: string = "Token"; 5 | const symbol: string = "TOKE"; 6 | const initialSupply: string = "420"; 7 | 8 | const initialSupplyInWei: BigNumber = ethers.utils.parseEther(initialSupply); 9 | const contractArgs: string[] = [name, symbol, initialSupplyInWei.toString()]; 10 | 11 | export default contractArgs; 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "sourceMap": true, 12 | "strict": true 13 | }, 14 | "include": [ 15 | "artifacts/**/*", 16 | "artifacts/**/*.json", 17 | "scripts/**/*", 18 | "tasks/**/*", 19 | "test/**/*", 20 | "typechain/**/*", 21 | "types/**/*" 22 | ], 23 | "files": [ 24 | "hardhat.config.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { Wallet } from "ethers"; 3 | import { Token, Token__factory } from "../typechain"; 4 | import contractArgs from "./arguments"; 5 | 6 | async function main() { 7 | const wallet: Wallet = new Wallet(`${process.env.WALLET_PRIVATE_KEY}`, ethers.provider); 8 | const factory = await ethers.getContractFactory("Token") as Token__factory; 9 | const contract = await factory.connect(wallet).deploy(contractArgs[0], contractArgs[1], contractArgs[2]) as Token; 10 | await contract.deployed(); 11 | console.log("contract address:", contract.address); 12 | } 13 | 14 | main() 15 | .then(() => process.exit(0)) 16 | .catch(error => { 17 | console.error(error); 18 | process.exit(1); 19 | }); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token", 3 | "version": "1.0.0", 4 | "repository": "git@github.com:jstn/token.git", 5 | "author": "Justin Ouellette", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@nomiclabs/hardhat-ethers": "^2.0.2", 9 | "@nomiclabs/hardhat-etherscan": "^2.1.2", 10 | "@nomiclabs/hardhat-waffle": "^2.0.1", 11 | "@openzeppelin/contracts": "^4.0.0", 12 | "@typechain/ethers-v5": "^6.0.5", 13 | "@typechain/hardhat": "^1.0.1", 14 | "@types/chai": "^4.2.17", 15 | "@types/mocha": "^8.2.2", 16 | "@types/node": "^15.0.1", 17 | "chai": "^4.3.4", 18 | "dotenv": "^8.2.0", 19 | "eslint": "^7.25.0", 20 | "ethereum-waffle": "^3.3.0", 21 | "ethers": "^5.1.4", 22 | "hardhat": "^2.2.1", 23 | "hardhat-erc1820": "^0.1.0", 24 | "hardhat-etherscan": "^1.0.1", 25 | "mocha": "^8.3.2", 26 | "ts-generator": "^0.1.1", 27 | "ts-node": "^9.1.1", 28 | "typechain": "^4.0.3", 29 | "typescript": "^4.2.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import { config as dotenvConfig } from "dotenv"; 3 | import "@nomiclabs/hardhat-etherscan"; 4 | import "@nomiclabs/hardhat-waffle"; 5 | import "@typechain/hardhat"; 6 | import "hardhat-erc1820"; 7 | 8 | dotenvConfig({ 9 | path: `${process.cwd()}/.env` 10 | }); 11 | 12 | const config: HardhatUserConfig = { 13 | solidity: { 14 | version: "0.8.4", 15 | settings: { 16 | metadata: { 17 | bytecodeHash: "none" 18 | }, 19 | optimizer: { 20 | enabled: true, 21 | runs: 200 22 | } 23 | } 24 | }, 25 | defaultNetwork: "hardhat", 26 | networks: { 27 | hardhat: { 28 | }, 29 | rinkeby: { 30 | url: `https://rinkeby.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 31 | accounts: [`${process.env.WALLET_PRIVATE_KEY}`] 32 | } 33 | }, 34 | etherscan: { 35 | apiKey: `${process.env.ETHERSCAN_API_KEY}` 36 | } 37 | }; 38 | 39 | export default config; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Justin Ouellette 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ERC-20 Token 2 | 3 | ## Be president of your own railroad. 4 | 5 | ### Step 1 6 | 7 | Edit [.env.example](.env.example) and rename it to `.env`. 8 | 9 | The wallet entered here will own the token's contract and receive the initial supply. 10 | 11 | Hot tip: export your private key from MetaMask and add `0x` to the beginning. 12 | 13 | ### Step 2 14 | 15 | Edit [arguments.ts](scripts/arguments.ts) to your liking and run the following: 16 | 17 | ``` 18 | yarn install 19 | yarn hardhat compile 20 | yarn hardhat test 21 | ``` 22 | 23 | ### Step 3 24 | 25 | Make sure your contract deployment wallet has ether on Rinkeby, then deploy like so: 26 | 27 | ``` 28 | yarn hardhat run --network rinkeby scripts/deploy.ts 29 | ``` 30 | 31 | Optionally, verify on Etherscan with the following (and hey, why not, you've come this far): 32 | 33 | ``` 34 | yarn hardhat verify --network rinkeby $CONTRACT_ADDRESS --constructor-args scripts/arguments.ts 35 | ``` 36 | 37 | Mainnet is just an ocean of gas away. 38 | 39 | ### Step 4 40 | 41 | Read the docs for the [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts/4.x/), whence this code was wrought. 42 | 43 | ### Step 5 44 | 45 | Be careful what you wish for. 46 | -------------------------------------------------------------------------------- /contracts/Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | import "@openzeppelin/contracts/security/Pausable.sol"; 9 | 10 | contract Token is ERC20, ERC20Burnable, ERC20Snapshot, Ownable, Pausable { 11 | constructor( 12 | string memory name, 13 | string memory symbol, 14 | uint256 initialSupply 15 | ) ERC20(name, symbol) { 16 | _mint(msg.sender, initialSupply); 17 | } 18 | 19 | function snapshot() external onlyOwner { 20 | _snapshot(); 21 | } 22 | 23 | function pause() external onlyOwner { 24 | _pause(); 25 | } 26 | 27 | function unpause() external onlyOwner { 28 | _unpause(); 29 | } 30 | 31 | function mint(address to, uint256 amount) external onlyOwner { 32 | _mint(to, amount); 33 | } 34 | 35 | function _beforeTokenTransfer(address from, address to, uint256 amount) 36 | internal 37 | whenNotPaused 38 | override(ERC20, ERC20Snapshot) 39 | { 40 | super._beforeTokenTransfer(from, to, amount); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | typechain/ 3 | cache/ 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | -------------------------------------------------------------------------------- /test/Token.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { expect } from "chai"; 3 | import { BigNumber, Signer } from "ethers"; 4 | import { Token, Token__factory } from "../typechain"; 5 | import contractArgs from "../scripts/arguments"; 6 | 7 | describe("Token", function () { 8 | let _contract: Token; 9 | let _signers: Signer[]; 10 | let _sender: string; 11 | let _receiver: string; 12 | 13 | const _name: string = contractArgs[0]; 14 | const _symbol: string = contractArgs[1]; 15 | const _initialSupply: string = contractArgs[2]; 16 | 17 | beforeEach(async function() { 18 | _signers = await ethers.getSigners(); 19 | _sender = await _signers[0].getAddress(); 20 | _receiver = await _signers[1].getAddress(); 21 | 22 | const factory = await ethers.getContractFactory("Token") as Token__factory; 23 | _contract = await factory.connect(_signers[0]).deploy(_name, _symbol, _initialSupply) as Token; 24 | await _contract.deployed(); 25 | }); 26 | 27 | it("should do token stuff", async function () { 28 | const senderBalanceBefore: BigNumber = await _contract.balanceOf(_sender); 29 | const receiverBalance: BigNumber = await _contract.balanceOf(_receiver); 30 | 31 | console.log("sender balance before", ethers.utils.formatEther(senderBalanceBefore)); 32 | console.log("receiver balance before", ethers.utils.formatEther(receiverBalance)); 33 | 34 | expect(senderBalanceBefore.eq(_initialSupply)).to.be.true; 35 | expect(receiverBalance.isZero()).to.be.true; 36 | 37 | console.log("sending $TOKE to receiver..."); 38 | const transferAmount: BigNumber = ethers.utils.parseEther("0.666"); 39 | await _contract.connect(_signers[0]).transfer(_receiver, transferAmount); 40 | 41 | const senderBalanceAfter: BigNumber = await _contract.balanceOf(_sender); 42 | const receiverBalanceAfter: BigNumber = await _contract.balanceOf(_receiver); 43 | 44 | console.log("sender balance after", ethers.utils.formatEther(senderBalanceAfter)); 45 | console.log("receiver balance after", ethers.utils.formatEther(receiverBalanceAfter)); 46 | 47 | const difference: BigNumber = senderBalanceBefore.sub(senderBalanceAfter); 48 | expect(difference.eq(transferAmount)).to.be.true; 49 | expect(receiverBalanceAfter.eq(transferAmount)).to.be.true; 50 | }); 51 | }); 52 | --------------------------------------------------------------------------------