├── .env ├── .gitattributes ├── .gitignore ├── README.md ├── brownie-config.yaml ├── contracts ├── EasyToken.sol ├── OurToken.sol └── TokenERC20.sol └── scripts ├── 1_deploy_token.py ├── 2_deploy_easy_token.py ├── __init__.py ├── helpful_scripts.py └── send_arbitrary_token.py /.env: -------------------------------------------------------------------------------- 1 | ### uncomment me to use the variables 2 | ### Do not send to github 3 | # export WEB3_INFURA_PROJECT_ID='aaa5aa5a5a5a55555aaa555a5a5555a` 4 | # export PRIVATE_KEY='asdfasdfasdfasdfasdfasdfasdfas' 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .history 3 | .hypothesis/ 4 | build/ 5 | reports/ 6 | .env 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ERC20 Python, Brownie, Solidity 2 | 3 | ## Prerequisites 4 | 5 | Please install or have installed the following: 6 | 7 | - [nodejs and npm](https://nodejs.org/en/download/) 8 | - [python](https://www.python.org/downloads/) 9 | ## Installation 10 | 11 | 1. [Install Brownie](https://eth-brownie.readthedocs.io/en/stable/install.html), if you haven't already. Here is a simple way to install brownie. 12 | 13 | ```bash 14 | pip install eth-brownie 15 | ``` 16 | Or, if that doesn't work, via pipx 17 | ```bash 18 | pip install --user pipx 19 | pipx ensurepath 20 | # restart your terminal 21 | pipx install eth-brownie 22 | ``` 23 | 24 | 2. [Install ganache-cli](https://www.npmjs.com/package/ganache-cli) 25 | 26 | ```bash 27 | npm install -g ganache-cli 28 | ``` 29 | 30 | ## Quickstart 31 | 32 | 33 | 1. Clone this repo 34 | 35 | ```bash 36 | git clone https://github.com/PatrickAlphaC/erc20-brownie 37 | ``` 38 | 39 | 2. Run a script 40 | 41 | ``` 42 | brownie run scripts/1_deploy_token.sol 43 | ``` 44 | -------------------------------------------------------------------------------- /brownie-config.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - OpenZeppelin/openzeppelin-contracts@4.3.2 3 | compiler: 4 | solc: 5 | remappings: 6 | - '@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.3.2' 7 | dotenv: .env 8 | networks: 9 | default: development 10 | kovan: active 11 | wallets: 12 | from_key: ${PRIVATE_KEY} 13 | -------------------------------------------------------------------------------- /contracts/EasyToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | 5 | contract EasyToken is ERC20 { 6 | constructor() public ERC20("EasyToken", "EzT") { 7 | _mint(msg.sender, 1000000000000000000000000); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/OurToken.sol: -------------------------------------------------------------------------------- 1 | // contracts/OurToken.sol 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract OurToken is ERC20 { 8 | // wei 9 | constructor(uint256 initialSupply) ERC20("OurToken", "OT") { 10 | _mint(msg.sender, initialSupply); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/TokenERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | interface tokenRecipient { 4 | function receiveApproval(address _from, uint256 _value, address _token, bytes calldata _extraData) external; 5 | } 6 | 7 | contract TokenERC20 { 8 | // Public variables of the token 9 | string public name; 10 | string public symbol; 11 | uint8 public decimals = 18; 12 | // 18 decimals is the strongly suggested default, avoid changing it 13 | uint256 public totalSupply; 14 | 15 | // This creates an array with all balances 16 | mapping (address => uint256) public balanceOf; 17 | mapping (address => mapping (address => uint256)) public allowance; 18 | 19 | // This generates a public event on the blockchain that will notify clients 20 | event Transfer(address indexed from, address indexed to, uint256 value); 21 | 22 | // This generates a public event on the blockchain that will notify clients 23 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 24 | 25 | // This notifies clients about the amount burnt 26 | event Burn(address indexed from, uint256 value); 27 | 28 | /** 29 | * Constructor function 30 | * 31 | * Initializes contract with initial supply tokens to the creator of the contract 32 | */ 33 | constructor( 34 | uint256 initialSupply, 35 | string memory tokenName, 36 | string memory tokenSymbol 37 | ) public { 38 | totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount 39 | balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens 40 | name = tokenName; // Set the name for display purposes 41 | symbol = tokenSymbol; // Set the symbol for display purposes 42 | emit Transfer(address(0), msg.sender, totalSupply); 43 | 44 | } 45 | 46 | /** 47 | * Internal transfer, only can be called by this contract 48 | */ 49 | function _transfer(address _from, address _to, uint _value) internal { 50 | // Prevent transfer to 0x0 address. Use burn() instead 51 | require(_to != address(0x0)); 52 | // Check if the sender has enough 53 | require(balanceOf[_from] >= _value); 54 | // Check for overflows 55 | require(balanceOf[_to] + _value >= balanceOf[_to]); 56 | // Save this for an assertion in the future 57 | uint previousBalances = balanceOf[_from] + balanceOf[_to]; 58 | // Subtract from the sender 59 | balanceOf[_from] -= _value; 60 | // Add the same to the recipient 61 | balanceOf[_to] += _value; 62 | emit Transfer(_from, _to, _value); 63 | // Asserts are used to use static analysis to find bugs in your code. They should never fail 64 | assert(balanceOf[_from] + balanceOf[_to] == previousBalances); 65 | } 66 | 67 | /** 68 | * Transfer tokens 69 | * 70 | * Send `_value` tokens to `_to` from your account 71 | * 72 | * @param _to The address of the recipient 73 | * @param _value the amount to send 74 | */ 75 | function transfer(address _to, uint256 _value) public returns (bool success) { 76 | _transfer(msg.sender, _to, _value); 77 | return true; 78 | } 79 | 80 | /** 81 | * Transfer tokens from other address 82 | * 83 | * Send `_value` tokens to `_to` on behalf of `_from` 84 | * 85 | * @param _from The address of the sender 86 | * @param _to The address of the recipient 87 | * @param _value the amount to send 88 | */ 89 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { 90 | require(_value <= allowance[_from][msg.sender]); // Check allowance 91 | allowance[_from][msg.sender] -= _value; 92 | _transfer(_from, _to, _value); 93 | return true; 94 | } 95 | 96 | /** 97 | * Set allowance for other address 98 | * 99 | * Allows `_spender` to spend no more than `_value` tokens on your behalf 100 | * 101 | * @param _spender The address authorized to spend 102 | * @param _value the max amount they can spend 103 | */ 104 | function approve(address _spender, uint256 _value) public 105 | returns (bool success) { 106 | allowance[msg.sender][_spender] = _value; 107 | emit Approval(msg.sender, _spender, _value); 108 | return true; 109 | } 110 | 111 | /** 112 | * Set allowance for other address and notify 113 | * 114 | * Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it 115 | * 116 | * @param _spender The address authorized to spend 117 | * @param _value the max amount they can spend 118 | * @param _extraData some extra information to send to the approved contract 119 | */ 120 | function approveAndCall(address _spender, uint256 _value, bytes memory _extraData) 121 | public 122 | returns (bool success) { 123 | tokenRecipient spender = tokenRecipient(_spender); 124 | if (approve(_spender, _value)) { 125 | spender.receiveApproval(msg.sender, _value, address(this), _extraData); 126 | return true; 127 | } 128 | } 129 | 130 | /** 131 | * Destroy tokens 132 | * 133 | * Remove `_value` tokens from the system irreversibly 134 | * 135 | * @param _value the amount of money to burn 136 | */ 137 | function burn(uint256 _value) public returns (bool success) { 138 | require(balanceOf[msg.sender] >= _value); // Check if the sender has enough 139 | balanceOf[msg.sender] -= _value; // Subtract from the sender 140 | totalSupply -= _value; // Updates totalSupply 141 | emit Burn(msg.sender, _value); 142 | return true; 143 | } 144 | 145 | /** 146 | * Destroy tokens from other account 147 | * 148 | * Remove `_value` tokens from the system irreversibly on behalf of `_from`. 149 | * 150 | * @param _from the address of the sender 151 | * @param _value the amount of money to burn 152 | */ 153 | function burnFrom(address _from, uint256 _value) public returns (bool success) { 154 | require(balanceOf[_from] >= _value); // Check if the targeted balance is enough 155 | require(_value <= allowance[_from][msg.sender]); // Check allowance 156 | balanceOf[_from] -= _value; // Subtract from the targeted balance 157 | allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance 158 | totalSupply -= _value; // Update totalSupply 159 | emit Burn(_from, _value); 160 | return true; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /scripts/1_deploy_token.py: -------------------------------------------------------------------------------- 1 | from brownie import accounts, config, TokenERC20, EasyToken 2 | from scripts.helpful_scripts import get_account 3 | 4 | 5 | initial_supply = 1000000000000000000000 # 1000 6 | token_name = "Token" 7 | token_symbol = "TKN" 8 | 9 | 10 | def main(): 11 | account = get_account() 12 | erc20 = TokenERC20.deploy( 13 | initial_supply, token_name, token_symbol, {"from": account} 14 | ) 15 | -------------------------------------------------------------------------------- /scripts/2_deploy_easy_token.py: -------------------------------------------------------------------------------- 1 | from brownie import accounts, config, EasyToken 2 | from scripts.helpful_scripts import get_account 3 | 4 | 5 | def main(): 6 | account = get_account() 7 | erc20 = EasyToken.deploy({"from": account}) 8 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickAlphaC/erc20-brownie/d62b4eb61cbc62f6172e1fdf3bed57e87367b5f0/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/helpful_scripts.py: -------------------------------------------------------------------------------- 1 | from brownie import network, accounts, config 2 | 3 | LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["hardhat", "development", "mainnet-fork"] 4 | 5 | 6 | def get_account(): 7 | if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS: 8 | return accounts[0] 9 | if network.show_active() in config["networks"]: 10 | account = accounts.add(config["wallets"]["from_key"]) 11 | return account 12 | return None 13 | -------------------------------------------------------------------------------- /scripts/send_arbitrary_token.py: -------------------------------------------------------------------------------- 1 | from brownie import accounts, config, Contract, EasyToken 2 | from scripts.helpful_scripts import get_account 3 | 4 | 5 | def main(): 6 | account = get_account() 7 | erc20_address = "Insert ERC20 Address here" 8 | recipient = "Insert address of recipient here" 9 | # This will be how many tokens to send in WEI 10 | amount = 1000000000000000000 # 1 token 11 | erc20 = Contract.from_abi("Arbitrary ERC20", erc20_address, abi=EasyToken.abi) 12 | tx = erc20.transfer(recipient, amount, {"from": account}) 13 | tx.wait(1) 14 | --------------------------------------------------------------------------------