Ethereum Tutorials

ICO TOOLKIT

OUR ICO

Writing Ethereum Crowd Sale Contract

ERC20 contract contains all the functionality necessary to buy and sell it using another contract (for example, an online exchange). An online exchange is a complex tool, mostly due to the need to implement "trading glass" - an algorithm for figuring out the current market price. Creating such a tool requires research and planning and definitely is not an everyday task.

However, there is a case when writing an online exchange becomes easy: just imagine that we KNOW what the price should be as we set it ourselves. This is what an ICO contract is for: to sell ERC20 tokens at a predefined price.

In this tutorial we will walk through a typical Solidity contract for an ICO Crowdsale, produced by an ICO Generator tool.

Dependencies

When an ICO Generator creates a script, it adds few contracts to it. All of them, except for the ICO contract itself and a helper contract called ShareHolder, were described in the previous section when we discussed an ERC20 contract. Below, these contracts are listed as outlines, with "..." instead of the code.

// --- - Safe Math
library SafeMath 
{
	... // discussed in a prev. chapter
}

// --- - ERC20 Interface
contract ERC20Interface
{
    ...
}

// --- - ERC20 Token
contract ERC20Token is ERC20Interface
{
    ...  
}

// --- - Ownable
contract Ownable 
{
	...
}

// --- - Mintable
contract MintableToken is ERC20Token, Ownable 
{
    ...
}

// --- - My Token
contract MyToken is MintableToken 
{
	...
}

ShareHolder Contract

ShareHolder is not a mandatory part of ICO Crowdsale contract, and can be ommitted.

ShareHolder contract is described in a ShareHolder Users Guide. The contract itself can be accessed using our Web Site as ShareHolder Contract.

As the tutorial provides detailed description of the ShareHolder contract, here we are only touching it briefly.

You can instruct other contracts to send part of their profit to ShareHolder contract, and it will keep that profit as it grows. "Shares" of shareHolder can be purchased and sold, they work as cumulative bank accounts, which means their price can only increase. So this is just a tool to distribute profit among share holders.

In ICO Generator, you can choose to include ShareHolder support to your ICO and distribute shares to each token buyer, like an extra bonus. Once again, this is not mandatory.

// --- ShareHolder forward declaration ---
contract ShareHolder
{
    function distributeBonusShares(address, uint, string) public;
    function undistributeBonusShares(string) public;
}
// --- End of ShareHolder forward declaration ---

ICO Contract

This contract manages the crowd sale, keeps track of tokens and so on. As was mentioned above, it is a simplified online exchange.

We are going to walk through the contract, providing necessary explanations to the code. But before doing it, we need to understand better the options we have.

You have already seen one option: should ShareHolder contract be included in our Crowd Sale contract. Here are few other options (ICO Generator will create code with different options, depending on options you checked in a form describing the contract you want to generate. Same code can be written manually, of course):

. Obviously, you would prefer to replace "MyToken" with something meaningful.

Decimals. This is for formatting purposes only: how many digits should be displayed for us, humans. Should it be 1 token or 1.23432456 tokens?

Should we keep contracts together?

Token in the same file or in a different one? When creating ICO Crowd Sale contract, we have two choices. We can place the token in the same file where the Crowd Sale contract is, or in a different file. Having token in the same file makes it easy to call its functions, but why should we have a Crowd Sale contract that lasts only about one month together with a Token, that is intended to be used for decades?

Also, if a CrowdSale contract is allowed to accept different tokens, we'll be able, in theory, to reuse the Crowd Sale Contract for more than one ICO (though no one does it).

Let's compare the two cases:

