Contract Breakdown

This is the full source code for the CeloAsset Presale Smart Contract and Token CeloAsset.Sol , written in Solidity. It is publicly shared to promote transparency and trust during the presale process.

CeloAsset Token QRCODECeloAsset Presale QRCODE
0xB11856620cCd6C8C2438BB5ce50E73fcCB2318c80x030BA80c4e668D302bEF22a0e8c15b12eeB84c56
Click Celo Block Contract LinkClick Celo Block Presale Block Link

CeloAsset Contract Explained

To help you understand how everything works, we’ve added bolded inline comments throughout the code. Here’s how to read it:

  • Code: These are the actual Solidity instructions that define how the contract behaves on the blockchain.
  • Bolded Comments: These start with // ** and are written in plain English to explain each section or line of code. They tell you what a function does, why a value exists, and how different parts interact.
  • Security & Access: Functions labeled with onlyOwner can only be run by the contract creator, ensuring safe control over critical steps like ending the presale or withdrawing funds.
  • Presale Logic: Key limits are defined to prevent abuse — for example, how many tokens you can buy at once or per wallet.
  • Claim System: Tokens are not sent immediately. Instead, they unlock slowly over time starting 1 year after launch (TGE), with 25% releasing every 30 days. To deter rug pulling.

Whether you’re a buyer, developer, or auditor, the bold notes will walk you through what each part of the contract does, why it’s important, and how it protects you.

Note: Best to read on a big screen and single QRCODES at the bottom.

CeloAsset Presale Smart Contract – Explained

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// Import standard ERC20 token interface from OpenZeppelin
import “@openzeppelin/contracts/token/ERC20/IERC20.sol”;
// Import Ownable for restricted functions (admin-only)
import “@openzeppelin/contracts/access/Ownable.sol”;

contract Presale is Ownable {
// The ERC20 token being sold in this presale
IERC20 public token;

// Wallet that receives CELO proceeds
address public presaleWallet;

// @dev Pricing: 1 CELO = 120 CA → $0.005/CA
// Example: 100k CA = $500 ; 1M CA = $5,000

// Exchange rate (tokens per CELO)
uint256 public constant TOKEN_RATE = 120 * 1e18;

// Total presale allocation (1 billion CA)
uint256 public constant MAX_TOKENS = 1_000_000_000 * 1e18;

// Max per wallet (1M CA)
uint256 public constant MAX_PER_WALLET = 1_000_000 * 1e18;

// Max per transaction (100k CA)
uint256 public constant MAX_PER_TX = 100_000 * 1e18;

// Tracks total tokens sold
uint256 public totalTokensSold;

// Is presale still active?
bool public presaleActive = true;

// Was TGE set? (controls unlock)
bool public tgeSet = false;

// TGE timestamp
uint256 public tgeTimestamp;

// How many tokens each wallet purchased
mapping(address => uint256) public purchased;

// How many tokens each wallet has claimed
mapping(address => uint256) public claimed;

// Event: Token purchase logged
event TokensPurchased(address indexed buyer, uint256 amount);

// Event: Tokens claimed post-TGE
event TokensClaimed(address indexed claimer, uint256 amount);

// ✅ Constructor — set token and presale wallet
constructor(address _token, address _presaleWallet) Ownable() {
    require(_token != address(0), "Token required");
    require(_presaleWallet != address(0), "Presale wallet required");

    token = IERC20(_token);
    presaleWallet = _presaleWallet;
}

// ✅ Fallback (auto calls buyTokens if CELO sent)
receive() external payable {
    buyTokens();
}

// ✅ Token purchase logic (user sends CELO)
function buyTokens() public payable {
    require(presaleActive, "Presale is not active");

    // Calculate CA to issue based on CELO sent
    uint256 tokensToBuy = (msg.value * TOKEN_RATE) / 1e18;

    require(tokensToBuy > 0, "Zero token purchase");
    require(tokensToBuy <= MAX_PER_TX, "Exceeds max per transaction");
    require(totalTokensSold + tokensToBuy <= MAX_TOKENS, "Exceeds cap");
    require(purchased[msg.sender] + tokensToBuy <= MAX_PER_WALLET, "Exceeds wallet cap");

    // Track user purchase + total sold
    purchased[msg.sender] += tokensToBuy;
    totalTokensSold += tokensToBuy;

    // Emit event — for frontend display
    emit TokensPurchased(msg.sender, tokensToBuy);

    // ✅ Forward CELO immediately to presaleWallet
    payable(presaleWallet).transfer(msg.value);
}

// ✅ End presale — prevents new purchases
function endPresale() external onlyOwner {
    presaleActive = false;
}

// ✅ Set TGE timestamp (starts unlock countdown)
function setTGE() external onlyOwner {
    require(!tgeSet, "TGE already set");
    require(!presaleActive, "End presale first");

    tgeTimestamp = block.timestamp;
    tgeSet = true;
}

// ✅ How many tokens a wallet can claim right now
function claimableTokens(address user) public view returns (uint256) {
    if (!tgeSet || block.timestamp < tgeTimestamp + 365 days) {
        return 0; // Nothing claimable during 1-year lock
    }

    // How long since unlock period began
    uint256 elapsed = block.timestamp - (tgeTimestamp + 365 days);

    // Each phase = 30 days
    uint256 phaseDuration = 30 days;

    // Determine % unlocked
    uint256 unlockedPercent;

    if (elapsed < phaseDuration) {
        unlockedPercent = 2500; // 25%
    } else if (elapsed < 2 * phaseDuration) {
        unlockedPercent = 5000; // 50%
    } else if (elapsed < 3 * phaseDuration) {
        unlockedPercent = 7500; // 75%
    } else {
        unlockedPercent = 10000; // 100%
    }

    // Compute how many tokens this wallet can claim
    uint256 totalUnlocked = (purchased[user] * unlockedPercent) / 10000;

    // Subtract what they’ve already claimed
    return totalUnlocked - claimed[user];
}

// ✅ Claim unlocked tokens
function claimTokens() external {
    uint256 claimable = claimableTokens(msg.sender);
    require(claimable > 0, "Nothing to claim");

    // Track claimed
    claimed[msg.sender] += claimable;

    // Transfer CA tokens from presale wallet to user
    require(token.transfer(msg.sender, claimable), "Transfer failed");

    emit TokensClaimed(msg.sender, claimable);
}

// ✅ Owner can withdraw any CELO still in contract 
function withdrawCelo(address payable to) external onlyOwner {
    require(to != address(0), "Invalid address");

    to.transfer(address(this).balance);
}

// ✅ View how many CA tokens remain for sale
function tokensRemaining() external view returns (uint256) {
    return MAX_TOKENS - totalTokensSold;
}

}

