├── docs ├── .gitkeep ├── AFRX_Token_White_Paper.pdf ├── Updated_AFRX_White_Paper_v1_4_May2025.pdf ├── AFRX_Dividend_FAQ.md ├── AFRX_White_Paper_Section_7_Escrow.md └── AFRXToken_legacy.sol ├── branding ├── afrx-token.jpg └── README.md ├── archived-contracts ├── README.md └── AFRXToken.sol ├── NOTICE.md ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── bug_report.md │ └── security-issue-template.md └── workflows │ └── blank.yml ├── SECURITY.md ├── LICENSE ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CHANGELOG.md ├── README.md └── AFRXTokenV1_18.sol /docs/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /branding/afrx-token.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afrail-inc/afrx-security-token/HEAD/branding/afrx-token.jpg -------------------------------------------------------------------------------- /docs/AFRX_Token_White_Paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afrail-inc/afrx-security-token/HEAD/docs/AFRX_Token_White_Paper.pdf -------------------------------------------------------------------------------- /docs/Updated_AFRX_White_Paper_v1_4_May2025.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afrail-inc/afrx-security-token/HEAD/docs/Updated_AFRX_White_Paper_v1_4_May2025.pdf -------------------------------------------------------------------------------- /archived-contracts/README.md: -------------------------------------------------------------------------------- 1 | # Archived Smart Contracts 2 | 3 | This folder contains previously deployed or deprecated versions of the AFRX Security Token smart contracts for reference and audit history. 4 | -------------------------------------------------------------------------------- /branding/README.md: -------------------------------------------------------------------------------- 1 | # AFRX Token Design 2 | 3 | This folder contains the official design mockup of the AFRX Security Token. 4 | 5 | ## File 6 | 7 | - `AFRX Security Token Icon` – Gold coin visual used for presentation, media, and token listing previews. 8 | 9 | ## Usage 10 | 11 | This mockup may be used in press, social media, investor decks, and listings. Please do not modify the visual elements without prior approval. 12 | 13 | For licensing or reproduction rights, contact: press@afrail.xyz 14 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | # Technical Clarification: ERC-3643 vs ERC-1400 2 | 3 | This repository includes documentation that references ERC‑3643 as being “based on” ERC‑1400. 4 | 5 | ### Clarification: 6 | 7 | - ERC‑3643 adopts many compliance principles originally described in ERC‑1400 — such as document linking, transfer restrictions, and identity-based permissions. 8 | - However, **ERC‑3643 is technically built on ERC‑20**, not on ERC‑1400 contracts. 9 | - This distinction is important for developers, auditors, and protocol reviewers analyzing the AFRX token’s architecture. 10 | 11 | No changes have been made to historical whitepapers or documentation in order to preserve version integrity. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: afrail-inc 7 | 8 | --- 9 | 10 | name: Feature Request 11 | about: Suggest an idea for this project. 12 | title: "[FEATURE]" 13 | labels: enhancement 14 | assignees: '' 15 | 16 | ## Feature Description 17 | Please describe the feature you would like to see implemented. 18 | 19 | ## Motivation 20 | Why is this feature important? How will it benefit the AFRX project and its users? 21 | 22 | ## Proposed Solution 23 | Provide a clear and detailed explanation of how this feature might be implemented. If you have any code or ideas, feel free to share them! 24 | 25 | ## Additional Context 26 | Any additional information (e.g., relevant research, examples of similar features elsewhere, etc.). 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: afrail-inc 7 | 8 | --- 9 | 10 | name: Bug Report 11 | about: Create a report to help us improve the AFRX Security Token project. 12 | title: "[BUG]" 13 | labels: bug 14 | assignees: '' 15 | 16 | ## Description 17 | Please provide a clear and concise description of what the bug is. 18 | 19 | ## Steps to Reproduce 20 | 1. Step 1 21 | 2. Step 2 22 | 3. Step 3 23 | 24 | ## Expected Behavior 25 | What did you expect to happen? 26 | 27 | ## Actual Behavior 28 | What actually happened? 29 | 30 | ## Screenshots 31 | If applicable, add screenshots to help explain your problem. 32 | 33 | ## Additional Context 34 | Add any other context about the problem here (e.g., version, environment). 35 | 36 | ## Smart Contract Address (if applicable) 37 | Please provide the contract address if relevant. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security-issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security Issue Template 3 | about: Reporting security issues 4 | title: '' 5 | labels: '' 6 | assignees: afrail-inc 7 | 8 | --- 9 | 10 | name: Security Issue 11 | about: Report a security vulnerability or concern. 12 | title: "[SECURITY]" 13 | labels: security 14 | assignees: '' 15 | 16 | ## Description 17 | Please describe the security issue clearly. 18 | 19 | ## Risk Assessment 20 | What level of risk do you assess this issue to have (low, medium, high)? 21 | 22 | ## Steps to Reproduce 23 | 1. Step 1 24 | 2. Step 2 25 | 26 | ## Potential Impact 27 | How could this vulnerability impact users, the contract, or other parts of the system? 28 | 29 | ## Recommended Mitigation 30 | If you have any recommendations or insights on how to mitigate this issue, please provide them. 31 | 32 | ## Contact Information 33 | Please provide contact information where we can reach you for follow-up. 34 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # AFRX Security Policy 2 | 3 | ## Supported Versions 4 | We currently maintain and monitor the latest version of the AFRX Security Token smart contract. 5 | 6 | If you find a security vulnerability, please report it promptly using the process below. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | If you discover a security issue related to this repository: 11 | 12 | - **Do not** create a public issue. 13 | - Instead, please email us directly at: 14 | 15 | **security@afrail.xyz** 16 | 17 | Provide as much detail as possible, including: 18 | - A clear description of the issue. 19 | - Steps to reproduce (if applicable). 20 | - The potential impact or risk. 21 | 22 | We will respond within **5 business days** and coordinate a resolution as quickly as possible. 23 | 24 | ## Our Commitment 25 | We are committed to protecting the security of AFRX token holders and users. 26 | Responsible disclosure is greatly appreciated and encouraged. 27 | 28 | Thank you for helping to make AFRX safer and more secure! 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Afrail Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /.github/workflows/blank.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with GitHub Actions 2 | 3 | name: CI 4 | 5 | # Set strict permissions to improve security 6 | permissions: 7 | contents: read 8 | actions: read 9 | pull-requests: none 10 | statuses: none 11 | 12 | on: 13 | # Triggers the workflow on push or pull request events to the "main" branch 14 | push: 15 | branches: [ "main" ] 16 | pull_request: 17 | branches: [ "main" ] 18 | 19 | # Allows manual trigger from the GitHub Actions tab 20 | workflow_dispatch: 21 | 22 | jobs: 23 | # This workflow contains a single job called "build" 24 | build: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | # Checks out your repository under $GITHUB_WORKSPACE 29 | - uses: actions/checkout@v4 30 | 31 | # Runs a one-line shell command 32 | - name: Run a one-line script 33 | run: echo Hello, world! 34 | 35 | # Runs a multi-line shell command 36 | - name: Run a multi-line script 37 | run: | 38 | echo Add other actions to build, 39 | echo test, and deploy your project. 40 | -------------------------------------------------------------------------------- /docs/AFRX_Dividend_FAQ.md: -------------------------------------------------------------------------------- 1 | AFRX Security Token – Dividend Distribution FAQ 2 | 3 | Who is eligible to receive dividends from AFRX? 4 | 5 | All verified and registered AFRX security token holders are eligible. This excludes wallets that do not meet compliance under ERC-3643 standards. 6 | 7 | Are dividends distributed to all AFRX investors or only specific roles? 8 | 9 | Dividends are distributed proportionally to all eligible token holders, based on holdings. There’s no special role required beyond ERC-3643 compliance. 10 | 11 | How often will dividends be paid out? 12 | 13 | Dividends are paid periodically based on profits generated by Afrail Inc. and its smart mobility subsidiaries. Schedules and amounts will be announced in advance. 14 | 15 | How are dividends distributed to token holders? 16 | 17 | Dividends are paid in USDC (or another approved stablecoin), directly to the verified wallets of compliant holders. 18 | 19 | Do I need to take any action to receive my dividends? 20 | 21 | No — as long as your wallet is verified and compliant, dividends are distributed automatically through the AFRX smart contract’s claim mechanism. 22 | 23 | What is ERC-3643 and why is it important? 24 | 25 | ERC-3643 enforces on-chain identity verification, regulatory compliance, and jurisdictional transfer restrictions — making it ideal for secure, legal dividend payouts. 26 | 27 | Where can I find updates or reports on dividend distributions? 28 | 29 | Official updates will be published on: 30 | 31 | The Afrail Inc. investor relations page 32 | 33 | The AFRX dashboard 34 | 35 | Our GitHub repository and community channels 36 | 37 | More info: https://afrail.xyz 38 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Code of Conduct 2 | 3 | Our Commitment 4 | 5 | We are committed to fostering a welcoming, safe, and respectful environment for everyone contributing to or using the AFRX Security Token repository. 6 | 7 | We expect all interactions to reflect the professional, compliance-focused nature of the AFRX project, and to comply with applicable legal and regulatory standards. 8 | 9 | Expected Behavior 10 | 11 | Act with integrity, respect, and professionalism at all times. 12 | 13 | Use inclusive and welcoming language. 14 | 15 | Be considerate of differing viewpoints, experiences, and backgrounds. 16 | 17 | Provide constructive feedback. 18 | 19 | Uphold compliance and regulatory standards relevant to financial technology and securities. 20 | 21 | Unacceptable Behavior 22 | 23 | Use of discriminatory, harassing, or abusive language or conduct. 24 | 25 | Any form of harassment, intimidation, or threats. 26 | 27 | Sharing misleading, false, or non-compliant information related to securities. 28 | 29 | Disrespectful or disruptive behavior that undermines project collaboration. 30 | 31 | Unapproved promotion of third-party products or services. 32 | 33 | Responsibilities of Maintainers 34 | 35 | Repository maintainers are responsible for clarifying the standards of acceptable behavior and will take appropriate corrective action in response to any behavior they deem inappropriate, threatening, offensive, or harmful. 36 | 37 | Maintainers have the right to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that violate this Code of Conduct. 38 | 39 | Reporting Issues 40 | 41 | If you experience or witness unacceptable behavior, please report it by emailing: 42 | 43 | conduct@afrail.xyz 44 | 45 | Reports will be handled confidentially. 46 | 47 | Scope 48 | 49 | This Code of Conduct applies within all project spaces, including GitHub issues, pull requests, and discussions, and in any project-related public spaces. 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to AFRX Security Token 2 | 3 | Thank you for considering contributing to the AFRX Security Token project! 4 | 5 | We welcome contributions that improve the project, enhance its compliance, security, or overall functionality. 6 | 7 | Please follow the guidelines below to help maintain a high standard of quality. 8 | 9 | ## Code of Conduct 10 | Participation in this project is governed by our [Code of Conduct](./CODE_OF_CONDUCT.md). 11 | Please review it before contributing. 12 | 13 | ## How to Contribute 14 | 15 | ### Reporting Issues 16 | - Use GitHub Issues to report bugs, suggest enhancements, or propose new features. 17 | - Provide clear and concise information to help us reproduce and understand the issue. 18 | 19 | ### Submitting Pull Requests 20 | 1. Fork the repository and create your branch from `main`. 21 | 2. Write clear, well-documented code that aligns with the project's style and standards. 22 | 3. Ensure all existing tests pass and add new tests if applicable. 23 | 4. Describe your changes clearly in the Pull Request (PR) description. 24 | 5. Link the issue your PR addresses, if applicable. 25 | 26 | ### Code Standards 27 | - Solidity Smart Contracts must follow industry best practices (e.g., [OpenZeppelin Guidelines](https://docs.openzeppelin.com/)). 28 | - Maintain clear, professional commit messages. 29 | - Perform security-focused code reviews for smart contracts. 30 | 31 | ### Legal and Compliance 32 | - Contributions must comply with relevant securities laws and regulations. 33 | - Do not submit content that might mislead investors or users. 34 | 35 | ## Communication 36 | For major changes or feature proposals, please first open an issue to discuss what you would like to change. 37 | 38 | Questions, clarifications, or sensitive disclosures should be emailed to: 39 | 40 | **conduct@afrail.xyz** 41 | 42 | # Website 43 | **https://afrail.xyz** 44 | 45 | Thank you for helping us build a secure, compliant, and innovative future with AFRX! 46 | -------------------------------------------------------------------------------- /docs/AFRX_White_Paper_Section_7_Escrow.md: -------------------------------------------------------------------------------- 1 | 2 | # 7. Proceeds Management and Escrow Strategy 3 | 4 | This section outlines how proceeds from the AFRX Security Token sale will be managed, including details on escrow custody, access requirements, and interim financing options. These structures are designed to protect investors, comply with regulatory best practices, and support pre-operational readiness for Afrail Inc. 5 | 6 | ## 7.1 Escrow-Based Custody of AFRX Proceeds 7 | 8 | All cash proceeds from the sale of AFRX Security Tokens will be paid directly into a regulated Escrow Account upon the commencement of token production and sale. This escrow mechanism ensures: 9 | 10 | - Transparency 11 | - Investor protection 12 | - Regulatory and compliance assurance 13 | 14 | Afrail Inc. will not be able to access these funds immediately upon receipt. 15 | 16 | ## 7.2 Access Contingency: Minimum Raise Threshold 17 | 18 | Funds in escrow will remain locked until Afrail Inc. reaches a minimum raise threshold of **10% of the targeted raise amount**. 19 | 20 | - **Target Raise:** $4.039 billion USD 21 | - **10% Threshold:** $403.9 million USD 22 | 23 | Once this threshold is reached and verified, Afrail Inc. may begin drawing down funds per agreed use-of-funds schedules and reporting requirements. 24 | 25 | ## 7.3 Bridge Financing Potential 26 | 27 | Prior to reaching the 10% threshold, Afrail Inc. will explore short-term, low-interest loans backed by the escrowed AFRX funds to support early operations, including: 28 | 29 | - Pre-engineering & infrastructure readiness in South Florida 30 | - Site preparation and engineering surveys in Northern Namibia 31 | 32 | These operations will help maintain momentum and readiness while ensuring financial prudence and accountability. 33 | 34 | ## 7.4 Key Benefits 35 | 36 | - ⚖️ **Investor Assurance**: Investors know their capital is not misused before substantial capital is raised. 37 | - 🔐 **Compliance & Credibility**: Aligns with global standards for real-world asset tokenization and STOs. 38 | - 🚀 **Operational Readiness**: Allows Afrail Inc. to progress with foundational work through secured interim financing. 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes to the AFRX Security Token smart contract repository will be documented in this file. 4 | 5 | ---------- 6 | 7 | ## [v1.18.4] – 2025-07-17 8 | 9 | ### Updated 10 | 11 | - `README.md`: Added prominent link at the top to `NOTICE.md` to clarify that ERC‑3643 is built on ERC‑20, not ERC‑1400. 12 | 13 | ---------- 14 | 15 | ## [v1.18.3] – 2025-07-17 16 | 17 | ### Added 18 | 19 | - `NOTICE.md` with technical clarification that ERC‑3643 is built on ERC‑20, not ERC‑1400. 20 | 21 | ### Updated 22 | 23 | - `README.md`: Updated all references from ERC‑1400 to ERC‑20 to reflect accurate token standard architecture. 24 | 25 | ---------- 26 | 27 | ## [v1.18.2] – 2025-06-24 28 | 29 | ### Changed 30 | 31 | - Replaced outdated README.md with updated full version including token design breakdown and corrected anchor links. 32 | - Linked the official AFRX token image (`branding/afrx-token.jpg`) in both image display and internal documentation. 33 | - Verified internal markdown anchors to ensure all Project Links route correctly to their sections. 34 | 35 | ---------- 36 | 37 | ## [v1.18.1] – 2025-06-22 38 | 39 | ### Changed 40 | 41 | - Updated token name in `AFRXTokenV1_18.sol` from "AfrailX Security Token" to "Afrail Security Token" (line 57). 42 | - No changes to contract logic or functionality. 43 | - Aligned token metadata with official issuer name: Afrail Inc. 44 | 45 | ---------- 46 | 47 | ## [v1.18] – 2025-06-22 48 | 49 | ### Added 50 | 51 | - Full audit-style revision of smart contract using CertiK-style methodology. 52 | - `AFRXTokenV1_18.sol` introduced with final production-grade compliance features. 53 | - Improved error handling and input validations across all major functions. 54 | - `dividendInfo[roundId].reclaimedAmount` to track unclaimed dividend recoveries. 55 | - `snapshotTimestamps` mapping for reclaim timing logic enforcement. 56 | - Events for `DividendsReclaimed`, `TestModeToggled`, `Upgraded` added. 57 | - Role-guarded upgradeability with UUPS implementation and `UPGRADER_ROLE`. 58 | 59 | ### Changed 60 | 61 | - Migrated prior production contract `AFRXToken.sol` to `contracts-archive` folder. 62 | - Updated jurisdiction enforcement to use normalized lowercase hashed keys. 63 | - Whitelisting logic now includes on-chain jurisdiction verification. 64 | - Token issuance now includes lockup timing enforcement (`LOCKUP_PERIOD`). 65 | - Enhanced double-claim protection in `claimDividends()` with snapshot balance validation. 66 | 67 | ### Fixed 68 | 69 | - Redundant logic and poor modularization from previous versions. 70 | - Snapshot imbalance issue when treasury or contract address skewed supply. 71 | - Compatibility issues with OpenZeppelin `ERC20Capped` and `Snapshot`. 72 | 73 | ### Deprecated 74 | 75 | - `AFRXToken.sol` (moved to archive) 76 | - `AFRXToken_legacy.sol` (retained for historical reference) 77 | 78 | ---------- 79 | 80 | ## [v1.17] – 2025-06-21 81 | 82 | Initial final-stage candidate before CertiK audit pass. Introduced: 83 | 84 | - Structured investor lockup and jurisdiction enforcement. 85 | - Basic dividend distribution via snapshots. 86 | - Full OpenZeppelin upgradeable framework. 87 | 88 | ---------- 89 | 90 | ## [v1.0.0] – 2025-05-26 91 | 92 | ### Added 93 | 94 | - Initial implementation of AFRX Security Token contract (ERC-3643 based) 95 | - KYC/AML whitelisting framework via Tokeny and ONCHAINID integration 96 | - Basic compliance modules: document linking, transfer validation, forced transfers 97 | - Terms of Service, Tokenomics, Audit, and Legal documentation files 98 | - Investor onboarding guides and full technical overview in `/docs` 99 | - Initial `README.md` and licensing files 100 | -------------------------------------------------------------------------------- /docs/AFRXToken_legacy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // AFRXToken_legacy.sol 3 | // Archived legacy version of the AFRX Security Token smart contract. 4 | // Note: Superseded by AFRXToken.sol v1.1 due to enhancements in compliance and dividend distribution mechanisms. 5 | 6 | pragma solidity ^0.8.0; 7 | 8 | // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; 9 | 10 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol"; import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 11 | 12 | contract AFRXToken is Initializable, ERC20Capped, ERC20Burnable, Pausable, AccessControl, ReentrancyGuard, ERC20Snapshot, UUPSUpgradeable { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE"); bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); 13 | 14 | uint256 public constant LOCKUP_PERIOD = 365 days; 15 | uint8 public constant TOKEN_DECIMALS = 18; 16 | string private constant TOKEN_NAME = "AfrailX Security Token"; 17 | string private constant TOKEN_SYMBOL = "AFRX"; 18 | 19 | struct InvestorInfo { 20 | uint256 releaseTime; 21 | bool isWhitelisted; 22 | string jurisdiction; 23 | } 24 | 25 | mapping(address => InvestorInfo) private _investors; 26 | mapping(bytes32 => bool) private allowedJurisdictions; 27 | 28 | event TokensIssued(address indexed investor, uint256 amount, string jurisdiction); 29 | event TokenIssueRejected(address indexed investor, string reason); 30 | event InvestorWhitelisted(address indexed investor, string jurisdiction); 31 | event InvestorRemovedFromWhitelist(address indexed investor); 32 | event JurisdictionAdded(string jurisdiction); 33 | event JurisdictionRemoved(string jurisdiction); 34 | event ContractPaused(string reason); 35 | event ContractUnpaused(); 36 | event DividendsDistributed(uint256 totalAmount); 37 | event BuybackExecuted(uint256 amount); 38 | 39 | modifier onlyWhitelisted(address account) { 40 | require(_investors[account].isWhitelisted, "Investor not whitelisted"); 41 | _; 42 | } 43 | 44 | constructor() ERC20(TOKEN_NAME, TOKEN_SYMBOL) ERC20Capped(5_770_000_000 * (10 ** TOKEN_DECIMALS)) {} 45 | 46 | function initialize(address admin) public initializer { 47 | _grantRole(DEFAULT_ADMIN_ROLE, admin); 48 | _grantRole(ADMIN_ROLE, admin); 49 | _grantRole(MINTER_ROLE, admin); 50 | _grantRole(PAUSER_ROLE, admin); 51 | _grantRole(SNAPSHOT_ROLE, admin); 52 | _grantRole(UPGRADER_ROLE, admin); 53 | } 54 | 55 | function issueTokens(address investor, uint256 amount, string memory jurisdiction) external onlyRole(MINTER_ROLE) whenNotPaused { 56 | require(totalSupply() + amount <= cap(), "Cap exceeded"); 57 | require(_isJurisdictionAllowed(jurisdiction), "Jurisdiction not allowed"); 58 | 59 | _mint(investor, amount); 60 | 61 | if (_investors[investor].releaseTime == 0) { 62 | _investors[investor] = InvestorInfo({ 63 | releaseTime: block.timestamp + LOCKUP_PERIOD, 64 | isWhitelisted: true, 65 | jurisdiction: _normalizeString(jurisdiction) 66 | }); 67 | } 68 | 69 | emit TokensIssued(investor, amount, jurisdiction); 70 | } 71 | 72 | function batchIssueTokens(address[] calldata investors, uint256[] calldata amounts, string[] calldata jurisdictions) external onlyRole(MINTER_ROLE) whenNotPaused { 73 | require(investors.length == amounts.length && amounts.length == jurisdictions.length, "Array lengths mismatch"); 74 | for (uint256 i = 0; i < investors.length; ++i) { 75 | issueTokens(investors[i], amounts[i], jurisdictions[i]); 76 | } 77 | } 78 | 79 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Snapshot) whenNotPaused { 80 | if (from != address(0) && to != address(0)) { 81 | require(_investors[from].isWhitelisted, "Sender not whitelisted"); 82 | require(_investors[to].isWhitelisted, "Recipient not whitelisted"); 83 | require(block.timestamp >= _investors[from].releaseTime, "Lock-up not over"); 84 | require(_isJurisdictionAllowed(_investors[to].jurisdiction), "Recipient jurisdiction not allowed"); 85 | } 86 | super._beforeTokenTransfer(from, to, amount); 87 | } 88 | 89 | function whitelistInvestor(address investor, string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 90 | require(_isJurisdictionAllowed(jurisdiction), "Jurisdiction not allowed"); 91 | _investors[investor].isWhitelisted = true; 92 | _investors[investor].jurisdiction = _normalizeString(jurisdiction); 93 | emit InvestorWhitelisted(investor, jurisdiction); 94 | } 95 | 96 | function removeFromWhitelist(address investor) external onlyRole(ADMIN_ROLE) { 97 | _investors[investor].isWhitelisted = false; 98 | emit InvestorRemovedFromWhitelist(investor); 99 | } 100 | 101 | function addJurisdiction(string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 102 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 103 | allowedJurisdictions[j] = true; 104 | emit JurisdictionAdded(jurisdiction); 105 | } 106 | 107 | function removeJurisdiction(string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 108 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 109 | allowedJurisdictions[j] = false; 110 | emit JurisdictionRemoved(jurisdiction); 111 | } 112 | 113 | function distributeDividends(uint256 totalAmount) external onlyRole(ADMIN_ROLE) nonReentrant whenNotPaused { 114 | require(totalSupply() > 0, "No tokens in circulation"); 115 | _mint(address(this), totalAmount); 116 | for (uint256 i = 0; i < getRoleMemberCount(DEFAULT_ADMIN_ROLE); i++) { 117 | address investor = getRoleMember(DEFAULT_ADMIN_ROLE, i); 118 | if (balanceOf(investor) > 0) { 119 | uint256 share = (balanceOf(investor) * totalAmount) / totalSupply(); 120 | _transfer(address(this), investor, share); 121 | } 122 | } 123 | emit DividendsDistributed(totalAmount); 124 | } 125 | 126 | function executeBuyback(uint256 amount) external onlyRole(ADMIN_ROLE) whenNotPaused { 127 | require(balanceOf(msg.sender) >= amount, "Insufficient tokens"); 128 | _burn(msg.sender, amount); 129 | emit BuybackExecuted(amount); 130 | } 131 | 132 | function _normalizeString(string memory str) internal pure returns (string memory) { 133 | bytes memory bStr = bytes(str); 134 | for (uint256 i = 0; i < bStr.length; i++) { 135 | if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) { 136 | bStr[i] = bytes1(uint8(bStr[i]) + 32); 137 | } 138 | } 139 | return string(bStr); 140 | } 141 | 142 | function pause(string memory reason) external onlyRole(PAUSER_ROLE) { 143 | _pause(); 144 | emit ContractPaused(reason); 145 | } 146 | 147 | function unpause() external onlyRole(PAUSER_ROLE) { 148 | _unpause(); 149 | emit ContractUnpaused(); 150 | } 151 | 152 | function snapshot() external onlyRole(SNAPSHOT_ROLE) { 153 | _snapshot(); 154 | } 155 | 156 | function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} 157 | 158 | // Required overrides 159 | function decimals() public pure override returns (uint8) { 160 | return TOKEN_DECIMALS; 161 | } 162 | 163 | function supportsInterface(bytes4 interfaceId) public view override(AccessControl) returns (bool) { 164 | return super.supportsInterface(interfaceId); 165 | } 166 | 167 | } 168 | 169 | -------------------------------------------------------------------------------- /archived-contracts/AFRXToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // AFRXToken.sol 3 | // Version: 1.1 4 | // Production version of the AFRX Security Token smart contract. 5 | // Features: Snapshot-based dividends, 365-day lockup, KYC/AML compliance, buyback and burn, UUPS upgradeability. 6 | // Compliant with SEC Regulation D Rule 506(c) and Regulation S. 7 | 8 | pragma solidity ^0.8.20; 9 | 10 | // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; 11 | 12 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; 13 | 14 | contract AFRXToken is ERC20Capped, ERC20Burnable, ERC20Snapshot, Pausable, AccessControl, ReentrancyGuard, UUPSUpgradeable { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE"); bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); 15 | 16 | uint256 public constant LOCKUP_PERIOD = 365 days; 17 | uint8 public constant TOKEN_DECIMALS = 18; 18 | 19 | struct InvestorInfo { 20 | uint256 releaseTime; 21 | bool isWhitelisted; 22 | string jurisdiction; 23 | } 24 | 25 | struct DividendInfo { 26 | uint256 totalAmount; 27 | uint256 snapshotId; 28 | uint256 totalSupplyAtSnapshot; 29 | } 30 | 31 | mapping(address => InvestorInfo) private _investors; 32 | mapping(bytes32 => bool) private allowedJurisdictions; 33 | 34 | mapping(uint256 => DividendInfo) public dividendInfo; 35 | mapping(uint256 => mapping(address => bool)) public dividendsClaimed; 36 | uint256 public currentDividendRound; 37 | 38 | event TokensIssued(address indexed investor, uint256 amount, string jurisdiction); 39 | event TokenIssueRejected(address indexed investor, string reason); 40 | event InvestorWhitelisted(address indexed investor, string jurisdiction); 41 | event InvestorRemovedFromWhitelist(address indexed investor); 42 | event JurisdictionAdded(string jurisdiction); 43 | event JurisdictionRemoved(string jurisdiction); 44 | event ContractPaused(string reason); 45 | event ContractUnpaused(); 46 | event DividendsAvailable(uint256 indexed roundId, uint256 totalAmount); 47 | event DividendsClaimed(uint256 indexed roundId, address indexed investor, uint256 amount); 48 | 49 | modifier onlyWhitelisted(address account) { 50 | require(_investors[account].isWhitelisted, "Investor not whitelisted"); 51 | _; 52 | } 53 | 54 | constructor() ERC20("AfrailX Security Token", "AFRX") ERC20Capped(5_770_000_000 * (10 ** TOKEN_DECIMALS)) {} 55 | 56 | function initialize(address admin) public initializer { 57 | _grantRole(DEFAULT_ADMIN_ROLE, admin); 58 | _grantRole(ADMIN_ROLE, admin); 59 | _grantRole(MINTER_ROLE, admin); 60 | _grantRole(PAUSER_ROLE, admin); 61 | _grantRole(SNAPSHOT_ROLE, admin); 62 | _grantRole(UPGRADER_ROLE, admin); 63 | } 64 | 65 | function issueTokens(address investor, uint256 amount, string memory jurisdiction) external onlyRole(MINTER_ROLE) whenNotPaused { 66 | require(totalSupply() + amount <= cap(), "Cap exceeded"); 67 | require(_isJurisdictionAllowed(jurisdiction), "Jurisdiction not allowed"); 68 | 69 | _mint(investor, amount); 70 | 71 | if (_investors[investor].releaseTime == 0) { 72 | _investors[investor] = InvestorInfo({ 73 | releaseTime: block.timestamp + LOCKUP_PERIOD, 74 | isWhitelisted: true, 75 | jurisdiction: _normalizeString(jurisdiction) 76 | }); 77 | } 78 | 79 | emit TokensIssued(investor, amount, jurisdiction); 80 | } 81 | 82 | function batchIssueTokens(address[] calldata investors, uint256[] calldata amounts, string[] calldata jurisdictions) external onlyRole(MINTER_ROLE) whenNotPaused { 83 | require(investors.length == amounts.length && amounts.length == jurisdictions.length, "Array lengths mismatch"); 84 | for (uint256 i = 0; i < investors.length; ++i) { 85 | issueTokens(investors[i], amounts[i], jurisdictions[i]); 86 | } 87 | } 88 | 89 | function distributeDividends(uint256 totalAmount) external onlyRole(ADMIN_ROLE) nonReentrant whenNotPaused { 90 | require(totalSupply() > 0, "No tokens in circulation"); 91 | 92 | uint256 snapshotId = _snapshot(); 93 | uint256 snapshotSupply = totalSupply(); 94 | 95 | _mint(address(this), totalAmount); 96 | 97 | dividendInfo[currentDividendRound] = DividendInfo({ 98 | totalAmount: totalAmount, 99 | snapshotId: snapshotId, 100 | totalSupplyAtSnapshot: snapshotSupply 101 | }); 102 | 103 | emit DividendsAvailable(currentDividendRound, totalAmount); 104 | currentDividendRound++; 105 | } 106 | 107 | function claimDividends(uint256 roundId) external nonReentrant whenNotPaused onlyWhitelisted(msg.sender) { 108 | require(!dividendsClaimed[roundId][msg.sender], "Already claimed"); 109 | 110 | DividendInfo storage dividend = dividendInfo[roundId]; 111 | uint256 balance = balanceOfAt(msg.sender, dividend.snapshotId); 112 | require(balance > 0, "No tokens at snapshot"); 113 | 114 | uint256 share = (balance * dividend.totalAmount) / dividend.totalSupplyAtSnapshot; 115 | require(share > 0, "No dividends to claim"); 116 | 117 | dividendsClaimed[roundId][msg.sender] = true; 118 | _transfer(address(this), msg.sender, share); 119 | 120 | emit DividendsClaimed(roundId, msg.sender, share); 121 | } 122 | 123 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Snapshot) whenNotPaused { 124 | if (from != address(0) && to != address(0)) { 125 | require(_investors[from].isWhitelisted, "Sender not whitelisted"); 126 | require(_investors[to].isWhitelisted, "Recipient not whitelisted"); 127 | require(block.timestamp >= _investors[from].releaseTime, "Lock-up not over"); 128 | require(_isJurisdictionAllowed(_investors[to].jurisdiction), "Recipient jurisdiction not allowed"); 129 | } 130 | super._beforeTokenTransfer(from, to, amount); 131 | } 132 | 133 | function whitelistInvestor(address investor, string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 134 | require(_isJurisdictionAllowed(jurisdiction), "Jurisdiction not allowed"); 135 | _investors[investor].isWhitelisted = true; 136 | _investors[investor].jurisdiction = _normalizeString(jurisdiction); 137 | emit InvestorWhitelisted(investor, jurisdiction); 138 | } 139 | 140 | function removeFromWhitelist(address investor) external onlyRole(ADMIN_ROLE) { 141 | _investors[investor].isWhitelisted = false; 142 | emit InvestorRemovedFromWhitelist(investor); 143 | } 144 | 145 | function addJurisdiction(string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 146 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 147 | allowedJurisdictions[j] = true; 148 | emit JurisdictionAdded(jurisdiction); 149 | } 150 | 151 | function removeJurisdiction(string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 152 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 153 | allowedJurisdictions[j] = false; 154 | emit JurisdictionRemoved(jurisdiction); 155 | } 156 | 157 | function pause(string memory reason) external onlyRole(PAUSER_ROLE) { 158 | _pause(); 159 | emit ContractPaused(reason); 160 | } 161 | 162 | function unpause() external onlyRole(PAUSER_ROLE) { 163 | _unpause(); 164 | emit ContractUnpaused(); 165 | } 166 | 167 | function snapshot() external onlyRole(SNAPSHOT_ROLE) { 168 | _snapshot(); 169 | } 170 | 171 | function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} 172 | 173 | function _isJurisdictionAllowed(string memory jurisdiction) internal view returns (bool) { 174 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 175 | return allowedJurisdictions[j]; 176 | } 177 | 178 | function _normalizeString(string memory str) internal pure returns (string memory) { 179 | bytes memory bStr = bytes(str); 180 | for (uint256 i = 0; i < bStr.length; i++) { 181 | if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) { 182 | bStr[i] = bytes1(uint8(bStr[i]) + 32); 183 | } 184 | } 185 | return string(bStr); 186 | } 187 | 188 | function decimals() public pure override returns (uint8) { 189 | return TOKEN_DECIMALS; 190 | } 191 | 192 | function supportsInterface(bytes4 interfaceId) public view override(AccessControl) returns (bool) { 193 | return super.supportsInterface(interfaceId); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AFRX Security Token 2 | 3 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 4 | ![Built with Solidity](https://img.shields.io/badge/built%20with-Solidity-363636) 5 | ![ERC-3643](https://img.shields.io/badge/ERC--3643-Compliant-brightgreen) 6 | 7 |