Same files Different files
interface MyToken 
{
    // add any functions you call from ICO
    function finishMinting() public returns (bool); 
    function mint(address _to, uint256 _amount) 
        public returns (bool); 
    function totalSupply() constant 
        public returns (uint256);
    function transfer(address receiver, 
        uint amount) public;
    function balanceOf( address _address ) 
        public returns(uint256); 
}
contract ICO is Ownable 
{
    ...    
    MyToken public m_token;
contract ICO is Ownable 
{
    ...
    MyToken public m_token;
    address m_addrTokenAddress = 0x00;
    function ICO() public 
    {
        ...
        m_token = new MyToken();
        ...
    }
    ...
    function ICO() public 
    {
        ...
        m_token = MyToken(m_addrTokenAddress);
        ...    
    }
    ...
    function getTokenAddress() 
        constant public returns (address) 
    {
        return address(m_token);
    }                        

As you can see, the difference is in the way we call the Token (by name or by address) and access it (accessing with address requires forward declaration).

Additional options available for ICO contract

These features are relatively simple and will be explained in details leter when we walk through the code. Here we are only going to explain what they do.

Can we pause the Crowd Sale?

A valuable feature in case something not ready and you learn it when it is too late. This feature blocks the sale of tokens until pause is over. Note that it works well with the next feature called "Can extend Crowdsale time".

Can we extend the Crowdsale duration?"

As we mentioned already, this feature is very handy when the crowd sale was paused and some time was lost. Also, sometimes things go too slow and extra time can save the campaign even if there was no pause in Crowd Sale.

Has Bonus Tokens. This feature allows you to distribute up to apredefined number of tokens to a beneficiary. For example, you may want to reward the developers team, or participants of your Bounty Campaign.

Use Whitelist. Whitelisting is used if for some reasons you want to sell tokens to qualified buyers only. Usually it comes together with KYC (Know Your Customer) data collection. In our code it is done by assigning a "whitelister", an address that can call function to "whitelist" people. Whitelisting itself works by setting/removing limits, max. number of ether one can spend.

Walking through the code

Let's walk through the code of an ICO contract, providing the necessary explanations. The contract we choose includes all the features and it has a Token contract in a separate file.

ShareHolder forward declaration: as we use ShareHolder contract and it is located in a different file (technically, it does not have to be YOUR contract, you can interact with any contract in a block chain) we need to tell our contract what functions to expect. We only need to list functions that we are going to call.

					
contract ShareHolder
{
    function distributeBonusShares(address, uint, string) public;
    function undistributeBonusShares(string) public;
}

We have changed the name of a Token from MyToken to MySuperToken, just to prove that's possible. As with ShareHolder, the Token is located in a different file, so we need to provide a forward declaration of all functions we are going to use from our ICO contract:

interface MySuperToken 
{
    // add any functions you call from ICO
	function finishMinting() public returns (bool); 
	function mint(address _to, uint256 _amount) public returns (bool); 
	function totalSupply() constant public returns (uint256);
    function transfer(address receiver, uint amount) public;
    function balanceOf( address _address ) public returns(uint256); 
}

We have changed the name of an ICO contract to MySuper ICO, just to prove it is possible. This is a main contract of our crowd sale, and it is derived from Ownable.

                
contract MySuperICO is Ownable 
{
    using SafeMath for uint256;
    
    MySuperToken public m_token;
    address m_addrTokenAddress = 0x009876;

Tokens can be sold or distributed as a reward (for example, among a team of developers and testers). There should be a max. number of tokets that the contract is allowed to distribute:

    
uint256 m_nMaxNumOfTokensAllowedToDistribute = 999;

The following code is part of our support for whitelisting.

    
mapping (string => uint256) public m_mapLevels;		// For unlim use '1 tether'
mapping (address => string) public m_mapWhitelist;	// addr =>"Gold", "Silver"...
address public m_addrWhiteLister = 0x6543;

First of all, we have created mapping called m_mapLevels. It contains a list of "levels", max. amounts people are allowed to spend on tokens. This mapping is filled in a constructor:

... Constructor, see below ...
m_mapLevels[0] = '1 ether';
m_mapLevels[1] = '1 tether';

It looks like in the list above, there are two kinds of people: first "level" can spend up to 1 ether, the second level is pretty much unlimited (1 tether is too much to be considered as a realistic amount).

If you use ICO Generator tool, you can alter the list on "Levels" tab, by editing a structure that will be used to alter the ICO contract's code when you press the "Update Levels" button. Of course you can also do it manually by editing the code in a constructor.

As we have decided to include ShareHolder support in our ICO, we have to provide the contract address. Also we need to know how many shares tokens should qualify to get one share (m_nNumOfTokensPerShare) and what is the name of our ICO campaign from ShareHolder's point of view.

The last parameter is required because ShareHolder contract can, in theory, receive payments from multiple sources. It keeps records of all transactions of course, so the m_strCampaignName is used for book keeping purposes. When you look through ShareHolder's stats, you will be able to find out who made a deposit.

    
address m_addrShareHolder = address(0);      // addr. of ShareHolder Profit Manager
ShareHolder m_contractShareHolder;
uint m_nNumOfTokensPerShare = 1;    // How many tokens should you buy to get one share
string m_strCampaignName = "MySuperICO";

Variables holding flagsof contract's state are self-explanatory.

   
mapping(address => uint256) public m_mapBalanceOf;
bool m_bPaused = false;
bool public m_bRefunding = false;    // Crowdsale ended, refund in progress
uint256 public m_nWeiRefunded;        // The total number of wei refunded.

A Crowd Sale campaign is usually divided to stages. For example, people buying tokens during the first week of a campaign may get a signifficant discount. Also, it is a good idea to store sale statistics in the same place (i.e. attached to dates interval):

// Crowd sale stages and goals
struct SaleOptions
{
    // Something like "WAIT_PRESALE", "PRESALE", "WAIT_SALE", "SALE_1", "SALE_2", "SALE_3", "WAIT_REFUND"
    // The last one (REFUND) should not be added - it is assumed that last one is "REFUND".
    // Also note that Crowdsale starts IMMEDIATELY. If you want a delay, add first item as "WAIT_PRESALE"
    // and set its m_bTradingAllowed to false.
    string status;    
    uint256 timeEnd;
    uint256 tokenPrice; 
    uint256 nWeiRaised;
    uint256 nTokensSold;
    bool tradingAllowed;
}
SaleOptions[] m_saleOptions;
uint m_nCurrentStatusIdx = 0;    // Index of array element in m_saleOptions
uint256 m_nTotalAmountRaised = 0;

Same as with whitelisting levels, crowdsale stages are filled in a constructor. And same as whitelisting levels, crowdsale stages can be altered in ICO Generator in the "Prices" tab:

Crowd Sale usually sets goals: a minimum goal is an amount required for crowd sale to be considered successful (and if not, return money) and a max. goal - an amount when people say "no, thanks". As saying "no, thanks" to money is not easy, the usual approach is to set low min. goal (let's make sure we get something) and max. goal is set unreallistically high (100,000,000,000 ether and we say "enough"). But of course, there are exceptions.

    
    // If not reached, issue a refund
    uint256 m_nMinGoal = 100 ether;
    // If reached, force finish of a campaign. If 0, no upper cup.
    uint256 m_nMaxGoal = 2000 ether;

If ICO is successful, funds are transfered to beneficiaries. There can be more than one receiver, for example, owner, developers (note: this is another way of rewarding developers, in addition to giving them free tokens) etc.

    // --- Receiving wallets (owner, dev. team etc.)
    
    // Wallet to receive the contract's balance once the sale
    // finishes and the minimum goal is met.
    struct Wallet
    {
        string strWalletName;    // "Owner", "Dev. Team"...
        address addrWallet;      // Address to transfer to
        uint nTokens;            // How many tokens to transfer (or 0)
        uint nPercentOfTokens;   // Percent of issued tokens to transfer (100000 based) (or 0)
        uint nWei;               // Money to transfer (or 0)
        uint nPercentWei;        // Percent of raised to transfer (100000 based) (or 0)
    }
    Wallet[] m_arrReceivingWallets;

Wallets are assigned in a constructor and can be edited in ICO Generator same way we edited prices and levels:

Another self-explanatory parameter:

    bool public m_bFinished = false;

Events

Events in Solidity are used for logging purpose. In other words, you can write information to block chain using events, but you can not access this information from your contract. However this information is accessible from Web site that has an access to block chain, and it is much cheaper to store data in event logs, then in contract's parameters.

So we are going to write to event logs whatever we want to access later on from our web site: contract's statistics.

	
// address indexed _address, string strLevel
// strLevel == "Gold" or strLevel == "Removed"
event WhiteListed(address indexed, string);	
event WhitelisterSet(address);	// address addrWhiteLister
	
event Pause();
event Unpause();
event Withdrawal(address addrBeneficiary, uint256 nTokens, 
	uint256 nPercentOfTokens, uint256 nWei, uint256 nPercentWei);
event Extended(uint256 nNumOfDays, uint256 nStage);
event Finalized();
event Refunding();
event Refunded(address indexed beneficiary, uint256 weiAmount);
event eventTokenCreated(address indexed purchaser, 
	address indexed beneficiary, uint256 value, string strDistributionReason);

Functions

As mentioned above, in constructor we fill few structures that are not supposed to be changed in future (like list of beneficiaries). We also create cobjects of external contracts (Token and ShareHolder).

function ICO() public 
{
    m_saleOptions.push(SaleOptions("WAIT_PRESALE",1543266000,1000000000000000,0,0,false));,
    m_saleOptions.push(SaleOptions("PRESALE",1545858000,1000000000000000,0,0,true));,
    m_saleOptions.push(SaleOptions("WAIT_SALE",1548536400,1000000000000000,0,0,false));,
    m_saleOptions.push(SaleOptions("SALE",1551214800,1000000000000000,0,0,true));,
    m_saleOptions.push(SaleOptions("FINALIZING",1553634000,1000000000000000,0,0,false));

	// --- 
    m_arrReceivingWallets.push(
        Wallet("Owner", // string strWalletName;
        0x949d4bC47fA7103cB3556852150bc580FA4499B9, // address addrWallet;
        0,       // uint nTokens; How many tokens to transfer (or 0)
        0,       // uint nPercentOfTokens; Percent of issued tokens to transfer (100000 based) (or 0)
        0,       // uint nWei; Money to transfer (or 0)
        100000)); // uint nPercentWei; Percent of raised to transfer (100000 based) (or 0)
    
    // ---
	m_mapLevels[0] = '1 ether';
	m_mapLevels[1] = '1 tether';

    // ---
	m_token = MySuperToken(m_addrTokenAddress);

    if(m_addrShareHolder != address(0)) 
		m_contractShareHolder = ShareHolder(m_addrShareHolder);
}

An "empty" function. This function has no name and is used (according to Solidity rules) an an "intercepting" function for unexpected things. For example, if someone sends money to a contract (not function of a contract that is supposed to receive money, but to an address of a contract), this function catches it.

An obvious use: to call buyTokens function from it.

Note that this is not just a "nice to have" thing, but an improvement that can get you some extra users. For example, say your ICO campaign does not have a web site that works in a tandem with MetaMask. Then in order to buy tokens, a client has to run a wallet that can work with contract's functions (MEW, Mist) which is not nearly as easy as working with MetaMask.

By making it possible to send money to contract (an address, without a function), you make sure those people will not abandon you.

Of course to SELL their tokens they will have to learn using advanced tools :)

    
// fallback function can be used to buy tokens
function() public payable 
{
    require(!m_bPaused);
	buyTokens(msg.sender);
}

The following function can be used by contract owner only (note the "onlyOwner" modifier) to assign free tokens to a selected address.

// Distribute tokens to selected party. Tokens are assigned free, 
// and if ICO fails, can not be refunded (as no one paid for them)
function distributeTokens(address beneficiary, uint256 nNumOfTokens, 
	string strDistributionReason) public onlyOwner 
{
	// First, it makes sure a campaign isn't over yet.
    require (!m_bFinished);

	// Then it figures out what is the current stage of a campaign
        getCurrentStatusIdx();    // Init the member variable
        
	// Few more checks to make sure request is legit
	require(isTradingAllowed());
    require (now < m_saleOptions[m_saleOptions.length - 1].timeEnd);
    require(beneficiary != 0x0);
    require(nNumOfTokens <= m_nMaxNumOfTokensAllowedToDistribute);
    
	// How much tokens shall we have if we do it?
    uint256 resultingTotalSupply = m_token.totalSupply().add(nNumOfTokens);

	// return money if total allowed supply exceeded
    require(m_nMaxGoal == 0 || m_nMaxGoal >= resultingTotalSupply);

	// If everything was ok, mint the requested tokens
    m_token.mint(beneficiary, nNumOfTokens);
        
	// If we use ShareHolder, issue shares to accompany tokens
    if(m_addrShareHolder != address(0))
    {
        uint nShares = nNumOfTokens / m_nNumOfTokensPerShare;
        if(nShares > 0)
            m_contractShareHolder.distributeBonusShares(beneficiary, nShares, m_strCampaignName);
    }
	
	// Finally, log the info about this event
    eventTokenCreated(msg.sender, beneficiary, nNumOfTokens, strDistributionReason);
}

Token purchase function. Note similarities between this function and distributeTokens above. Technically, the two functions could be combined.

function buyTokens(address beneficiary) public payable 
{
    require (!m_bFinished && !m_bPaused 
		&& getWhiteListMaxAmount(beneficiary) > msg.value);
        
	getCurrentStatusIdx();    // Init the member variable
        
    require(isTradingAllowed());
    require (m_saleOptions[m_nCurrentStatusIdx].tradingAllowed == true);
    require (now < m_saleOptions[m_saleOptions.length - 1].timeEnd);
    require(beneficiary != 0x0);
        
    uint256 nTokenPrice = getTokenPrice();
    require (msg.value >= nTokenPrice);
        
    uint256 nNumOfTokens = msg.value.div(nTokenPrice);
    uint256 resultingTotalSupply = m_token.totalSupply().add(nNumOfTokens);

    // return money if total allowed supply exceeded
    require(m_nMaxGoal == 0 || m_nMaxGoal >= resultingTotalSupply);

    // update state
    m_saleOptions[m_nCurrentStatusIdx].nWeiRaised = 
        m_saleOptions[m_nCurrentStatusIdx].nWeiRaised.add(msg.value);
    m_nTotalAmountRaised = m_nTotalAmountRaised.add(msg.value);        
        
    m_token.mint(beneficiary, nNumOfTokens);
    m_mapBalanceOf[msg.sender] += msg.value;
        
		
    if(m_addrShareHolder != address(0))
    {
        uint nShares = nNumOfTokens / m_nNumOfTokensPerShare;
        if(nShares > 0)
            m_contractShareHolder.distributeBonusShares(msg.sender, nShares, m_strCampaignName);
    }
	
	eventTokenCreated(msg.sender, beneficiary, nNumOfTokens, "Bought");
}

Distribute money upon successful end of campaign. Note that this function uses cycle. It means that the list of wallets MUST be short, otherwise we can run out of gas.

function withdraw() onlyOwner public
{
    require(goalReached() && m_bFinished && 
		now < m_saleOptions[m_saleOptions.length - 2].timeEnd);

    uint256 weiAmount = this.balance;

    if(weiAmount > 0) 
    {
        for(uint i = 0; i < m_arrReceivingWallets.length; i++)
        {
            // Add check for sufficient funds
            if(m_arrReceivingWallets[i].nTokens > 0)
                m_token.mint(m_arrReceivingWallets[i].addrWallet, 
					m_arrReceivingWallets[i].nTokens);
            if(m_arrReceivingWallets[i].nPercentOfTokens > 0)
                m_token.mint(m_arrReceivingWallets[i].addrWallet, 
					m_token.totalSupply().mul(m_arrReceivingWallets[i].
						nPercentOfTokens).div(100000));
            if(m_arrReceivingWallets[i].nWei > 0 && 
				m_arrReceivingWallets[i].nWei <= weiAmount)
                m_arrReceivingWallets[i].addrWallet.transfer(
					m_arrReceivingWallets[i].nWei);
            if(m_arrReceivingWallets[i].nPercentWei > 0)
                m_arrReceivingWallets[i].addrWallet.transfer(
					weiAmount.mul(m_arrReceivingWallets[i].nPercentWei).
						div(100000));
            // ---
            Withdrawal(m_arrReceivingWallets[i].addrWallet, 
				m_arrReceivingWallets[i].nTokens, 
                m_arrReceivingWallets[i].nPercentOfTokens, 
				m_arrReceivingWallets[i].nWei,
                m_arrReceivingWallets[i].nPercentWei);
        }
    }
}

As a campaign is over, we need to figure out if it was successful or not.

function finish() onlyOwner public 
{
    require(!m_bFinished);
    require(now < m_saleOptions[m_saleOptions.length - 2].timeEnd);

    m_bFinished = true;
    m_token.finishMinting();

    if(goalReached()) 
        withdraw();
    else 
    {
        m_bRefunding = true;
        Refunding();
    }

    Finalized();
}

The following function cycles through the list of campaign "stages", checking current date against stage's date in order to figure out what stage is it now.

function getCurrentStatusIdx() internal returns (uint256 nIdx)
{
    for(uint i = m_saleOptions.length - 1; i >= 0; i--)
    {
        if(now < m_saleOptions[i].timeEnd)
        {
            m_nCurrentStatusIdx = i;
            return i;
        }    
    }
    // Something went terribly wrong!
    m_nCurrentStatusIdx = m_saleOptions.length - 1;
    return m_saleOptions.length - 1;
}

The following function is used to alter the duration of a specified stage of a campaign. Note that dates of later stages are adjusted automatically.

function extendTimeEnd(uint256 nNumOfDays, uint256 nStage) 
	onlyOwner public 
{
    require(!m_bFinished);
    require(nStage < m_saleOptions.length);
    require(now + 1 days <= m_saleOptions[nStage].timeEnd);
    require(nNumOfDays > 0);

    m_saleOptions[nStage].timeEnd += nNumOfDays * 24 * 3600;
    for(uint i = nStage + 1; i < m_saleOptions.length; i++)
        m_saleOptions[i].timeEnd += nNumOfDays * 24 * 3600;

    Extended(nNumOfDays, nStage);
}                    

Set the "paused" flag to temporary suspend a crowd sale.

function pause() onlyOwner public 
{
    require(!m_bPaused);
    m_bPaused = true;
    Pause();
}
    
function unpause() onlyOwner public 
{
    // To extend current period to compensate for pause, 
	// the owner can call extendTimeEnd()
    require(m_bPaused);
    m_bPaused = false;
    Unpause();
}                    

The following function is called if the crowdsale campaign failed to reach its goal.

Note that we use "send on demand" pattern here, so in order to get money, the client has to demand it (by calling this function).

function refund(address _investor) public 
{
    require(m_bFinished);
    require(m_bRefunding);
    require(m_mapBalanceOf[_investor] > 0);

    uint256 weiAmount = m_mapBalanceOf[_investor];
    m_mapBalanceOf[_investor] = 0;
    m_nWeiRefunded = m_nWeiRefunded.add(weiAmount);
    Refunded(_investor, weiAmount);

    if(m_addrShareHolder != address(0))
    {
        m_contractShareHolder.undistributeBonusShares(m_strCampaignName);    
    }
		
    _investor.transfer(weiAmount);
}

Getters: static functions to return contract's state(s). Most functions are self-explanatoty.

    function getShareHolder() public constant 
		returns (address) { return m_addrShareHolder; }
    // ------
	function getTokenAddress() constant public 
		returns (address) 
	{
		return address(m_token);
	}    
	// ------
    function getCurrentStatus() public constant 
		returns (string)
    {
        return m_saleOptions[m_nCurrentStatusIdx].status;
    }
    // ------
    //determine the price of the token depending on ICO stage
    function getTokenPrice() public constant 
		returns (uint256 nTokenPrice) 
    {
        return m_saleOptions[m_nCurrentStatusIdx].tokenPrice;
    }
    // ------
    function isTradingAllowed() public constant 
		returns (bool) 
    {
        return m_saleOptions[m_nCurrentStatusIdx].tradingAllowed;
    }
    // ------
    function hasEnded() public constant 
		returns (bool) 
    {
        return (now > m_saleOptions[
			m_saleOptions.length - 1].timeEnd);
    }
    // ------
    function goalReached() public constant returns (bool) 
    {
        return m_nTotalAmountRaised >= m_nMinGoal;
    }    

Whitelisting functions. Note that if the whitelisting is not used, these functions are not required.

    modifier onlyOwnerOrWhiteLister() 
	{
        require((msg.sender == m_addrOwner) || 
			(msg.sender == m_addrWhiteLister));
		_;
    }
	// ------
    function whiteListUser(address addrUser, string strLevel) 
		public onlyOwnerOrWhiteLister
	{
		m_mapWhitelist[addrUser] = strLevel;
    }
	// ------
    function whiteListUsers(address[] users) 
		public onlyOwnerOrWhiteLister 
	{
        for(uint i = 0; i < users.length; i++) 
            m_mapWhitelist[users[i]] = strLevel;
    }
    // ------
    function unWhiteListUser(address addrUser) public onlyOwnerOrWhiteLister
	{
        delete m_mapWhitelist[addrUser];
    }
    // ------
    function unWhiteListUsers(address[] users) public onlyOwnerOrWhiteLister 
	{
        for (uint i = 0; i < users.length; i++) {
            delete m_mapWhitelist[users[i]];
    }    
	// ------
	function getWhiteListMaxAmount(address addUser) 
		public constant returns (uint256) 
	{
        if(m_mapWhitelist[addUser]) 
			return m_mapLevels[m_mapWhitelist[addUser]];
		else return 0;
	}
	// ------	
    function getWhiteListStatus(address addUser) public 
		constant returns (string) 
	{
        if(m_mapWhitelist[addUser]) 
			return m_mapWhitelist[addUser];
        
		return "";
    }
	// ------
    function setWhiteLister(address addrWhiteLister) 
		public onlyOwnerOrWhiteLister 
	{
		require(_newWhiteLister != address(0));
		WhitelisterSet(addrWhiteLister);
		m_addrWhiteLister = addrWhiteLister;
    }







Learn Touch Typing

(C) snowcron.com, all rights reserved

Please read the disclaimer