CeloAsset Token Smart Contract – Explained

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// Import ERC20 functionality (OpenZeppelin)
import “@openzeppelin/contracts/token/ERC20/ERC20.sol”;
// Import Ownable (access control)
import “@openzeppelin/contracts/access/Ownable.sol”;

contract CeloAsset is ERC20, Ownable {
// ✅ Vault structure for reserve, strategy, non-circ
struct Vault {
uint256 amount; // Total allocated in this vault
uint256 lastUnlocked; // Last unlock timestamp
bool locked; // Is this vault still locked?
uint256 lockDuration; // Lock period
uint256 deployedAt; // Vault creation time
uint256 unlockedSoFar; // Cumulative unlocked amount
}

// ✅ Role wallets (liquidity, investment, etc.)
mapping(string => address) public roleWallets;

// ✅ Vault mappings
mapping(string => Vault) public vaults;

// ✅ Tax exemption mapping (internal wallets, deployer, etc.)
mapping(address => bool) public isTaxExempt;

// ✅ Stock split claimable balances
mapping(address => uint256) public claimableSplit;

// ✅ When stock split claiming can begin
uint256 public claimStartTime;

// ✅ Total supply (hardcoded 10B CA)
uint256 public constant TOTAL_SUPPLY = 10_000_000_000 * 1e18;

// ✅ Base tax = 1% = 100 basis points
uint256 public constant BASE_TAX = 100;

// ✅ Tax receiver wallets
address public liquidityWallet;
address public opsWallet;

// ✅ Fixed TGE time (used in vesting calculations)
uint256 public constant HARDCODED_TGE_TIME = 1718020200;

// ✅ Mint lock duration (2 years)
uint256 public constant MINT_LOCK_DURATION = 2 * 365 days;

// ✅ Contract deployment timestamp
uint256 public deployedAt = block.timestamp;

// ✅ Events
event StockSplitClaimed(address user, uint256 amount);

// ✅ Constructor: assign wallets and mint supply
constructor(
    address _liquidity,
    address _investment,
    address _marketing,
    address _operations,
    address _reserve,
    address _strategy,
    address _admin,
    address _presale,
    address _nonCirculating
) ERC20("CeloAsset", "CA") Ownable() {
    require(
        _liquidity != address(0) &&
        _investment != address(0) &&
        _marketing != address(0) &&
        _operations != address(0) &&
        _reserve != address(0) &&
        _strategy != address(0) &&
        _admin != address(0) &&
        _presale != address(0) &&
        _nonCirculating != address(0),
        "Invalid address"
    );

    deployedAt = block.timestamp;

    // Assign wallets
    roleWallets["liquidity"] = _liquidity;
    roleWallets["investment"] = _investment;
    roleWallets["marketing"] = _marketing;
    roleWallets["operations"] = _operations;
    roleWallets["reserve"] = _reserve;
    roleWallets["strategy"] = _strategy;
    roleWallets["admin"] = _admin;
    roleWallets["presale"] = _presale;
    roleWallets["noncirculating"] = _nonCirculating;

    liquidityWallet = _liquidity;
    opsWallet = _operations;

    // Mint direct allocations
    _mint(_liquidity, 20_000_000 * 1e18);
    _mint(_investment, 10_000_000 * 1e18);
    _mint(_marketing, 7_500_000 * 1e18);
    _mint(_operations, 5_000_000 * 1e18);

    // Mint vault allocations (held inside contract)
    _mint(address(this), (1_000_000_000 + 2_800_000_000 + 5_147_398_989) * 1e18);

    // Setup vaults
    vaults["reserve"] = Vault({
        amount: 1_000_000_000 * 1e18,
        lastUnlocked: 0,
        locked: true,
        lockDuration: 2 * 365 days,
        deployedAt: block.timestamp,
        unlockedSoFar: 0
    });

    vaults["strategy"] = Vault({
        amount: 2_800_000_000 * 1e18,
        lastUnlocked: 0,
        locked: true,
        lockDuration: 2 * 365 days,
        deployedAt: block.timestamp,
        unlockedSoFar: 0
    });

    vaults["noncirculating"] = Vault({
        amount: 5_147_398_989 * 1e18,
        lastUnlocked: 0,
        locked: true,
        lockDuration: 3 * 365 days,
        deployedAt: block.timestamp,
        unlockedSoFar: 0
    });

    // Mint presale allocation
    _mint(_presale, 1_010_101_011 * 1e18);

    // Tax exempt deployer
    isTaxExempt[msg.sender] = true;
}

// ✅ Returns current tax rate
function getDynamicTax() public pure returns (uint256) {
    return BASE_TAX;
}

// ✅ Returns current stock split claimable % (0–10000)
function getClaimablePercent() public view returns (uint256) {
    uint256 oneYear = 365 days;
    uint256 phaseDuration = 30 days;

    if (block.timestamp < HARDCODED_TGE_TIME + oneYear) {
        return 0;
    }

    uint256 elapsed = block.timestamp - (HARDCODED_TGE_TIME + oneYear);

    if (elapsed < phaseDuration) {
        return 2500;
    } else if (elapsed < 2 * phaseDuration) {
        return 5000;
    } else if (elapsed < 3 * phaseDuration) {
        return 7500;
    } else {
        return 10000;
    }
}

// ✅ Unlock 5% of vault — owner only — quarterly window
function unlockVault(string memory vaultKey) external onlyOwner {
    require(isQuarterlyWindow(), "Not in unlock window");

    Vault storage v = vaults[vaultKey];
    require(v.locked, "Already unlocked");
    require(block.timestamp >= v.deployedAt + v.lockDuration, "Vault still time-locked");

    uint256 unlockable = (v.amount * 5) / 100;
    uint256 remaining = v.amount - v.unlockedSoFar;
    if (unlockable > remaining) unlockable = remaining;
    require(unlockable > 0, "Nothing left to unlock");

    v.unlockedSoFar += unlockable;
    v.lastUnlocked = block.timestamp;
    if (v.unlockedSoFar >= v.amount) {
        v.locked = false;
    }

    _transfer(address(this), roleWallets[vaultKey], unlockable);
}

// ✅ Relock vault (admin only)
function relockVault(string memory vaultKey) external onlyOwner {
    Vault storage v = vaults[vaultKey];
    require(!v.locked, "Already locked");

    _transfer(roleWallets[vaultKey], address(this), v.amount);
    v.locked = true;
}

// ✅ Custom transfer — applies 1% tax unless exempt
function _transfer(address sender, address recipient, uint256 amount) internal override {
    if (isTaxExempt[sender] || isTaxExempt[recipient]) {
        super._transfer(sender, recipient, amount);
        return;
    }

    uint256 taxRate = getDynamicTax();

    uint256 taxAmount = (amount * taxRate) / 10000;
    uint256 toLiquidity = (taxAmount * 75) / 100;
    uint256 toOps = taxAmount - toLiquidity;
    uint256 finalAmount = amount - taxAmount;

    super._transfer(sender, liquidityWallet, toLiquidity);
    super._transfer(sender, opsWallet, toOps);
    super._transfer(sender, recipient, finalAmount);
}

// ✅ Quarterly window logic
function isQuarterlyWindow() public view returns (bool) {
    uint256 currentTime = block.timestamp;
    (uint256 year, uint256 month, uint256 day, uint256 hourUTC,,,) = timestampToDateTime(currentTime);

    if (hourUTC < 8 || hourUTC >= 12) return false;
    if (!isFirstSunday(year, month, day)) return false;

    return true;
}

// ✅ Is first Sunday of month?
function isFirstSunday(uint256 year, uint256 month, uint256 day) internal pure returns (bool) {
    uint256 dow = (timestampToDayOfWeek(year, month, day) + 6) % 7;
    return dow == 0 && day <= 7;
}

// ✅ Convert timestamp → date/time
function timestampToDateTime(uint256 timestamp) internal pure returns (
    uint256 year, uint256 month, uint256 day,
    uint256 hour, uint256 minute, uint256 second,
    uint256 weekday
) {
    uint256 z = timestamp / 86400 + 719468;
    uint256 era = z / 146097;
    uint256 doe = z - era * 146097;
    uint256 yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
    year = yoe + era * 400;
    uint256 doy = doe - (365*yoe + yoe/4 - yoe/100);
    uint256 mp = (5*doy + 2)/153;
    day = doy - (153*mp + 2)/5 + 1;
    month = mp < 10 ? mp + 3 : mp - 9;
    if (month <= 2) year++;

    hour = (timestamp / 3600) % 24;
    minute = (timestamp / 60) % 60;
    second = timestamp % 60;
    weekday = (z + 1) % 7;
}

// ✅ Day of week calculator
function timestampToDayOfWeek(uint256 y, uint256 m, uint256 d) internal pure returns (uint256) {
    if (m < 3) {
        m += 12;
        y -= 1;
    }

    uint256 k = y % 100;
    uint256 j = y / 100;

    return (d + 13*(m+1)/5 + k + k/4 + j/4 + 5*j) % 7;
}

// ✅ User claims stock split tokens
function claimStockSplit() external {
    require(block.timestamp >= claimStartTime, "Claim not started");

    uint256 totalEligible = claimableSplit[msg.sender];
    require(totalEligible > 0, "Nothing to claim");

    uint256 claimablePercent = getClaimablePercent();
    require(claimablePercent > 0, "Claim not available yet");

    uint256 maxClaimable = (totalEligible * claimablePercent) / 100;

    // Clear balance
    claimableSplit[msg.sender] = 0;

    _mint(msg.sender, maxClaimable);

    emit StockSplitClaimed(msg.sender, maxClaimable);
}

// ✅ Mark tax exemption
function setTaxExempt(address addr, bool exempt) external onlyOwner {
    isTaxExempt[addr] = exempt;
}

// ✅ User burns tokens
function burn(uint256 amount) external {
    _burn(msg.sender, amount);
}

// ✅ Admin mint (time locked, quarterly window)
function mint(address to, uint256 amount) external onlyOwner {
    require(block.timestamp >= deployedAt + MINT_LOCK_DURATION, "Minting still time-locked");
    require(isQuarterlyWindow(), "Minting allowed only in quarterly window");

    _mint(to, amount);
}

// ✅ Set stock split allocation
function setClaim(address user, uint256 amount) external onlyOwner {
    claimableSplit[user] = amount;
}

// ✅ Set global stock split start
function setClaimStart(uint256 startTime) external onlyOwner {
    claimStartTime = startTime;
}

}

Thank you for reading and hope if this is your first token you learned from our code brake down.

CeloAsset Token QRCODE

0xB11856620cCd6C8C2438BB5ce50E73fcCB2318c8

Click Celo Block Contract Location Link

CeloAsset Presale QRCODE

0x030BA80c4e668D302bEF22a0e8c15b12eeB84c56

Click for Celo Block Presale Block Location Link