8 | AFRX Token 9 |

10 | 11 | Smart contract implementation for the **AFRX Security Token**, built on the [ERC-3643](https://tokeny.com/erc3643/) standard (ERC-20 base), incorporating enhanced compliance, upgradeability, and snapshot-based dividend distribution. 12 | 13 | > 📌 **Important Notice:** Please see [NOTICE.md](./NOTICE.md) for technical clarification on ERC‑3643 vs ERC‑1400. 14 | 15 | ## Project Links 16 | 17 | - [AFRXTokenV1_18.sol (Production Contract)](https://github.com/afrail-inc/afrx-security-token/blob/main/AFRXTokenV1_18.sol) 18 | - [AFRXToken.sol (Archived Prior Version)](https://github.com/afrail-inc/afrx-security-token/blob/main/archived-contracts/AFRXToken.sol) 19 | - [AFRXToken_legacy.sol (Older Archived Contract)](https://github.com/afrail-inc/afrx-security-token/blob/main/docs/AFRXToken_legacy.sol) 20 | - [AFRX Token White Paper (PDF)](https://github.com/afrail-inc/afrx-security-token/blob/main/docs/AFRX_Token_White_Paper.pdf) 21 | - [Updated AFRX Token White Paper (PDF)](https://github.com/afrail-inc/afrx-security-token/blob/main/docs/Updated_AFRX_White_Paper_v1_4_May2025.pdf) 22 | - [AFRX Security Token – Official Token Design](#afrx-security-token--official-token-design) 23 | - [CHANGELOG.md](https://github.com/afrail-inc/afrx-security-token/blob/main/CHANGELOG.md) 24 | - [AFRX Dividend Distribution FAQ](https://github.com/afrail-inc/afrx-security-token/blob/main/docs/AFRX_Dividend_FAQ.md) 25 | - [SECURITY.md](https://github.com/afrail-inc/afrx-security-token/blob/main/SECURITY.md) 26 | - [Section 7: Proceeds Management and Escrow Strategy](#-section-7-proceeds-management-and-escrow-strategy) 27 | - [AFRX Branding Assets](https://github.com/afrail-inc/afrx-security-token/tree/main/branding) 28 | 29 | ## Overview 30 | 31 | AFRX is a fully compliant security token issued by Afrail Inc., designed for global investor participation under **Regulation D Rule 506(c)** and **Regulation S**. This smart contract incorporates: 32 | 33 | - On-chain jurisdiction control 34 | - Whitelisting/KYC enforcement 35 | - Dividend claims by snapshot 36 | - Buyback & burn mechanics 37 | - UUPS upgradeable proxy architecture 38 | 39 | ## Features 40 | 41 | - ERC-3643 Compliance (built on ERC-20 base) 42 | - On-chain KYC/AML and jurisdiction whitelisting 43 | - 365-day lockup enforcement per investor 44 | - Snapshot-based dividend distribution (`ERC20Snapshot`) 45 | - Dividend claim function with double-claim protection 46 | - Token buyback and burn mechanism 47 | - Role-based access control (`AccessControl`) 48 | - UUPS upgradeable architecture 49 | - Emergency pause/unpause functionality 50 | 51 | ## Smart Contract Architecture 52 | 53 | The AFRX Security Token is designed to meet enterprise and regulatory requirements under SEC Regulation D Rule 506(c) and Regulation S. 54 | 55 | - **Token Symbol:** AFRX 56 | - **Token Cap:** 5,770,000,000 57 | - **Standard:** ERC-3643 58 | - **Lockup Period:** 365 days 59 | - **Compliance:** Jurisdiction + whitelist-based investor restrictions 60 | - **Upgradeability:** Yes (via UUPS Proxy) 61 | - **Audit Status:** Final audit pending (CertiK); Pre-reviewed by Okiki Omisande (CodeHawks, Immunefi) 62 | 63 | ## AFRX Security Token – Official Token Design 64 | 65 | This image represents the official token design for the AFRX Security Token, issued by Afrail Inc. under the ERC-3643 standard. The design is not just symbolic — it encapsulates the token’s core attributes of compliance, programmability, and real-world equity backing. 66 | 67 | **🔍 Token Design Breakdown:** 68 | 69 | Central Symbol (Modified **“a”** with Tail and Square Dot) 70 | The stylized central emblem is a unique take on the lowercase "a", referencing **Afrail Inc.** as the issuer. 71 | 72 | The vertical tail suggests directional innovation — **mobility** and **movement**. 73 | 74 | The square dot beneath the tail represents **precision**, **order**, and **finality** — hallmarks of smart contract determinism and regulatory clarity. 75 | 76 | **Text Engraving:** 77 | 78 | Top Arc: **AFRX SECURITY TOKEN ERC-3643**: This reinforces the token’s regulatory compliance as a tokenized security, adhering to globally recognized standards for permissioned tokens with KYC/AML layers. 79 | 80 | Bottom Arc: **BACKED BY REAL EQUITY IN AFRAIL INC**: This line affirms that AFRX is not a utility token or speculative cryptocurrency. Each token is linked to actual equity held in Afrail Inc., providing *intrinsic value* and *real ownership*. 81 | 82 | Side Stars (at 9 and 3 o’clock) 83 | The symmetrical stars symbolize stability and compliance — referencing dual oversight: one by the issuer (Afrail Inc.), the other by regulatory frameworks. Their positioning balances the circular theme, reinforcing the idea of completeness and trust. 84 | 85 | **Gold Finish:** 86 | The gold coloration is deliberate — symbolizing: 87 | 88 | - Asset-backing 89 | - Investor-grade security 90 | - Long-term value 91 | 92 | It visually separates AFRX from typical volatile tokens by emphasizing trust, regulation, and real equity. 93 | 94 | **🛍 Purpose and Usage:** 95 | 96 | This token design should appear in all: 97 | 98 | - ✅ Official investor materials 99 | - ✅ Product whitepapers 100 | - ✅ Regulatory filings (visual representation) 101 | - ✅ Press releases 102 | - ✅ Token dashboards or portals (where graphical representations are supported) 103 | 104 | ## 🔐 Section 7: Proceeds Management and Escrow Strategy 105 | 106 | All cash proceeds from the sale of AFRX Security Tokens will be paid directly into a regulated **Escrow Account** upon commencement of token production and sale. This structure ensures: 107 | 108 | - ✅ Transparency 109 | - ✅ Investor protection 110 | - ✅ Regulatory and compliance assurance 111 | 112 | Afrail Inc. **will not have access to these funds** until a minimum capital raise threshold is met. 113 | 114 | ### 🎯 Minimum Raise Requirement 115 | 116 | To unlock funds from the escrow: 117 | 118 | - **Target Raise:** $4.039 billion USD 119 | - **Minimum Threshold (10%):** $403.9 million USD 120 | 121 | Once 10% is raised and verified, Afrail Inc. may begin drawing down funds in accordance with an approved use-of-funds schedule. 122 | 123 | ### 🏗️ Bridge Financing for Early Operations 124 | 125 | Before reaching 10%, Afrail Inc. will pursue **short-term, low-interest loans backed by the escrow balance**, for: 126 | 127 | - 🏙️ Pre-engineering in **South Florida** 128 | - 🌍 Site preparation in **Northern Namibia** 129 | 130 | This enables early progress while maintaining responsible financial safeguards. 131 | 132 | ### ✅ Benefits of This Structure 133 | 134 | - ⚖️ **Investor Assurance:** No premature access to capital 135 | - 🔐 **Compliance:** Aligned with security token regulatory best practices 136 | - 🚀 **Readiness:** Enables engineering momentum with escrow-backed credibility 137 | 138 | ## Contract Versions 139 | 140 | - **AFRXToken.sol** – Current production contract (v1.1) with enhanced compliance and dividend distribution. 141 | - **AFRXToken_legacy.sol** – Archived legacy contract retained for historical reference. 142 | 143 | ## Installation 144 | 145 | ```solidity 146 | // Claim dividends for a round 147 | AFRXToken.claimDividends(roundId); 148 | 149 | // Admin distributes dividends (snapshot is auto-generated) 150 | AFRXToken.distributeDividends(totalAmount); 151 | 152 | // Add investor to whitelist 153 | AFRXToken.whitelistInvestor(address, "United States"); 154 | 155 | // Issue tokens to KYC'd investor 156 | AFRXToken.issueTokens(address, 1000 ether, "United States"); 157 | ``` 158 | 159 | ## License 160 | 161 | MIT License 162 | 163 | Copyright (c) 2025 Afrail Inc. 164 | 165 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 166 | 167 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 168 | 169 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 170 | 171 | ## Clone the repository 172 | 173 | ```bash 174 | git clone https://github.com/afrail-inc/afrx-security-token.git 175 | cd afrx-security-token 176 | ``` 177 | -------------------------------------------------------------------------------- /AFRXTokenV1_18.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; 2 | 3 | import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20CappedUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 4 | 5 | contract AFRXTokenV1_18 is Initializable, ERC20CappedUpgradeable, ERC20BurnableUpgradeable, ERC20SnapshotUpgradeable, PausableUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE"); bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); 6 | 7 | uint256 public constant LOCKUP_PERIOD = 365 days; 8 | uint8 public constant TOKEN_DECIMALS = 18; 9 | 10 | bool public isTestMode; 11 | address public treasury; 12 | 13 | struct InvestorInfo { 14 | uint256 releaseTime; 15 | bool isWhitelisted; 16 | string jurisdiction; 17 | } 18 | 19 | struct DividendInfo { 20 | uint256 totalAmount; 21 | uint256 snapshotId; 22 | uint256 totalSupplyAtSnapshot; 23 | uint256 reclaimedAmount; 24 | } 25 | 26 | mapping(address => InvestorInfo) private _investors; 27 | mapping(bytes32 => bool) private allowedJurisdictions; 28 | mapping(uint256 => DividendInfo) public dividendInfo; 29 | mapping(uint256 => mapping(address => bool)) public dividendsClaimed; 30 | mapping(uint256 => uint256) public snapshotTimestamps; 31 | uint256 public currentDividendRound; 32 | 33 | event TokensIssued(address indexed investor, uint256 amount, string jurisdiction); 34 | event InvestorWhitelisted(address indexed investor, string jurisdiction); 35 | event InvestorRemovedFromWhitelist(address indexed investor); 36 | event JurisdictionAdded(string jurisdiction); 37 | event JurisdictionRemoved(string jurisdiction); 38 | event ContractPaused(string reason); 39 | event ContractUnpaused(); 40 | event DividendsAvailable(uint256 indexed roundId, uint256 totalAmount); 41 | event DividendsClaimed(uint256 indexed roundId, address indexed investor, uint256 amount); 42 | event LockupReleased(address indexed investor); 43 | event Upgraded(address indexed newImplementation); 44 | event TestModeToggled(bool status); 45 | event DividendsReclaimed(uint256 indexed roundId, uint256 amount); 46 | 47 | modifier onlyWhitelisted(address account) { 48 | require(_investors[account].isWhitelisted, "Investor not whitelisted"); 49 | _; 50 | } 51 | 52 | constructor() initializer {} 53 | 54 | function initialize(address admin, address _treasury) public initializer { 55 | require(_treasury != address(0), "Treasury address required"); 56 | 57 | __ERC20_init("Afrail Security Token", "AFRX"); 58 | __ERC20Capped_init(5_770_000_000 * (10 ** TOKEN_DECIMALS)); 59 | __ERC20Burnable_init(); 60 | __ERC20Snapshot_init(); 61 | __Pausable_init(); 62 | __AccessControl_init(); 63 | __ReentrancyGuard_init(); 64 | __UUPSUpgradeable_init(); 65 | 66 | treasury = _treasury; 67 | 68 | _grantRole(DEFAULT_ADMIN_ROLE, admin); 69 | _grantRole(ADMIN_ROLE, admin); 70 | _grantRole(MINTER_ROLE, admin); 71 | _grantRole(PAUSER_ROLE, admin); 72 | _grantRole(SNAPSHOT_ROLE, admin); 73 | _grantRole(UPGRADER_ROLE, admin); 74 | } 75 | 76 | function issueTokens(address investor, uint256 amount, string memory jurisdiction) 77 | external 78 | onlyRole(MINTER_ROLE) 79 | whenNotPaused 80 | { 81 | require(totalSupply() + amount <= cap(), "Cap exceeded"); 82 | require(_isJurisdictionAllowed(jurisdiction), "Jurisdiction not allowed"); 83 | 84 | _mint(investor, amount); 85 | 86 | if (_investors[investor].releaseTime == 0) { 87 | _investors[investor] = InvestorInfo({ 88 | releaseTime: block.timestamp + LOCKUP_PERIOD, 89 | isWhitelisted: true, 90 | jurisdiction: _normalizeString(jurisdiction) 91 | }); 92 | } 93 | 94 | emit TokensIssued(investor, amount, jurisdiction); 95 | } 96 | 97 | function batchIssueTokens(address[] calldata investors, uint256[] calldata amounts, string[] calldata jurisdictions) 98 | external 99 | onlyRole(MINTER_ROLE) 100 | whenNotPaused 101 | { 102 | require(investors.length == amounts.length && amounts.length == jurisdictions.length, "Array lengths mismatch"); 103 | for (uint256 i = 0; i < investors.length; ++i) { 104 | issueTokens(investors[i], amounts[i], jurisdictions[i]); 105 | } 106 | } 107 | 108 | function distributeDividends(uint256 totalAmount) external onlyRole(ADMIN_ROLE) nonReentrant whenNotPaused { 109 | require(totalSupply() > 0, "No tokens in circulation"); 110 | require(treasury != address(0), "Treasury not set"); 111 | require(balanceOf(treasury) >= totalAmount, "Insufficient treasury balance"); 112 | 113 | uint256 snapshotId = _snapshot(); 114 | uint256 snapshotSupply = totalSupply() - balanceOf(address(this)); 115 | 116 | _transfer(treasury, address(this), totalAmount); 117 | 118 | dividendInfo[currentDividendRound] = DividendInfo({ 119 | totalAmount: totalAmount, 120 | snapshotId: snapshotId, 121 | totalSupplyAtSnapshot: snapshotSupply, 122 | reclaimedAmount: 0 123 | }); 124 | 125 | snapshotTimestamps[snapshotId] = block.timestamp; 126 | 127 | emit DividendsAvailable(currentDividendRound, totalAmount); 128 | currentDividendRound++; 129 | } 130 | 131 | function claimDividends(uint256 roundId) external nonReentrant whenNotPaused onlyWhitelisted(msg.sender) { 132 | require(!dividendsClaimed[roundId][msg.sender], "Already claimed"); 133 | 134 | DividendInfo storage dividend = dividendInfo[roundId]; 135 | uint256 balance = balanceOfAt(msg.sender, dividend.snapshotId); 136 | require(balance > 0, "No tokens at snapshot"); 137 | 138 | uint256 share = (balance * dividend.totalAmount) / dividend.totalSupplyAtSnapshot; 139 | require(share > 0, "No dividends to claim"); 140 | 141 | dividendsClaimed[roundId][msg.sender] = true; 142 | _transfer(address(this), msg.sender, share); 143 | 144 | emit DividendsClaimed(roundId, msg.sender, share); 145 | } 146 | 147 | function reclaimUnclaimedDividends(uint256 roundId, address to) external onlyRole(ADMIN_ROLE) { 148 | require(block.timestamp > snapshotTimestamps[dividendInfo[roundId].snapshotId] + 365 days, "Too early to reclaim"); 149 | require(to != address(0), "Invalid address"); 150 | 151 | uint256 remaining = balanceOf(address(this)); 152 | dividendInfo[roundId].reclaimedAmount = remaining; 153 | _transfer(address(this), to, remaining); 154 | 155 | emit DividendsReclaimed(roundId, remaining); 156 | } 157 | 158 | function releaseLockup(address investor) external onlyRole(ADMIN_ROLE) { 159 | _investors[investor].releaseTime = block.timestamp; 160 | emit LockupReleased(investor); 161 | } 162 | 163 | function toggleTestMode(bool status) external onlyRole(ADMIN_ROLE) { 164 | isTestMode = status; 165 | emit TestModeToggled(status); 166 | } 167 | 168 | function whitelistInvestor(address investor, string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 169 | require(_isJurisdictionAllowed(jurisdiction), "Jurisdiction not allowed"); 170 | _investors[investor].isWhitelisted = true; 171 | _investors[investor].jurisdiction = _normalizeString(jurisdiction); 172 | emit InvestorWhitelisted(investor, jurisdiction); 173 | } 174 | 175 | function removeFromWhitelist(address investor) external onlyRole(ADMIN_ROLE) { 176 | _investors[investor].isWhitelisted = false; 177 | emit InvestorRemovedFromWhitelist(investor); 178 | } 179 | 180 | function addJurisdiction(string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 181 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 182 | allowedJurisdictions[j] = true; 183 | emit JurisdictionAdded(jurisdiction); 184 | } 185 | 186 | function removeJurisdiction(string memory jurisdiction) external onlyRole(ADMIN_ROLE) { 187 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 188 | allowedJurisdictions[j] = false; 189 | emit JurisdictionRemoved(jurisdiction); 190 | } 191 | 192 | function pause(string memory reason) external onlyRole(PAUSER_ROLE) { 193 | _pause(); 194 | emit ContractPaused(reason); 195 | } 196 | 197 | function unpause() external onlyRole(PAUSER_ROLE) { 198 | _unpause(); 199 | emit ContractUnpaused(); 200 | } 201 | 202 | function snapshot() external onlyRole(SNAPSHOT_ROLE) { 203 | _snapshot(); 204 | } 205 | 206 | function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) { 207 | emit Upgraded(newImplementation); 208 | } 209 | 210 | function _isJurisdictionAllowed(string memory jurisdiction) internal view returns (bool) { 211 | bytes32 j = keccak256(abi.encodePacked(_normalizeString(jurisdiction))); 212 | return allowedJurisdictions[j]; 213 | } 214 | 215 | function _normalizeString(string memory str) internal pure returns (string memory) { 216 | bytes memory bStr = bytes(str); 217 | for (uint256 i = 0; i < bStr.length; i++) { 218 | if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) { 219 | bStr[i] = bytes1(uint8(bStr[i]) + 32); 220 | } 221 | } 222 | return string(bStr); 223 | } 224 | 225 | function decimals() public pure override returns (uint8) { 226 | return TOKEN_DECIMALS; 227 | } 228 | 229 | function supportsInterface(bytes4 interfaceId) public view override(AccessControlUpgradeable) returns (bool) { 230 | return super.supportsInterface(interfaceId); 231 | } 232 | 233 | } 234 | 235 | --------------------------------------------------------------------------------