├── migrations ├── 1_initial_migration.js └── 2_deploy_sparklecrowdsale.js ├── contracts ├── Migrations.sol ├── SparkleToken.sol ├── SparkleCrowdsale.sol ├── MultiOwnable.sol └── SparkleBaseCrowdsale.sol ├── package.json ├── truffle.js ├── truffle-config.js ├── README.md └── Sparkle-Crowdsale.abi.txt /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /migrations/2_deploy_sparklecrowdsale.js: -------------------------------------------------------------------------------- 1 | const SparkleCrowdsale = artifacts.require('./SparkleCrowdsale'); 2 | const SparkleToken = artifacts.require('./SparkleToken'); 3 | 4 | module.exports = function(deployer, network, accounts) { 5 | return deployer 6 | .then(() => { 7 | // NOTE: If for some reason after deployment and testing you need re-deploy then change false to true 8 | return deployer.deploy(SparkleToken, {overwrite: true}); 9 | }) 10 | .then(() => { 11 | const _tokenAddress = SparkleToken.address; 12 | const _tokenRate = new web3.BigNumber(400e8); 13 | const _tokenCap = new web3.BigNumber(19698000e8); 14 | const _startTime = web3.eth.getBlock('latest').timestamp + 10; 15 | const _endTime = _startTime + 86400 * 1; 16 | const _etherDepositWallet = accounts[5]; 17 | const _kycRequired = true; 18 | 19 | return deployer.deploy(SparkleCrowdsale,_tokenAddress, _tokenRate, _tokenCap, _startTime, _endTime, _etherDepositWallet, _kycRequired); 20 | }); 21 | }; -------------------------------------------------------------------------------- /contracts/SparkleToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.25; 2 | 3 | /** 4 | * @dev OpenZeppelin Solidity v2.0.0 imports (Using: npm openzeppelin-solidity@2.0.0) 5 | */ 6 | import 'openzeppelin-solidity/contracts/token/ERC20/ERC20-debug.sol'; 7 | import 'openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed-debug.sol'; 8 | import 'openzeppelin-solidity/contracts/ownership/Ownable.sol'; 9 | 10 | /** 11 | * @title SparkleToken 12 | * @dev Fully compliant ERC20 Token contract. 13 | * Inherits Ownable, ERC20 and ERC20Detailed from OpenZeppelin-Solidity v2.0.0 14 | */ 15 | contract SparkleToken is Ownable, ERC20, ERC20Detailed { 16 | /** 17 | * @dev ERC20 Compliance variables 18 | */ 19 | string public _tokenName = "Sparkle"; 20 | string public _tokenSymbol = "SPRKL"; 21 | string public _tokenVersion = "v1.00"; 22 | uint8 public _tokenDecimals = 8; 23 | 24 | /** 25 | * @dev Max supply to be minted at Sparkle Token creation 26 | */ 27 | uint256 public _tokenMaxSupply = 70000000 * (10 ** 8); 28 | 29 | /** 30 | * @dev The FFS constructor specifies ERC20 details 31 | * to be used. 32 | */ 33 | constructor() 34 | Ownable() 35 | ERC20() 36 | ERC20Detailed(_tokenName, _tokenSymbol, _tokenDecimals) 37 | public { 38 | /** 39 | * @dev Mint the total maximum supply of tokens at creation time 40 | */ 41 | _mint(owner(), _tokenMaxSupply); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /contracts/SparkleCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.25; 2 | 3 | import './SparkleBaseCrowdsale.sol'; 4 | 5 | contract SparkleCrowdsale is SparkleBaseCrowdsale { 6 | 7 | // Token contract address 8 | //address public initTokenAddress = 0x0; 9 | // Crowdsale specification 10 | //uint256 public initTokenRate = 300 * (10 ** 8); //-> 30000000000 11 | //uint256 public initMaxTokenCap = 19698000 * (10 ** 8); //-> 1969800000000000 12 | // Crowdsale start/end time 13 | //uint256 public initStartTime = now; 14 | //uint256 public initEndTime = now + 12 hours; 15 | // Crowdsale Token allowance expected in TokenWallet (NOTE: Tokens remaining after distributions will be burned!) 16 | //uint256 public initMaxTokenAllowance = 32830000 * (10 ** 8); //-> 3283000000000000 17 | // Administrative addresses 18 | //address public initDepositWallet = 0xA176db55a9F14F1659F384d4e9385Fa8F661E300; // Wallet to receive ether 19 | //address public ibnitTokenWallet = 0x36224915E23B5cF14375F68Fe86509bf2879f081; // Token allowance wallet 20 | 21 | constructor(address _tokenAddress, uint256 _tokenRate, uint256 _tokenCap, uint256 _tokenStartTime, uint256 _tokenEndTime, address _etherDepositWallet, bool _kycRequired) 22 | SparkleBaseCrowdsale(ERC20(_tokenAddress), _tokenRate, _tokenCap, _tokenStartTime, _tokenEndTime, _etherDepositWallet, _kycRequired) 23 | public 24 | { 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v1", 3 | "version": "1.0.0", 4 | "description": "SparkleToken ERC20 contract", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "lint:js": "eslint .", 12 | "lint:js:fix": "eslint . --fix", 13 | "lint:sol": "solium -d .", 14 | "lint:sol:fix": "solium -d . --fix", 15 | "lint": "npm run lint:js && npm run lint:sol", 16 | "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", 17 | "console": "truffle console", 18 | "coverage": "scripts/coverage.sh", 19 | "version": "scripts/version.js" 20 | }, 21 | "keywords": [], 22 | "author": "Sparklemobile", 23 | "license": "MIT", 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "chai": "^4.2.0", 27 | "chai-bignumber": "^3.0.0", 28 | "coveralls": "^3.0.2", 29 | "dotenv": "^6.1.0", 30 | "eslint": "^5.9.0", 31 | "eslint-config-standard": "^12.0.0", 32 | "eslint-plugin-import": "^2.14.0", 33 | "eslint-plugin-node": "^8.0.0", 34 | "eslint-plugin-promise": "^4.0.1", 35 | "eslint-plugin-standard": "^4.0.0", 36 | "ethereumjs-wallet": "^0.6.2", 37 | "ethjs-abi": "^0.2.1", 38 | "ganache-cli": "^6.2.1", 39 | "openzeppelin-solidity": "^2.0.0", 40 | "solidity-coverage": "^0.5.11", 41 | "solium": "^1.1.8", 42 | "truffle": "^4.1.14", 43 | "truffle-hdwallet-provider": "0.0.6", 44 | "truffle-wallet-provider": "0.0.5", 45 | "web3-utils": "^1.0.0-beta.36" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | // Using dotenv 2 | require('dotenv').config(); 3 | 4 | // Build and configure the logic for the HDWalletProvider regardless of being infura or otherwise 5 | const HDWalletProvider = require('truffle-hdwallet-provider'); 6 | const mnemonicProvider = (mnemonic, rpcEndpoint) => new HDWalletProvider(mnemonic, rpcEndpoint); 7 | const infuraProvider = network => mnemonicProvider(process.env.MNEMONIC || '', `https://${network}.infura.io/v3/${process.env.INFURA_APIKEY}`); 8 | 9 | // Following create the mainnet and ropsten providers for infura 10 | const ropstenProvider = infuraProvider('ropsten'); 11 | const mainnetProvider = infuraProvider('mainnet'); 12 | 13 | /** 14 | * @title module.exports 15 | * @dev Truffle configuration sections 16 | * 17 | * Network: 18 | * - development: Deployment path on port 8545 (geth/ganache/rpc) 19 | * - ganachecli: Deployment path on port 7545 (ganache-cli) 20 | * - ropten: Deployment on the Ethereum ropsten test network (powered-by infura) 21 | * - mainnet: Deployment on the Ethereum main network (powered-by infura) 22 | * 23 | * rpc: 24 | * - host: server to connect to for rpc connection 25 | * - port: server port used to accept rpc connections 26 | * 27 | * Solc 28 | * - optimizer: should the optimizer be used (true/false) 29 | * - runs: number of times the optimizer shold be run 30 | */ 31 | module.exports = { 32 | networks: { 33 | development: { 34 | host: 'localhost', 35 | port: 8545, 36 | network_id: '*', // eslint-disable-line camelcase 37 | }, 38 | ganachecli: { 39 | host: 'localhost', 40 | port: 7545, 41 | network_id: '*', // eslint-disable-line camelcase 42 | }, 43 | ropsten: { 44 | provider: ropstenProvider, 45 | network_id: 3, 46 | }, 47 | mainnet: { 48 | provider: mainnetProvider, 49 | network_id: 1, 50 | }, 51 | }, 52 | rpc: { 53 | host: "127.0.0.1", 54 | port: 8080 55 | }, 56 | solc: { 57 | optimizer: { 58 | enabled: true, 59 | runs: 200 60 | } 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | // Using dotenv 2 | require('dotenv').config(); 3 | 4 | // Build and configure the logic for the HDWalletProvider regardless of being infura or otherwise 5 | const HDWalletProvider = require('truffle-hdwallet-provider'); 6 | const mnemonicProvider = (mnemonic, rpcEndpoint) => new HDWalletProvider(mnemonic, rpcEndpoint); 7 | const infuraProvider = network => mnemonicProvider(process.env.MNEMONIC || '', `https://${network}.infura.io/v3/${process.env.INFURA_APIKEY}`); 8 | 9 | // Following create the mainnet and ropsten providers for infura 10 | const ropstenProvider = infuraProvider('ropsten'); 11 | const mainnetProvider = infuraProvider('mainnet'); 12 | 13 | /** 14 | * @title module.exports 15 | * @dev Truffle configuration sections 16 | * 17 | * Network: 18 | * - development: Deployment path on port 8545 (geth/ganache/rpc) 19 | * - ganachecli: Deployment path on port 7545 (ganache-cli) 20 | * - ropten: Deployment on the Ethereum ropsten test network (powered-by infura) 21 | * - mainnet: Deployment on the Ethereum main network (powered-by infura) 22 | * 23 | * rpc: 24 | * - host: server to connect to for rpc connection 25 | * - port: server port used to accept rpc connections 26 | * 27 | * Solc 28 | * - optimizer: should the optimizer be used (true/false) 29 | * - runs: number of times the optimizer shold be run 30 | */ 31 | module.exports = { 32 | networks: { 33 | development: { 34 | host: 'localhost', 35 | port: 8545, 36 | network_id: '*', // eslint-disable-line camelcase 37 | }, 38 | ganachecli: { 39 | host: 'localhost', 40 | port: 7545, 41 | network_id: '*', // eslint-disable-line camelcase 42 | }, 43 | ropsten: { 44 | provider: ropstenProvider, 45 | network_id: 3, 46 | }, 47 | mainnet: { 48 | provider: mainnetProvider, 49 | network_id: 1, 50 | }, 51 | }, 52 | rpc: { 53 | host: "127.0.0.1", 54 | port: 8080 55 | }, 56 | solc: { 57 | optimizer: { 58 | enabled: true, 59 | runs: 200 60 | } 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Sparkle Crowdsale 4 | 5 | ![Badge](https://img.shields.io/badge/OpenZepplin-2.0-blue.svg)  ![Badge](https://img.shields.io/badge/EIPs-ERC--20%20-blue.svg) ![badge](https://img.shields.io/badge/Crowdsale-Closed-red)  [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Sparklemobile%20a%20new%20peer%20to%20peer%20currency%20dedicated%20to%20connecting%20users%20with%20quality%20automotive%20detailing.%20New%20Hybrid%20Cloud%20Proof%20of%20Liquidity%20Protocol%20&url=https://sparklemobile.io/tokensale.html&via=Sparkletoken&hashtags=Developers,ERC20,PoL,Protocol,SPRKL,Sparkle) [![Follow](https://img.shields.io/twitter/follow/Sparkletoken.svg?label=Follow&style=social)](https://twitter.com/Sparkletoken) 6 | 7 | 8 | 9 | ### Sparkle Dashboard 10 | Sparkle's crowdsale and dashboard were built for full functionally for all users across multiple platforms. 11 | 12 | ### Compatable purchase platforms 13 | 14 | * [Metamask](https://metamask.io/) ![Badge](https://img.shields.io/badge/Status%20-recommended%20-brightgreen.svg) 15 | * [MyEtherWallet](https://www.myetherwallet.com/#generate-wallet) ![Badge](https://img.shields.io/badge/Status%20-recommended%20-brightgreen.svg) 16 | * [Trust Wallet Mobile Ethereum Browser](https://links.trustwalletapp.com/yiklM0CGBT) ![Badge](https://img.shields.io/badge/status%20-recommended%20-brightgreen.svg) 17 | * [CoinBase Mobile Ethereum Browser](https://play.google.com/store/apps/details?id=org.toshi&hl=en_US) ![Badge](https://img.shields.io/badge/status-use%20with%20caution%20-yellow.svg) 18 | * [Kcash Mobile Ethereum Browser](https://play.google.com/store/apps/details?id=com.kcashpro.wallet) ![Badge](https://img.shields.io/badge/status-use%20with%20caution%20-yellow.svg) 19 | * [Imtoken Mobile Ethereum Browser](https://play.google.com/store/apps/details?id=im.token.app) ![Badge](https://img.shields.io/badge/status-use%20with%20caution%20-yellow.svg) 20 | * [Cipher Mobile Ethereum Browser](https://play.google.com/store/apps/details?id=com.cipherbrowser.cipher) ![Badge](https://img.shields.io/badge/status-use%20with%20caution%20-yellow.svg) 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /contracts/MultiOwnable.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @title MultiOwnable.sol 3 | * @dev Provide multi-ownable functionality to a smart contract. 4 | * @dev Note this contract preserves the idea of a master owner where this owner 5 | * cannot be removed or deleted. Master owner's are the only owner's who can add 6 | * and remove other owner's. Transfer of master ownership is supported and can 7 | * also only be transferred by the current master owner 8 | * @dev When master ownership is transferred the original master owner is not 9 | * removed from the additional owners list 10 | */ 11 | pragma solidity 0.4.25; 12 | 13 | /** 14 | * @dev OpenZeppelin Solidity v2.0.0 imports (Using: npm openzeppelin-solidity@2.0.0) 15 | */ 16 | import 'openzeppelin-solidity/contracts/ownership/Ownable.sol'; 17 | 18 | contract MultiOwnable is Ownable { 19 | /** 20 | * @dev Mapping of additional addresses that are considered owners 21 | */ 22 | mapping (address => bool) additionalOwners; 23 | 24 | /** 25 | * @dev Modifier that overrides 'Ownable' to support multiple owners 26 | */ 27 | modifier onlyOwner() { 28 | // Ensure that msg.sender is an owner or revert 29 | require(isOwner(msg.sender), "Permission denied [owner]."); 30 | _; 31 | } 32 | 33 | /** 34 | * @dev Modifier that provides additional testing to ensure msg.sender 35 | * is master owner, or first address to deploy contract 36 | */ 37 | modifier onlyMaster() { 38 | // Ensure that msg.sender is the master user 39 | require(super.isOwner(), "Permission denied [master]."); 40 | _; 41 | } 42 | 43 | /** 44 | * @dev Ownership added event for Dapps interested in this event 45 | */ 46 | event OwnershipAdded ( 47 | address indexed addedOwner 48 | ); 49 | 50 | /** 51 | * @dev Ownership removed event for Dapps interested in this event 52 | */ 53 | event OwnershipRemoved ( 54 | address indexed removedOwner 55 | ); 56 | 57 | /** 58 | * @dev MultiOwnable .cTor responsible for initialising the masterOwner 59 | * or contract super-user 60 | * @dev The super user cannot be deleted from the ownership mapping and 61 | * can only be transferred 62 | */ 63 | constructor() 64 | Ownable() 65 | public 66 | { 67 | // Obtain owner of the contract (msg.sender) 68 | address masterOwner = owner(); 69 | // Add the master owner to the additional owners list 70 | additionalOwners[masterOwner] = true; 71 | } 72 | 73 | /** 74 | * @dev Returns the owner status of the specified address 75 | */ 76 | function isOwner(address _ownerAddressToLookup) 77 | public 78 | view 79 | returns (bool) 80 | { 81 | // Return the ownership state of the specified owner address 82 | return additionalOwners[_ownerAddressToLookup]; 83 | } 84 | 85 | /** 86 | * @dev Returns the master status of the specfied address 87 | */ 88 | function isMaster(address _masterAddressToLookup) 89 | public 90 | view 91 | returns (bool) 92 | { 93 | return (super.owner() == _masterAddressToLookup); 94 | } 95 | 96 | /** 97 | * @dev Add a new owner address to additional owners mapping 98 | * @dev Only the master owner can add additional owner addresses 99 | */ 100 | function addOwner(address _ownerToAdd) 101 | onlyMaster 102 | public 103 | returns (bool) 104 | { 105 | // Ensure the new owner address is not address(0) 106 | require(_ownerToAdd != address(0), "Invalid address specified (0x0)"); 107 | // Ensure that new owner address is not already in the owners list 108 | require(!isOwner(_ownerToAdd), "Address specified already in owners list."); 109 | // Add new owner to additional owners mapping 110 | additionalOwners[_ownerToAdd] = true; 111 | emit OwnershipAdded(_ownerToAdd); 112 | return true; 113 | } 114 | 115 | /** 116 | * @dev Add a new owner address to additional owners mapping 117 | * @dev Only the master owner can add additional owner addresses 118 | */ 119 | function removeOwner(address _ownerToRemove) 120 | onlyMaster 121 | public 122 | returns (bool) 123 | { 124 | // Ensure that the address to remove is not the master owner 125 | require(_ownerToRemove != super.owner(), "Permission denied [master]."); 126 | // Ensure that owner address to remove is actually an owner 127 | require(isOwner(_ownerToRemove), "Address specified not found in owners list."); 128 | // Add remove ownership from address in the additional owners mapping 129 | additionalOwners[_ownerToRemove] = false; 130 | emit OwnershipRemoved(_ownerToRemove); 131 | return true; 132 | } 133 | 134 | /** 135 | * @dev Transfer ownership of this contract to another address 136 | * @dev Only the master owner can transfer ownership to another address 137 | * @dev Only existing owners can have ownership transferred to them 138 | */ 139 | function transferOwnership(address _newOwnership) 140 | onlyMaster 141 | public 142 | { 143 | // Ensure the new ownership is not address(0) 144 | require(_newOwnership != address(0), "Invalid address specified (0x0)"); 145 | // Ensure the new ownership address is not the current ownership addressess 146 | require(_newOwnership != owner(), "Address specified must not match current owner address."); 147 | // Ensure that the new ownership is promoted from existing owners 148 | require(isOwner(_newOwnership), "Master ownership can only be transferred to an existing owner address."); 149 | // Call into the parent class and transfer ownership 150 | super.transferOwnership(_newOwnership); 151 | // If we get here, then add the new ownership address to the additional owners mapping 152 | // Note that the original master owner address was not removed and is still an owner until removed 153 | additionalOwners[_newOwnership] = true; 154 | } 155 | 156 | } -------------------------------------------------------------------------------- /Sparkle-Crowdsale.abi.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [ 7 | { 8 | "constant": false, 9 | "inputs": [ 10 | { 11 | "name": "_ownerToAdd", 12 | "type": "address" 13 | } 14 | ], 15 | "name": "addOwner", 16 | "outputs": [ 17 | { 18 | "name": "", 19 | "type": "bool" 20 | } 21 | ], 22 | "payable": false, 23 | "stateMutability": "nonpayable", 24 | "type": "function" 25 | }, 26 | { 27 | "constant": false, 28 | "inputs": [ 29 | { 30 | "name": "account", 31 | "type": "address" 32 | } 33 | ], 34 | "name": "addPauser", 35 | "outputs": [], 36 | "payable": false, 37 | "stateMutability": "nonpayable", 38 | "type": "function" 39 | }, 40 | { 41 | "constant": false, 42 | "inputs": [], 43 | "name": "approveRemainingTokenRefund", 44 | "outputs": [], 45 | "payable": false, 46 | "stateMutability": "nonpayable", 47 | "type": "function" 48 | }, 49 | { 50 | "constant": false, 51 | "inputs": [ 52 | { 53 | "name": "_addressesForApproval", 54 | "type": "address[]" 55 | } 56 | ], 57 | "name": "bulkApproveKYCAddresses", 58 | "outputs": [], 59 | "payable": false, 60 | "stateMutability": "nonpayable", 61 | "type": "function" 62 | }, 63 | { 64 | "constant": false, 65 | "inputs": [ 66 | { 67 | "name": "_addressesToRevoke", 68 | "type": "address[]" 69 | } 70 | ], 71 | "name": "bulkRevokeKYCAddresses", 72 | "outputs": [], 73 | "payable": false, 74 | "stateMutability": "nonpayable", 75 | "type": "function" 76 | }, 77 | { 78 | "constant": false, 79 | "inputs": [ 80 | { 81 | "name": "beneficiary", 82 | "type": "address" 83 | } 84 | ], 85 | "name": "buyTokens", 86 | "outputs": [], 87 | "payable": true, 88 | "stateMutability": "payable", 89 | "type": "function" 90 | }, 91 | { 92 | "constant": false, 93 | "inputs": [ 94 | { 95 | "name": "_newStageValue", 96 | "type": "uint256" 97 | } 98 | ], 99 | "name": "changeCrowdsaleStage", 100 | "outputs": [], 101 | "payable": false, 102 | "stateMutability": "nonpayable", 103 | "type": "function" 104 | }, 105 | { 106 | "constant": false, 107 | "inputs": [], 108 | "name": "claimTokens", 109 | "outputs": [], 110 | "payable": false, 111 | "stateMutability": "nonpayable", 112 | "type": "function" 113 | }, 114 | { 115 | "constant": false, 116 | "inputs": [], 117 | "name": "pause", 118 | "outputs": [], 119 | "payable": false, 120 | "stateMutability": "nonpayable", 121 | "type": "function" 122 | }, 123 | { 124 | "constant": false, 125 | "inputs": [ 126 | { 127 | "name": "_addressToRefund", 128 | "type": "address" 129 | } 130 | ], 131 | "name": "refundRemainingTokens", 132 | "outputs": [], 133 | "payable": false, 134 | "stateMutability": "nonpayable", 135 | "type": "function" 136 | }, 137 | { 138 | "constant": false, 139 | "inputs": [ 140 | { 141 | "name": "_ownerToRemove", 142 | "type": "address" 143 | } 144 | ], 145 | "name": "removeOwner", 146 | "outputs": [ 147 | { 148 | "name": "", 149 | "type": "bool" 150 | } 151 | ], 152 | "payable": false, 153 | "stateMutability": "nonpayable", 154 | "type": "function" 155 | }, 156 | { 157 | "constant": false, 158 | "inputs": [], 159 | "name": "renounceOwnership", 160 | "outputs": [], 161 | "payable": false, 162 | "stateMutability": "nonpayable", 163 | "type": "function" 164 | }, 165 | { 166 | "constant": false, 167 | "inputs": [], 168 | "name": "renouncePauser", 169 | "outputs": [], 170 | "payable": false, 171 | "stateMutability": "nonpayable", 172 | "type": "function" 173 | }, 174 | { 175 | "constant": false, 176 | "inputs": [ 177 | { 178 | "name": "_newOwnership", 179 | "type": "address" 180 | } 181 | ], 182 | "name": "transferOwnership", 183 | "outputs": [], 184 | "payable": false, 185 | "stateMutability": "nonpayable", 186 | "type": "function" 187 | }, 188 | { 189 | "constant": false, 190 | "inputs": [], 191 | "name": "unpause", 192 | "outputs": [], 193 | "payable": false, 194 | "stateMutability": "nonpayable", 195 | "type": "function" 196 | }, 197 | { 198 | "inputs": [], 199 | "payable": false, 200 | "stateMutability": "nonpayable", 201 | "type": "constructor" 202 | }, 203 | { 204 | "payable": true, 205 | "stateMutability": "payable", 206 | "type": "fallback" 207 | }, 208 | { 209 | "anonymous": false, 210 | "inputs": [ 211 | { 212 | "indexed": true, 213 | "name": "_appovedByAddress", 214 | "type": "address" 215 | }, 216 | { 217 | "indexed": false, 218 | "name": "_numberOfApprovals", 219 | "type": "uint256" 220 | } 221 | ], 222 | "name": "ApprovedKYCAddresses", 223 | "type": "event" 224 | }, 225 | { 226 | "anonymous": false, 227 | "inputs": [ 228 | { 229 | "indexed": true, 230 | "name": "_revokedByAddress", 231 | "type": "address" 232 | }, 233 | { 234 | "indexed": false, 235 | "name": "_numberOfRevokals", 236 | "type": "uint256" 237 | } 238 | ], 239 | "name": "RevokedKYCAddresses", 240 | "type": "event" 241 | }, 242 | { 243 | "anonymous": false, 244 | "inputs": [ 245 | { 246 | "indexed": true, 247 | "name": "_claimingAddress", 248 | "type": "address" 249 | }, 250 | { 251 | "indexed": false, 252 | "name": "_tokensClaimed", 253 | "type": "uint256" 254 | } 255 | ], 256 | "name": "TokensClaimed", 257 | "type": "event" 258 | }, 259 | { 260 | "anonymous": false, 261 | "inputs": [ 262 | { 263 | "indexed": true, 264 | "name": "_beneficiary", 265 | "type": "address" 266 | }, 267 | { 268 | "indexed": false, 269 | "name": "_tokensSold", 270 | "type": "uint256" 271 | } 272 | ], 273 | "name": "TokensSold", 274 | "type": "event" 275 | }, 276 | { 277 | "anonymous": false, 278 | "inputs": [ 279 | { 280 | "indexed": true, 281 | "name": "_approvingAddress", 282 | "type": "address" 283 | }, 284 | { 285 | "indexed": false, 286 | "name": "tokenBurnApproved", 287 | "type": "bool" 288 | } 289 | ], 290 | "name": "TokenRefundApprovalChanged", 291 | "type": "event" 292 | }, 293 | { 294 | "anonymous": false, 295 | "inputs": [ 296 | { 297 | "indexed": true, 298 | "name": "_changingAddress", 299 | "type": "address" 300 | }, 301 | { 302 | "indexed": false, 303 | "name": "_newStageValue", 304 | "type": "uint256" 305 | } 306 | ], 307 | "name": "CrowdsaleStageChanged", 308 | "type": "event" 309 | }, 310 | { 311 | "anonymous": false, 312 | "inputs": [ 313 | { 314 | "indexed": true, 315 | "name": "_refundingToAddress", 316 | "type": "address" 317 | }, 318 | { 319 | "indexed": false, 320 | "name": "_numberOfTokensBurned", 321 | "type": "uint256" 322 | } 323 | ], 324 | "name": "CrowdsaleTokensRefunded", 325 | "type": "event" 326 | }, 327 | { 328 | "anonymous": false, 329 | "inputs": [ 330 | { 331 | "indexed": true, 332 | "name": "purchaser", 333 | "type": "address" 334 | }, 335 | { 336 | "indexed": true, 337 | "name": "beneficiary", 338 | "type": "address" 339 | }, 340 | { 341 | "indexed": false, 342 | "name": "value", 343 | "type": "uint256" 344 | }, 345 | { 346 | "indexed": false, 347 | "name": "amount", 348 | "type": "uint256" 349 | } 350 | ], 351 | "name": "TokensPurchased", 352 | "type": "event" 353 | }, 354 | { 355 | "anonymous": false, 356 | "inputs": [ 357 | { 358 | "indexed": false, 359 | "name": "account", 360 | "type": "address" 361 | } 362 | ], 363 | "name": "Paused", 364 | "type": "event" 365 | }, 366 | { 367 | "anonymous": false, 368 | "inputs": [ 369 | { 370 | "indexed": false, 371 | "name": "account", 372 | "type": "address" 373 | } 374 | ], 375 | "name": "Unpaused", 376 | "type": "event" 377 | }, 378 | { 379 | "anonymous": false, 380 | "inputs": [ 381 | { 382 | "indexed": true, 383 | "name": "account", 384 | "type": "address" 385 | } 386 | ], 387 | "name": "PauserAdded", 388 | "type": "event" 389 | }, 390 | { 391 | "anonymous": false, 392 | "inputs": [ 393 | { 394 | "indexed": true, 395 | "name": "account", 396 | "type": "address" 397 | } 398 | ], 399 | "name": "PauserRemoved", 400 | "type": "event" 401 | }, 402 | { 403 | "anonymous": false, 404 | "inputs": [ 405 | { 406 | "indexed": true, 407 | "name": "addedOwner", 408 | "type": "address" 409 | } 410 | ], 411 | "name": "OwnershipAdded", 412 | "type": "event" 413 | }, 414 | { 415 | "anonymous": false, 416 | "inputs": [ 417 | { 418 | "indexed": true, 419 | "name": "removedOwner", 420 | "type": "address" 421 | } 422 | ], 423 | "name": "OwnershipRemoved", 424 | "type": "event" 425 | }, 426 | { 427 | "anonymous": false, 428 | "inputs": [ 429 | { 430 | "indexed": true, 431 | "name": "previousOwner", 432 | "type": "address" 433 | }, 434 | { 435 | "indexed": true, 436 | "name": "newOwner", 437 | "type": "address" 438 | } 439 | ], 440 | "name": "OwnershipTransferred", 441 | "type": "event" 442 | }, 443 | { 444 | "constant": true, 445 | "inputs": [], 446 | "name": "closingTime", 447 | "outputs": [ 448 | { 449 | "name": "", 450 | "type": "uint256" 451 | } 452 | ], 453 | "payable": false, 454 | "stateMutability": "view", 455 | "type": "function" 456 | }, 457 | { 458 | "constant": true, 459 | "inputs": [ 460 | { 461 | "name": "_addressToLookup", 462 | "type": "address" 463 | } 464 | ], 465 | "name": "contributionAmount", 466 | "outputs": [ 467 | { 468 | "name": "", 469 | "type": "uint256" 470 | } 471 | ], 472 | "payable": false, 473 | "stateMutability": "view", 474 | "type": "function" 475 | }, 476 | { 477 | "constant": true, 478 | "inputs": [], 479 | "name": "crowdsaleStage", 480 | "outputs": [ 481 | { 482 | "name": "", 483 | "type": "uint8" 484 | } 485 | ], 486 | "payable": false, 487 | "stateMutability": "view", 488 | "type": "function" 489 | }, 490 | { 491 | "constant": true, 492 | "inputs": [], 493 | "name": "depositWallet", 494 | "outputs": [ 495 | { 496 | "name": "", 497 | "type": "address" 498 | } 499 | ], 500 | "payable": false, 501 | "stateMutability": "view", 502 | "type": "function" 503 | }, 504 | { 505 | "constant": true, 506 | "inputs": [], 507 | "name": "endTime", 508 | "outputs": [ 509 | { 510 | "name": "", 511 | "type": "uint256" 512 | } 513 | ], 514 | "payable": false, 515 | "stateMutability": "view", 516 | "type": "function" 517 | }, 518 | { 519 | "constant": true, 520 | "inputs": [ 521 | { 522 | "name": "_weiAmount", 523 | "type": "uint256" 524 | } 525 | ], 526 | "name": "getExchangeRate", 527 | "outputs": [ 528 | { 529 | "name": "", 530 | "type": "uint256" 531 | } 532 | ], 533 | "payable": false, 534 | "stateMutability": "view", 535 | "type": "function" 536 | }, 537 | { 538 | "constant": true, 539 | "inputs": [], 540 | "name": "getRemainingTokens", 541 | "outputs": [ 542 | { 543 | "name": "", 544 | "type": "uint256" 545 | } 546 | ], 547 | "payable": false, 548 | "stateMutability": "view", 549 | "type": "function" 550 | }, 551 | { 552 | "constant": true, 553 | "inputs": [], 554 | "name": "hasClosed", 555 | "outputs": [ 556 | { 557 | "name": "", 558 | "type": "bool" 559 | } 560 | ], 561 | "payable": false, 562 | "stateMutability": "view", 563 | "type": "function" 564 | }, 565 | { 566 | "constant": true, 567 | "inputs": [], 568 | "name": "initDepositWallet", 569 | "outputs": [ 570 | { 571 | "name": "", 572 | "type": "address" 573 | } 574 | ], 575 | "payable": false, 576 | "stateMutability": "view", 577 | "type": "function" 578 | }, 579 | { 580 | "constant": true, 581 | "inputs": [], 582 | "name": "initEndTime", 583 | "outputs": [ 584 | { 585 | "name": "", 586 | "type": "uint256" 587 | } 588 | ], 589 | "payable": false, 590 | "stateMutability": "view", 591 | "type": "function" 592 | }, 593 | { 594 | "constant": true, 595 | "inputs": [], 596 | "name": "initKYCRequired", 597 | "outputs": [ 598 | { 599 | "name": "", 600 | "type": "bool" 601 | } 602 | ], 603 | "payable": false, 604 | "stateMutability": "view", 605 | "type": "function" 606 | }, 607 | { 608 | "constant": true, 609 | "inputs": [], 610 | "name": "initStartTime", 611 | "outputs": [ 612 | { 613 | "name": "", 614 | "type": "uint256" 615 | } 616 | ], 617 | "payable": false, 618 | "stateMutability": "view", 619 | "type": "function" 620 | }, 621 | { 622 | "constant": true, 623 | "inputs": [], 624 | "name": "initTokenAddress", 625 | "outputs": [ 626 | { 627 | "name": "", 628 | "type": "address" 629 | } 630 | ], 631 | "payable": false, 632 | "stateMutability": "view", 633 | "type": "function" 634 | }, 635 | { 636 | "constant": true, 637 | "inputs": [], 638 | "name": "initTokenCap", 639 | "outputs": [ 640 | { 641 | "name": "", 642 | "type": "uint256" 643 | } 644 | ], 645 | "payable": false, 646 | "stateMutability": "view", 647 | "type": "function" 648 | }, 649 | { 650 | "constant": true, 651 | "inputs": [], 652 | "name": "initTokenRate", 653 | "outputs": [ 654 | { 655 | "name": "", 656 | "type": "uint256" 657 | } 658 | ], 659 | "payable": false, 660 | "stateMutability": "view", 661 | "type": "function" 662 | }, 663 | { 664 | "constant": true, 665 | "inputs": [ 666 | { 667 | "name": "_addressToLookuo", 668 | "type": "address" 669 | } 670 | ], 671 | "name": "isKYCVerified", 672 | "outputs": [ 673 | { 674 | "name": "", 675 | "type": "bool" 676 | } 677 | ], 678 | "payable": false, 679 | "stateMutability": "view", 680 | "type": "function" 681 | }, 682 | { 683 | "constant": true, 684 | "inputs": [ 685 | { 686 | "name": "_masterAddressToLookup", 687 | "type": "address" 688 | } 689 | ], 690 | "name": "isMaster", 691 | "outputs": [ 692 | { 693 | "name": "", 694 | "type": "bool" 695 | } 696 | ], 697 | "payable": false, 698 | "stateMutability": "view", 699 | "type": "function" 700 | }, 701 | { 702 | "constant": true, 703 | "inputs": [], 704 | "name": "isOpen", 705 | "outputs": [ 706 | { 707 | "name": "", 708 | "type": "bool" 709 | } 710 | ], 711 | "payable": false, 712 | "stateMutability": "view", 713 | "type": "function" 714 | }, 715 | { 716 | "constant": true, 717 | "inputs": [ 718 | { 719 | "name": "_ownerAddressToLookup", 720 | "type": "address" 721 | } 722 | ], 723 | "name": "isOwner", 724 | "outputs": [ 725 | { 726 | "name": "", 727 | "type": "bool" 728 | } 729 | ], 730 | "payable": false, 731 | "stateMutability": "view", 732 | "type": "function" 733 | }, 734 | { 735 | "constant": true, 736 | "inputs": [], 737 | "name": "isOwner", 738 | "outputs": [ 739 | { 740 | "name": "", 741 | "type": "bool" 742 | } 743 | ], 744 | "payable": false, 745 | "stateMutability": "view", 746 | "type": "function" 747 | }, 748 | { 749 | "constant": true, 750 | "inputs": [ 751 | { 752 | "name": "account", 753 | "type": "address" 754 | } 755 | ], 756 | "name": "isPauser", 757 | "outputs": [ 758 | { 759 | "name": "", 760 | "type": "bool" 761 | } 762 | ], 763 | "payable": false, 764 | "stateMutability": "view", 765 | "type": "function" 766 | }, 767 | { 768 | "constant": true, 769 | "inputs": [], 770 | "name": "kycRequired", 771 | "outputs": [ 772 | { 773 | "name": "", 774 | "type": "bool" 775 | } 776 | ], 777 | "payable": false, 778 | "stateMutability": "view", 779 | "type": "function" 780 | }, 781 | { 782 | "constant": true, 783 | "inputs": [], 784 | "name": "openingTime", 785 | "outputs": [ 786 | { 787 | "name": "", 788 | "type": "uint256" 789 | } 790 | ], 791 | "payable": false, 792 | "stateMutability": "view", 793 | "type": "function" 794 | }, 795 | { 796 | "constant": true, 797 | "inputs": [], 798 | "name": "owner", 799 | "outputs": [ 800 | { 801 | "name": "", 802 | "type": "address" 803 | } 804 | ], 805 | "payable": false, 806 | "stateMutability": "view", 807 | "type": "function" 808 | }, 809 | { 810 | "constant": true, 811 | "inputs": [], 812 | "name": "paused", 813 | "outputs": [ 814 | { 815 | "name": "", 816 | "type": "bool" 817 | } 818 | ], 819 | "payable": false, 820 | "stateMutability": "view", 821 | "type": "function" 822 | }, 823 | { 824 | "constant": true, 825 | "inputs": [], 826 | "name": "rate", 827 | "outputs": [ 828 | { 829 | "name": "", 830 | "type": "uint256" 831 | } 832 | ], 833 | "payable": false, 834 | "stateMutability": "view", 835 | "type": "function" 836 | }, 837 | { 838 | "constant": true, 839 | "inputs": [], 840 | "name": "refundRemainingOk", 841 | "outputs": [ 842 | { 843 | "name": "", 844 | "type": "bool" 845 | } 846 | ], 847 | "payable": false, 848 | "stateMutability": "view", 849 | "type": "function" 850 | }, 851 | { 852 | "constant": true, 853 | "inputs": [], 854 | "name": "startTime", 855 | "outputs": [ 856 | { 857 | "name": "", 858 | "type": "uint256" 859 | } 860 | ], 861 | "payable": false, 862 | "stateMutability": "view", 863 | "type": "function" 864 | }, 865 | { 866 | "constant": true, 867 | "inputs": [], 868 | "name": "token", 869 | "outputs": [ 870 | { 871 | "name": "", 872 | "type": "address" 873 | } 874 | ], 875 | "payable": false, 876 | "stateMutability": "view", 877 | "type": "function" 878 | }, 879 | { 880 | "constant": true, 881 | "inputs": [], 882 | "name": "tokenAddress", 883 | "outputs": [ 884 | { 885 | "name": "", 886 | "type": "address" 887 | } 888 | ], 889 | "payable": false, 890 | "stateMutability": "view", 891 | "type": "function" 892 | }, 893 | { 894 | "constant": true, 895 | "inputs": [], 896 | "name": "tokenCap", 897 | "outputs": [ 898 | { 899 | "name": "", 900 | "type": "uint256" 901 | } 902 | ], 903 | "payable": false, 904 | "stateMutability": "view", 905 | "type": "function" 906 | }, 907 | { 908 | "constant": true, 909 | "inputs": [], 910 | "name": "tokenRate", 911 | "outputs": [ 912 | { 913 | "name": "", 914 | "type": "uint256" 915 | } 916 | ], 917 | "payable": false, 918 | "stateMutability": "view", 919 | "type": "function" 920 | }, 921 | { 922 | "constant": true, 923 | "inputs": [ 924 | { 925 | "name": "_addressToLookup", 926 | "type": "address" 927 | } 928 | ], 929 | "name": "tokensPending", 930 | "outputs": [ 931 | { 932 | "name": "", 933 | "type": "uint256" 934 | } 935 | ], 936 | "payable": false, 937 | "stateMutability": "view", 938 | "type": "function" 939 | }, 940 | { 941 | "constant": true, 942 | "inputs": [], 943 | "name": "tokensSold", 944 | "outputs": [ 945 | { 946 | "name": "", 947 | "type": "uint256" 948 | } 949 | ], 950 | "payable": false, 951 | "stateMutability": "view", 952 | "type": "function" 953 | }, 954 | { 955 | "constant": true, 956 | "inputs": [], 957 | "name": "wallet", 958 | "outputs": [ 959 | { 960 | "name": "", 961 | "type": "address" 962 | } 963 | ], 964 | "payable": false, 965 | "stateMutability": "view", 966 | "type": "function" 967 | }, 968 | { 969 | "constant": true, 970 | "inputs": [], 971 | "name": "weiRaised", 972 | "outputs": [ 973 | { 974 | "name": "", 975 | "type": "uint256" 976 | } 977 | ], 978 | "payable": false, 979 | "stateMutability": "view", 980 | "type": "function" 981 | } 982 | ] 983 | -------------------------------------------------------------------------------- /contracts/SparkleBaseCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.25; 2 | 3 | import './MultiOwnable.sol'; 4 | import 'openzeppelin-solidity/contracts/lifecycle/Pausable.sol'; 5 | import 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol'; 6 | import 'openzeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol'; 7 | 8 | /** 9 | * @dev SparkelBaseCrowdsale: Core crowdsale functionality 10 | */ 11 | contract SparkleBaseCrowdsale is MultiOwnable, Pausable, TimedCrowdsale { 12 | using SafeMath for uint256; 13 | 14 | /** 15 | * @dev CrowdsaleStage enumeration indicating which operational stage this contract is running 16 | */ 17 | enum CrowdsaleStage { 18 | preICO, 19 | bonusICO, 20 | mainICO 21 | } 22 | 23 | /** 24 | * @dev Internal contract variable stored 25 | */ 26 | ERC20 public tokenAddress; 27 | uint256 public tokenRate; 28 | uint256 public tokenCap; 29 | uint256 public startTime; 30 | uint256 public endTime; 31 | address public depositWallet; 32 | bool public kycRequired; 33 | bool public refundRemainingOk; 34 | 35 | uint256 public tokensSold; 36 | 37 | /** 38 | * @dev Contribution structure representing a token purchase 39 | */ 40 | struct OrderBook { 41 | uint256 weiAmount; // Amount of Wei that has been contributed towards tokens by this address 42 | uint256 pendingTokens; // Total pending tokens held by this address waiting for KYC verification, and user to claim their tokens(pending restrictions) 43 | bool kycVerified; // Has this address been kyc validated 44 | } 45 | 46 | // Contributions mapping to user addresses 47 | mapping(address => OrderBook) private orders; 48 | 49 | // Initialize the crowdsale stage to preICO (this stage will change) 50 | CrowdsaleStage public crowdsaleStage = CrowdsaleStage.preICO; 51 | 52 | /** 53 | * @dev Event signaling that a number of addresses have been approved for KYC 54 | */ 55 | event ApprovedKYCAddresses (address indexed _appovedByAddress, uint256 _numberOfApprovals); 56 | 57 | /** 58 | * @dev Event signaling that a number of addresses have been revoked from KYC 59 | */ 60 | event RevokedKYCAddresses (address indexed _revokedByAddress, uint256 _numberOfRevokals); 61 | 62 | /** 63 | * @dev Event signalling that tokens have been claimed from the crowdsale 64 | */ 65 | event TokensClaimed (address indexed _claimingAddress, uint256 _tokensClaimed); 66 | 67 | /** 68 | * @dev Event signaling that tokens were sold and how many were sold 69 | */ 70 | event TokensSold(address indexed _beneficiary, uint256 _tokensSold); 71 | 72 | /** 73 | * @dev Event signaling that toke burn approval has been changed 74 | */ 75 | event TokenRefundApprovalChanged(address indexed _approvingAddress, bool tokenBurnApproved); 76 | 77 | /** 78 | * @dev Event signaling that token burn approval has been changed 79 | */ 80 | event CrowdsaleStageChanged(address indexed _changingAddress, uint _newStageValue); 81 | 82 | /** 83 | * @dev Event signaling that crowdsale tokens have been burned 84 | */ 85 | event CrowdsaleTokensRefunded(address indexed _refundingToAddress, uint256 _numberOfTokensBurned); 86 | 87 | /** 88 | * @dev SparkleTokenCrowdsale Contract contructor 89 | */ 90 | constructor(ERC20 _tokenAddress, uint256 _tokenRate, uint256 _tokenCap, uint256 _startTime, uint256 _endTime, address _depositWallet, bool _kycRequired) 91 | public 92 | Crowdsale(_tokenRate, _depositWallet, _tokenAddress) 93 | TimedCrowdsale(_startTime, _endTime) 94 | MultiOwnable() 95 | Pausable() 96 | { 97 | tokenAddress = _tokenAddress; 98 | tokenRate = _tokenRate; 99 | tokenCap = _tokenCap; 100 | startTime = _startTime; 101 | endTime = _endTime; 102 | depositWallet = _depositWallet; 103 | kycRequired = _kycRequired; 104 | refundRemainingOk = false; 105 | } 106 | 107 | /** 108 | * @dev claimPendingTokens() provides users with a function to receive their purchase tokens 109 | * after their KYC Verification 110 | */ 111 | function claimTokens() 112 | whenNotPaused 113 | onlyWhileOpen 114 | public 115 | { 116 | // Ensure calling address is not address(0) 117 | require(msg.sender != address(0), "Invalid address specified: address(0)"); 118 | // Obtain a copy of the caller's order record 119 | OrderBook storage order = orders[msg.sender]; 120 | // Ensure caller has been KYC Verified 121 | require(order.kycVerified, "Address attempting to claim tokens is not KYC Verified."); 122 | // Ensure caller has pending tokens to claim 123 | require(order.pendingTokens > 0, "Address does not have any pending tokens to claim."); 124 | // For security sake grab the pending token value 125 | uint256 localPendingTokens = order.pendingTokens; 126 | // zero out pendingTokens to prevent potential re-entrancy vulnverability 127 | order.pendingTokens = 0; 128 | // Deliver the callers tokens 129 | _deliverTokens(msg.sender, localPendingTokens); 130 | // Emit event 131 | emit TokensClaimed(msg.sender, localPendingTokens); 132 | } 133 | 134 | /** 135 | * @dev getExchangeRate() provides a public facing manner in which to 136 | * determine the current rate of exchange in the crowdsale 137 | * @param _weiAmount is the amount of wei to purchase tokens with 138 | * @return number of tokens the specified wei amount would purchase 139 | */ 140 | function getExchangeRate(uint256 _weiAmount) 141 | whenNotPaused 142 | onlyWhileOpen 143 | public 144 | view 145 | returns (uint256) 146 | { 147 | if (crowdsaleStage == CrowdsaleStage.preICO) { 148 | // Ensure _weiAmount is > than current stage minimum 149 | require(_weiAmount >= 1 ether, "PreICO minimum ether required: 1 ETH."); 150 | } 151 | else if (crowdsaleStage == CrowdsaleStage.bonusICO || crowdsaleStage == CrowdsaleStage.mainICO) { 152 | // Ensure _weiAmount is > than current stage minimum 153 | require(_weiAmount >= 500 finney, "bonusICO/mainICO minimum ether required: 0.5 ETH."); 154 | } 155 | 156 | // Calculate the number of tokens this amount of wei is worth 157 | uint256 tokenAmount = _getTokenAmount(_weiAmount); 158 | // Ensure the number of tokens requests will not exceed available tokens 159 | require(getRemainingTokens() >= tokenAmount, "Specified wei value woudld exceed amount of tokens remaining."); 160 | // Calculate and return the token amount this amount of wei is worth (includes bonus factor) 161 | return tokenAmount; 162 | } 163 | 164 | /** 165 | * @dev getRemainingTokens() provides function to return the current remaining token count 166 | * @return number of tokens remaining in the crowdsale to be sold 167 | */ 168 | function getRemainingTokens() 169 | whenNotPaused 170 | public 171 | view 172 | returns (uint256) 173 | { 174 | // Return the balance of the contract (IE: tokenCap - tokensSold) 175 | return tokenCap.sub(tokensSold); 176 | } 177 | 178 | /** 179 | * @dev refundRemainingTokens provides functionn to refund remaining tokens to the specified address 180 | * @param _addressToRefund is the address in which the remaining tokens will be refunded to 181 | */ 182 | function refundRemainingTokens(address _addressToRefund) 183 | onlyOwner 184 | whenNotPaused 185 | public 186 | { 187 | // Ensure the specified address is not address(0) 188 | require(_addressToRefund != address(0), "Specified address is invalid [0x0]"); 189 | // Ensure the crowdsale has closed before burning tokens 190 | require(hasClosed(), "Crowdsale must be finished to burn tokens."); 191 | // Ensure that step-1 of the burning process is satisfied (owner set to true) 192 | require(refundRemainingOk, "Crowdsale remaining token refund is disabled."); 193 | uint256 tempBalance = token().balanceOf(this); 194 | // Transfer the remaining tokens to specified address 195 | _deliverTokens(_addressToRefund, tempBalance); 196 | // Emit event 197 | emit CrowdsaleTokensRefunded(_addressToRefund, tempBalance); 198 | } 199 | 200 | /** 201 | * @dev approveRemainingTokenRefund approves the function to withdraw any remaining tokens 202 | * after the crowdsale ends 203 | * @dev This was put in place as a two-step process to burn tokens so burning was secure 204 | */ 205 | function approveRemainingTokenRefund() 206 | onlyOwner 207 | whenNotPaused 208 | public 209 | { 210 | // Ensure calling address is not address(0) 211 | require(msg.sender != address(0), "Calling address invalid [0x0]"); 212 | // Ensure the crowdsale has closed before approving token burning 213 | require(hasClosed(), "Token burn approval can only be set after crowdsale closes"); 214 | refundRemainingOk = true; 215 | emit TokenRefundApprovalChanged(msg.sender, refundRemainingOk); 216 | } 217 | 218 | /** 219 | * @dev setStage() sets the current crowdsale stage to the specified value 220 | * @param _newStageValue is the new stage to be changed to 221 | */ 222 | function changeCrowdsaleStage(uint _newStageValue) 223 | onlyOwner 224 | whenNotPaused 225 | onlyWhileOpen 226 | public 227 | { 228 | // Create temporary stage variable 229 | CrowdsaleStage _stage; 230 | // Determine if caller is trying to set: preICO 231 | if (uint(CrowdsaleStage.preICO) == _newStageValue) { 232 | // Set the internal stage to the new value 233 | _stage = CrowdsaleStage.preICO; 234 | } 235 | // Determine if caller is trying to set: bonusICO 236 | else if (uint(CrowdsaleStage.bonusICO) == _newStageValue) { 237 | // Set the internal stage to the new value 238 | _stage = CrowdsaleStage.bonusICO; 239 | } 240 | // Determine if caller is trying to set: mainICO 241 | else if (uint(CrowdsaleStage.mainICO) == _newStageValue) { 242 | // Set the internal stage to the new value 243 | _stage = CrowdsaleStage.mainICO; 244 | } 245 | else { 246 | revert("Invalid stage selected"); 247 | } 248 | 249 | // Update the internal crowdsale stage to the new stage 250 | crowdsaleStage = _stage; 251 | // Emit event 252 | emit CrowdsaleStageChanged(msg.sender, uint(_stage)); 253 | } 254 | 255 | /** 256 | * @dev isAddressKYCVerified() checks the KYV Verification status of the specified address 257 | * @param _addressToLookuo address to check status of KYC Verification 258 | * @return kyc status of the specified address 259 | */ 260 | function isKYCVerified(address _addressToLookuo) 261 | whenNotPaused 262 | onlyWhileOpen 263 | public 264 | view 265 | returns (bool) 266 | { 267 | // Ensure _addressToLookuo is not address(0) 268 | require(_addressToLookuo != address(0), "Invalid address specified: address(0)"); 269 | // Obtain the addresses order record 270 | OrderBook storage order = orders[_addressToLookuo]; 271 | // Return the JYC Verification status for the specified address 272 | return order.kycVerified; 273 | } 274 | 275 | /** 276 | * @dev Approve in bulk the specified addfresses indicating they were KYC Verified 277 | * @param _addressesForApproval is a list of addresses that are to be KYC Verified 278 | */ 279 | function bulkApproveKYCAddresses(address[] _addressesForApproval) 280 | onlyOwner 281 | whenNotPaused 282 | onlyWhileOpen 283 | public 284 | { 285 | 286 | // Ensure that there are any address(es) in the provided array 287 | require(_addressesForApproval.length > 0, "Specified address array is empty"); 288 | // Interate through all addresses provided 289 | for (uint i = 0; i <_addressesForApproval.length; i++) { 290 | // Approve this address using the internal function 291 | _approveKYCAddress(_addressesForApproval[i]); 292 | } 293 | 294 | // Emit event indicating address(es) have been approved for KYC Verification 295 | emit ApprovedKYCAddresses(msg.sender, _addressesForApproval.length); 296 | } 297 | 298 | /** 299 | * @dev Revoke in bulk the specified addfresses indicating they were denied KYC Verified 300 | * @param _addressesToRevoke is a list of addresses that are to be KYC Verified 301 | */ 302 | function bulkRevokeKYCAddresses(address[] _addressesToRevoke) 303 | onlyOwner 304 | whenNotPaused 305 | onlyWhileOpen 306 | public 307 | { 308 | // Ensure that there are any address(es) in the provided array 309 | require(_addressesToRevoke.length > 0, "Specified address array is empty"); 310 | // Interate through all addresses provided 311 | for (uint i = 0; i <_addressesToRevoke.length; i++) { 312 | // Approve this address using the internal function 313 | _revokeKYCAddress(_addressesToRevoke[i]); 314 | } 315 | 316 | // Emit event indicating address(es) have been revoked for KYC Verification 317 | emit RevokedKYCAddresses(msg.sender, _addressesToRevoke.length); 318 | } 319 | 320 | /** 321 | * @dev tokensPending() provides owners the function to retrieve an addresses pending 322 | * token amount 323 | * @param _addressToLookup is the address to return the pending token value for 324 | * @return the number of pending tokens waiting to be claimed from specified address 325 | */ 326 | function tokensPending(address _addressToLookup) 327 | onlyOwner 328 | whenNotPaused 329 | onlyWhileOpen 330 | public 331 | view 332 | returns (uint256) 333 | { 334 | // Ensure specified address is not address(0) 335 | require(_addressToLookup != address(0), "Specified address is invalid [0x0]"); 336 | // Obtain the order for specified address 337 | OrderBook storage order = orders[_addressToLookup]; 338 | // Return the pendingTokens amount 339 | return order.pendingTokens; 340 | } 341 | 342 | /** 343 | * @dev contributionAmount() provides owners the function to retrieve an addresses total 344 | * contribution amount in eth 345 | * @param _addressToLookup is the address to return the contribution amount value for 346 | * @return the number of ether contribured to the crowdsale by specified address 347 | */ 348 | function contributionAmount(address _addressToLookup) 349 | onlyOwner 350 | whenNotPaused 351 | onlyWhileOpen 352 | public 353 | view 354 | returns (uint256) 355 | { 356 | // Ensure specified address is not address(0) 357 | require(_addressToLookup != address(0), "Specified address is Invalid [0x0]"); 358 | // Obtain the order for specified address 359 | OrderBook storage order = orders[_addressToLookup]; 360 | // Return the contribution amount in wei 361 | return order.weiAmount; 362 | } 363 | 364 | /** 365 | * @dev _approveKYCAddress provides the function to approve the specified address 366 | * indicating KYC Verified 367 | * @param _addressToApprove of the user that is being verified 368 | */ 369 | function _approveKYCAddress(address _addressToApprove) 370 | onlyOwner 371 | internal 372 | { 373 | // Ensure that _addressToApprove is not address(0) 374 | require(_addressToApprove != address(0), "Invalid address specified: address(0)"); 375 | // Get this addesses contribution record 376 | OrderBook storage order = orders[_addressToApprove]; 377 | // Set the contribution record to indicate address has been kyc verified 378 | order.kycVerified = true; 379 | } 380 | 381 | /** 382 | * @dev _revokeKYCAddress() provides the function to revoke previously 383 | * granted KYC verification in cases of fraud or false/invalid KYC data 384 | * @param _addressToRevoke is the address to remove KYC verification from 385 | */ 386 | function _revokeKYCAddress(address _addressToRevoke) 387 | onlyOwner 388 | internal 389 | { 390 | // Ensure address is not address(0) 391 | require(_addressToRevoke != address(0), "Invalid address specified: address(0)"); 392 | // Obtain a copy of this addresses contribution record 393 | OrderBook storage order = orders[_addressToRevoke]; 394 | // Revoke this addresses KYC verification 395 | order.kycVerified = false; 396 | } 397 | 398 | /** 399 | * @dev _rate() provides the function of calcualting the rate based on crowdsale stage 400 | * @param _weiAmount indicated the amount of ether intended to use for purchase 401 | * @return number of tokens worth based on specified Wei value 402 | */ 403 | function _rate(uint _weiAmount) 404 | internal 405 | view 406 | returns (uint256) 407 | { 408 | require(_weiAmount > 0, "Specified wei amoount must be > 0"); 409 | 410 | // Determine if the current operation stage of the crowdsale is preICO 411 | if (crowdsaleStage == CrowdsaleStage.preICO) 412 | { 413 | // Determine if the purchase is >= 21 ether 414 | if (_weiAmount >= 21 ether) { // 20% bonus 415 | return 480e8; 416 | } 417 | 418 | // Determine if the purchase is >= 11 ether 419 | if (_weiAmount >= 11 ether) { // 15% bonus 420 | return 460e8; 421 | } 422 | 423 | // Determine if the purchase is >= 5 ether 424 | if (_weiAmount >= 5 ether) { // 10% bonus 425 | return 440e8; 426 | } 427 | 428 | } 429 | else 430 | // Determine if the current operation stage of the crowdsale is bonusICO 431 | if (crowdsaleStage == CrowdsaleStage.bonusICO) 432 | { 433 | // Determine if the purchase is >= 21 ether 434 | if (_weiAmount >= 21 ether) { // 10% bonus 435 | return 440e8; 436 | } 437 | else if (_weiAmount >= 11 ether) { // 7% bonus 438 | return 428e8; 439 | } 440 | else 441 | if (_weiAmount >= 5 ether) { // 5% bonus 442 | return 420e8; 443 | } 444 | 445 | } 446 | 447 | // Rate is either < bounus or is main sale so return base rate only 448 | return rate(); 449 | } 450 | 451 | /** 452 | * @dev Performs token to wei converstion calculations based on crowdsale specification 453 | * @param _weiAmount to spend 454 | * @return number of tokens purchasable for the specified _weiAmount at crowdsale stage rates 455 | */ 456 | function _getTokenAmount(uint256 _weiAmount) 457 | whenNotPaused 458 | internal 459 | view 460 | returns (uint256) 461 | { 462 | // Get the current rate set in the constructor and calculate token units per wei 463 | uint256 currentRate = _rate(_weiAmount); 464 | // Calculate the total number of tokens buyable at based rate (before adding bonus) 465 | uint256 sparkleToBuy = currentRate.mul(_weiAmount).div(10e17); 466 | // Return proposed token amount 467 | return sparkleToBuy; 468 | } 469 | 470 | /** 471 | * @dev 472 | * @param _beneficiary is the address that is currently purchasing tokens 473 | * @param _weiAmount is the number of tokens this address is attempting to purchase 474 | */ 475 | function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) 476 | whenNotPaused 477 | internal 478 | view 479 | { 480 | // Call into the parent validation to ensure _beneficiary and _weiAmount are valid 481 | super._preValidatePurchase(_beneficiary, _weiAmount); 482 | // Calculate amount of tokens for the specified _weiAmount 483 | uint256 requestedTokens = getExchangeRate(_weiAmount); 484 | // Calculate the currently sold tokens 485 | uint256 tempTotalTokensSold = tokensSold; 486 | // Incrememt total tokens 487 | tempTotalTokensSold.add(requestedTokens); 488 | // Ensure total max token cap is > tempTotalTokensSold 489 | require(tempTotalTokensSold <= tokenCap, "Requested wei amount will exceed the max token cap and was not accepted."); 490 | // Ensure that requested tokens will not go over the remaining token balance 491 | require(requestedTokens <= getRemainingTokens(), "Requested tokens would exceed tokens available and was not accepted."); 492 | // Obtain the order record for _beneficiary if one exists 493 | OrderBook storage order = orders[_beneficiary]; 494 | // Ensure this address has been kyc validated 495 | require(order.kycVerified, "Address attempting to purchase is not KYC Verified."); 496 | // Update this addresses order to reflect the purchase and ether spent 497 | order.weiAmount = order.weiAmount.add(_weiAmount); 498 | order.pendingTokens = order.pendingTokens.add(requestedTokens); 499 | // increment totalTokens sold 500 | tokensSold = tokensSold.add(requestedTokens); 501 | // Emit event 502 | emit TokensSold(_beneficiary, requestedTokens); 503 | } 504 | 505 | /** 506 | * @dev _processPurchase() is overridden and will be called by OpenZep v2.0 internally 507 | * @param _beneficiary is the address that is currently purchasing tokens 508 | * @param _tokenAmount is the number of tokens this address is attempting to purchase 509 | */ 510 | function _processPurchase(address _beneficiary, uint256 _tokenAmount) 511 | whenNotPaused 512 | internal 513 | { 514 | // We do not call the base class _processPurchase() functions. This is needed here or the base 515 | // classes function will be called. 516 | } 517 | 518 | } 519 | 520 | /* 521 | Notes: 522 | ====== 523 | 524 | 1) "tokenCap" has no real effect except to help calculate remaining tokens 525 | - Limit on purchase is based on how many tokens remain after purchases (including claimed and unclaimed tokens) 526 | - Make sure to send the same amount of tokens to the contract as declared by the tokenCap static variable 527 | (ex: 19698000e8) 528 | 529 | 2) Finalizing crowdsale 530 | - when crowdsale is finished a refund of the remaining tokens is possible 531 | - Obtaining the refund is a two-step process 532 | 1) call approveRemainingTokenRefund() *can only be called once crowdsale has ended* 533 | 2) call refundRemainingTokens(address) *can only be called once crowdsale has ended and refund approved (previous step) 534 | 535 | 3) Sending eth to contract (aKa: purchase tokens) 536 | - testing on ganache test network works out to about 200k units of gas to run purchase code (any not used is refunded to the caller) 537 | - IMPORTANT: Have noticed the odd time when there is enough gas but not enough gas (ethereum wtf?) it will say the tx 538 | succeeded, it will take the eth and send it to the deposit wallet, however it will not actually update 539 | the order book to reflect their purchase. I have not looked at tx's here deeply to see if there is enough 540 | data to faclitate a refund to someone should this happen, and why I was concerend with having an unlimited 541 | upped cap like we do. it is a lot to lose. Anyhow I said my peace, you said yours, and this is how it is, 542 | unlimited upper limit to purchase tokens. 543 | - RECOMMENDED: 200000 units of gas should be sent when trying to buy tokens to ensure full processing 544 | - NOTE: Unable to replicate the IMPORTANT issue in any manner consistantly enough to know how to fix it. 545 | 546 | 4) Owner loopup, pendingTokens, and weiAmount 547 | - the orderbook mapping was made private to not disclose to much information abotu other addesses easily 548 | - added owner function tokensPending(address) and contributionAmount(address) to obtain this data easily for owner(s) 549 | 550 | 5) Remaining tokens 551 | - remaining tokens was changed a bit and now reflects all purchased tokens (it previously only listed claimed tokens as sold) 552 | - remaining tokens now uses tokenCap and tokensSold to determine how many tokens are remaining in the sale 553 | - call getRemainingTokens() to obtain the number of remaining tokens 554 | - NOTE: again, tokensSold reflects the number of tokens sold not just tokens claimed from being sold(ewps) 555 | 556 | 6) KYC IS REQUIRED 557 | - While the crowdsale contract COULD be used without and why I built it into it, it defaults to KYC being required 558 | for any token purchases, as well as claiming any pending tokens (for cases when KYC needs to be revoked preventing 559 | tokens from being claimed) 560 | 561 | 7) Bulk Approval/Revokal 562 | - array format truffle: [0xAddress, 0xAddress, 0xAddress] 563 | - array format remix: ["0xAddress", "0xAddress", "0xAdress"] 564 | - NOTE: Top limit or max number of addresses in the array has not be tested and no idea what the max might be 565 | 566 | 8) Exchange rate 567 | - Exchangerate has been precalculated now and based upon 400 sparkle per 1 ETH 568 | - NOTE: Underlying rate is still used but only to define the base rate with no bonus (stage2, or purchase < bonus minimum) 569 | This means that it still needs to be specified by the tokenRate static variable however tokenRate is not used 570 | in calculations to determine bonus amounts (to save code/gas/make more simple). 571 | - Exchange rate returns number of tokens including any bonus calculated based on crowdsale stage 572 | - Ex: 5eth purchase @ stage0 = 2200 Sparkle tokens (5 x 400 + stage0bonus) 573 | - changeCrowdsaleStage(0) -> PreICO 574 | - changeCrowdsaleStage(1) -> BonusICO 575 | - changeCrowdsaleStage(2) -> MainICO 576 | 577 | 9) Crowdsale stages 578 | - Are manually set by an owner 579 | - To be set to match public annoumcement structure 580 | 581 | */ 582 | 583 | 584 | 585 | 586 | --------------------------------------------------------------------------------