├── video.png └── contracts ├── BirdCoin.sol └── BirdCoinCrowdsale.sol /video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Birdchain/birdchain/HEAD/video.png -------------------------------------------------------------------------------- /contracts/BirdCoin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/token/MintableToken.sol"; 4 | import "./BirdCoinCrowdsale.sol"; 5 | 6 | contract BirdCoin is MintableToken { 7 | string public constant name = "BirdCoin"; 8 | string public constant symbol = "BIRD"; 9 | uint8 public constant decimals = 18; 10 | bool private isLocked = true; 11 | mapping (address => uint256) private lockDuration; 12 | BirdCoinCrowdsale private crowdsale; 13 | 14 | function BirdCoin() MintableToken() { 15 | crowdsale = BirdCoinCrowdsale(msg.sender); 16 | } 17 | 18 | // Checks whether it can transfer or otherwise throws. 19 | modifier canTransfer(address _sender, uint _value) { 20 | require(lockDuration[_sender] < now); 21 | require(!isLocked); 22 | _; 23 | } 24 | // Calls withdraw on BirdCoinCrowdsale contract 25 | modifier withdraw(address _owner) { 26 | crowdsale.withdraw(_owner); 27 | _; 28 | } 29 | 30 | // Checks modifier and allows transfer if tokens are not locked. 31 | function transfer(address _to, uint _value) canTransfer(msg.sender, _value) withdraw(msg.sender) returns (bool success) { 32 | return super.transfer(_to, _value); 33 | } 34 | 35 | // Checks modifier and allows transfer if tokens are not locked. 36 | function transferFrom(address _from, address _to, uint _value) canTransfer(_from, _value) withdraw(_from) returns (bool success) { 37 | return super.transferFrom(_from, _to, _value); 38 | } 39 | 40 | // This method is used by Crowdsale contract to avoid recursive crowdsale.withdraw() call 41 | function transferCrowdsale(address _to, uint _value) onlyOwner returns (bool) { 42 | return super.transfer(_to, _value); 43 | } 44 | 45 | function balanceOf(address _owner) public constant returns (uint256 balance) { 46 | return super.balanceOf(_owner).add(crowdsale.calcAdditionalTokens(_owner)); 47 | } 48 | 49 | function lockTill(address addr, uint256 timeTill) onlyOwner { 50 | lockDuration[addr] = timeTill; 51 | } 52 | 53 | function unlockTokens() onlyOwner { 54 | isLocked = false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/BirdCoinCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "zeppelin-solidity/contracts/crowdsale/RefundVault.sol"; 5 | import "./BirdCoin.sol"; 6 | 7 | contract BirdCoinCrowdsale is Ownable { 8 | using SafeMath for uint256; 9 | 10 | address constant FOUNDERS_WALLET = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; // Wallet address where ethereum will be kept 11 | address constant WHITELISTER_WALLET = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; 12 | address constant EARLY_BIRDS_WALLET = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; 13 | address constant TEAM_WALLET_1_YEAR = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; 14 | address constant TEAM_WALLET_2_YEAR = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; 15 | address constant TEAM_WALLET_3_YEAR = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; 16 | address constant TEAM_WALLET_4_YEAR = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; 17 | address constant BOUNTY_WALLET = 0xEA3E63a29e40DAce4559EE4e566655Fab65FEcB9; 18 | uint256 constant RATE = 42000; 19 | uint256 constant MIN_STAKE = 0.1 ether; 20 | uint256 constant MAX_STAKE = 2000 ether; 21 | uint256 constant HARD_CAP = 86000 ether; 22 | uint256 constant SOFT_CAP = 9500 ether; 23 | uint constant SILVER = 1 ether; 24 | uint constant GOLD = 5 ether; 25 | 26 | BirdCoin public token; 27 | RefundVault public vault; 28 | 29 | mapping (address => uint256) private purchasers; 30 | 31 | bool public isFinalized = false; 32 | uint256 public membersCount = 0; 33 | uint256 public weiRaised; 34 | uint256 public icoBalance; 35 | uint256 startTime = 1511017200; 36 | uint256 public endTime = 1514732400; 37 | uint256 public icoBalanceLeft; 38 | uint256 public initialIcoBalance; 39 | uint public currentStage = 0; 40 | 41 | struct Stages { 42 | uint limit; 43 | uint discount; 44 | } 45 | Stages[] stagesList; 46 | mapping (address => uint32) whitelist; 47 | 48 | event TokenPurchase(address indexed beneficiary, uint256 value, uint256 amount, uint256 bonus, uint stage); 49 | 50 | function BirdCoinCrowdsale() { 51 | stagesList.push(Stages({limit: 9500 ether, discount: 150 })); 52 | stagesList.push(Stages({limit: 18000 ether, discount: 145 })); 53 | stagesList.push(Stages({limit: 26500 ether, discount: 140 })); 54 | stagesList.push(Stages({limit: 35000 ether, discount: 135 })); 55 | stagesList.push(Stages({limit: 43500 ether, discount: 130 })); 56 | stagesList.push(Stages({limit: 52000 ether, discount: 125 })); 57 | stagesList.push(Stages({limit: 60500 ether, discount: 120 })); 58 | stagesList.push(Stages({limit: 69000 ether, discount: 115 })); 59 | stagesList.push(Stages({limit: 77500 ether, discount: 110 })); 60 | stagesList.push(Stages({limit: 86000 ether, discount: 105 })); 61 | 62 | require(startTime >= now && endTime >= startTime); 63 | 64 | token = new BirdCoin(); 65 | vault = new RefundVault(FOUNDERS_WALLET); 66 | 67 | icoBalance = 4698750000 ether; 68 | initialIcoBalance = icoBalance; 69 | } 70 | 71 | function () payable { 72 | if (!isFinalized) { 73 | buyTokens(); 74 | } else { 75 | claimRefund(); 76 | } 77 | } 78 | 79 | modifier onlyWhitelister() { 80 | require(msg.sender == WHITELISTER_WALLET); 81 | _; 82 | } 83 | 84 | function addToWhitelist(address addr, uint8 level) public onlyWhitelister { 85 | require(whitelist[addr] == 0 && (level == 1 || level == 5 || level == 2 || level == 6 || level == 10)); 86 | whitelist[addr] = level; 87 | membersCount = membersCount.add(1); 88 | } 89 | 90 | function buyTokens() public payable { 91 | require(validPurchase()); 92 | 93 | weiRaised = weiRaised.add(msg.value); 94 | 95 | uint256 tokens = 0; 96 | uint256 excess = 0; 97 | if (weiRaised >= stagesList[currentStage].limit) { 98 | excess = weiRaised.sub(stagesList[currentStage].limit); 99 | tokens = msg.value.sub(excess).mul(stagesList[currentStage].discount).div(100); 100 | currentStage = currentStage.add(1); 101 | if (currentStage < 10) { 102 | tokens = tokens.add(excess.mul(stagesList[currentStage].discount).div(100)); 103 | excess = 0; 104 | } 105 | } else { 106 | tokens = msg.value.mul(stagesList[currentStage].discount).div(100); 107 | } 108 | 109 | uint256 bonus = 0; 110 | if (whitelist[msg.sender] & 4 == 4 && msg.value >= 0.5 ether) { 111 | bonus = SILVER; 112 | } 113 | 114 | if (whitelist[msg.sender] & 8 == 8 && msg.value >= GOLD) { 115 | bonus = GOLD; 116 | } 117 | 118 | tokens = tokens.add(bonus).mul(RATE); 119 | icoBalance = icoBalance.sub(tokens); 120 | token.mint(msg.sender, tokens); 121 | uint256 value = msg.value.sub(excess); 122 | purchasers[msg.sender] = tokens; 123 | TokenPurchase(msg.sender, value, tokens, bonus, currentStage); 124 | vault.deposit.value(value)(msg.sender); 125 | if (excess > 0) { 126 | weiRaised = weiRaised.sub(excess); 127 | msg.sender.transfer(excess); 128 | } 129 | } 130 | 131 | function validPurchase() internal constant returns (bool) { 132 | bool validAddress = (msg.sender != 0x0); 133 | bool isWhitelisted = (whitelist[msg.sender] & 1 == 1 && msg.value <= 2.5 ether) || whitelist[msg.sender] & 2 == 2; 134 | bool withinPeriod = (now >= startTime && now <= endTime); 135 | bool withinRangePurchase = (msg.value >= MIN_STAKE && msg.value <= MAX_STAKE); 136 | bool isStageValid = currentStage < 10; 137 | return validAddress && withinPeriod && withinRangePurchase && isWhitelisted && isStageValid; 138 | } 139 | 140 | /**************************** Refunds *****************************/ 141 | 142 | function claimRefund() private { 143 | require(isFinalized); 144 | require(!goalReached()); 145 | 146 | vault.refund(msg.sender); 147 | } 148 | 149 | function finalize() onlyOwner public { 150 | require(!isFinalized); 151 | require(now > endTime || HARD_CAP - weiRaised < MIN_STAKE); 152 | 153 | if (goalReached()) { 154 | vault.close(); 155 | token.unlockTokens(); 156 | 157 | bountyReward(); 158 | earlyBirdsReward(); 159 | teamReward(); 160 | foundersReward(); 161 | 162 | token.mint(this, icoBalance); 163 | token.finishMinting(); 164 | icoBalanceLeft = icoBalance; 165 | } else { 166 | vault.enableRefunds(); 167 | } 168 | 169 | isFinalized = true; 170 | } 171 | 172 | function goalReached() private constant returns (bool) { 173 | return weiRaised >= SOFT_CAP; 174 | } 175 | 176 | /********************* Token distribution ********************/ 177 | 178 | function calcAdditionalTokens(address _purchaser) constant public returns (uint256) { 179 | if (purchasers[_purchaser] > 0 && isFinalized && goalReached()) { 180 | uint256 value = purchasers[_purchaser]; 181 | uint256 result = icoBalanceLeft.mul(value).div(initialIcoBalance.sub(icoBalanceLeft)); 182 | return result; 183 | } 184 | 185 | return 0; 186 | } 187 | 188 | function withdraw(address _purchaser) public { 189 | uint256 additionalTokens = calcAdditionalTokens(_purchaser); 190 | if (additionalTokens > 0) { 191 | purchasers[_purchaser] = 0; 192 | token.transferCrowdsale(_purchaser, additionalTokens); 193 | } 194 | } 195 | 196 | function teamReward() private { 197 | uint256 partPerYear = 294000000 ether; 198 | uint yearInSeconds = 31536000; 199 | 200 | token.lockTill(TEAM_WALLET_1_YEAR, startTime.add(yearInSeconds)); 201 | token.mint(TEAM_WALLET_1_YEAR, partPerYear); 202 | 203 | token.lockTill(TEAM_WALLET_2_YEAR, startTime.add(yearInSeconds.mul(2))); 204 | token.mint(TEAM_WALLET_2_YEAR, partPerYear); 205 | 206 | token.lockTill(TEAM_WALLET_3_YEAR, startTime.add(yearInSeconds.mul(3))); 207 | token.mint(TEAM_WALLET_3_YEAR, partPerYear); 208 | 209 | token.lockTill(TEAM_WALLET_4_YEAR, startTime.add(yearInSeconds.mul(4))); 210 | token.mint(TEAM_WALLET_4_YEAR, partPerYear); 211 | } 212 | 213 | function bountyReward() private { 214 | token.mint(BOUNTY_WALLET, 504000000 ether); 215 | } 216 | 217 | function foundersReward() private { 218 | token.lockTill(FOUNDERS_WALLET, startTime.add(63072000)); // 2years 219 | token.mint(FOUNDERS_WALLET, 1092000000 ether); 220 | } 221 | 222 | function earlyBirdsReward() private { 223 | token.mint(EARLY_BIRDS_WALLET, 1932000000 ether); 224 | } 225 | } 226 | --------------------------------------------------------------------------------