/// <summary>
        /// mint tokens is called when a user wishes to purchase tokens
        /// </summary>
        /// <returns></returns>
        public static bool MintTokens()
        {
            object[]    transactionData = Helpers.GetTransactionAndSaleData();
            Transaction tx = (Transaction)transactionData[0];

            byte[]     sender                    = (byte[])transactionData[1];
            byte[]     receiver                  = (byte[])transactionData[2];
            ulong      receivedNEO               = (ulong)transactionData[3];
            ulong      receivedGAS               = (ulong)transactionData[4];
            BigInteger whiteListGroupNumber      = (BigInteger)transactionData[5];
            BigInteger crowdsaleAvailableAmount  = (BigInteger)transactionData[6];
            BigInteger groupMaximumContribution  = (BigInteger)transactionData[7];
            BigInteger totalTokensPurchased      = (BigInteger)transactionData[8];
            BigInteger neoRemainingAfterPurchase = (BigInteger)transactionData[9];
            BigInteger gasRemainingAfterPurchase = (BigInteger)transactionData[10];
            BigInteger totalContributionBalance  = (BigInteger)transactionData[11];

            if (Helpers.GetBlockTimestamp() >= ICOTemplate.PublicSaleEndTime())
            {
                Runtime.Notify("MintTokens() failed. Token Sale is closed.", false);
                return(false);
            }

            if (!CanUserParticipateInSale(transactionData))
            {
                Runtime.Notify("MintTokens() CanUserParticipate failed", false);
                return(false);
            }

            byte[] lastTransactionHash = Storage.Get(Storage.CurrentContext, StorageKeys.MintTokensLastTX());
            if (lastTransactionHash == tx.Hash)
            {
                // ensure that minTokens doesnt process the same transaction more than once
                Runtime.Notify("MintTokens() not processing duplicate tx.Hash", tx.Hash);
                return(false);
            }

            Storage.Put(Storage.CurrentContext, StorageKeys.MintTokensLastTX(), tx.Hash);
            Runtime.Notify("MintTokens() receivedNEO / receivedGAS", receivedNEO, receivedGAS);

            if (neoRemainingAfterPurchase > 0 || gasRemainingAfterPurchase > 0)
            {
                // this purchase would have exceed the allowed max supply so we spent what we could and will refund the remainder
                refund(sender, neoRemainingAfterPurchase, gasRemainingAfterPurchase);
            }

            BigInteger senderAmountSubjectToVesting = SubjectToVestingPeriod(sender);
            BigInteger newTokenBalance = NEP5.BalanceOf(sender) + totalTokensPurchased + senderAmountSubjectToVesting;

            Helpers.SetBalanceOf(sender, newTokenBalance);
            Helpers.SetBalanceOfSaleContribution(sender, totalContributionBalance);
            Helpers.SetTotalSupply(totalTokensPurchased);

            transfer(null, sender, totalTokensPurchased);
            return(true);
        }