Skip to content

Instantly share code, notes, and snippets.

@masihtehrani
Created July 30, 2025 12:20
Show Gist options
  • Select an option

  • Save masihtehrani/293a850acb8f79d62e24f2018799f97c to your computer and use it in GitHub Desktop.

Select an option

Save masihtehrani/293a850acb8f79d62e24f2018799f97c to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.30+commit.73712a01.js&optimize=false&runs=200&gist=
// File: RwaToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// All necessary imports
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
// This struct definition remains the same
struct RwaTokenParams {
string name;
string symbol;
uint256 initialSupply;
address burnWallet;
address batchWallet;
address airdropWallet;
address supportWallet;
string tokenImageUrl;
string websiteUrl;
}
// CORRECTED: Removed ERC20Upgradeable from the inheritance list as it's already included in ERC20PermitUpgradeable
contract RwaToken is Initializable, AccessControlUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, ERC20PermitUpgradeable {
using SafeERC20 for IERC20;
// --- Roles ---
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant FREEZER_ROLE = keccak256("FREEZER_ROLE");
bytes32 public constant DISTRIBUTOR_ROLE = keccak256("DISTRIBUTOR_ROLE");
bytes32 public constant RESCUER_ROLE = keccak256("RESCUER_ROLE");
bytes32 public constant CONFIGURATOR_ROLE = keccak256("CONFIGURATOR_ROLE");
// --- State Variables ---
address public burnWallet;
address public batchWallet;
address public airdropWallet;
address public supportWallet;
string private _tokenImageUrl;
string private _websiteUrl;
mapping(address => bool) public frozenWallets;
// --- Constants ---
uint256 public constant DISTRIBUTION_LIMIT = 200;
// --- Events ---
event TokensBurned(address indexed from, uint256 amount);
event WalletFrozen(address indexed wallet);
event WalletUnfrozen(address indexed wallet);
event BatchWalletFunded(uint256 amount);
event AirdropWalletFunded(uint256 amount);
event TokensDistributed(address indexed fromWallet, uint256 totalAmount, uint256 userCount);
event TokenImageUrlUpdated(string newUrl);
event WebsiteUrlUpdated(string newUrl);
event TokensRescued(address indexed token, address indexed to, uint256 amount);
event SupportWalletUpdated(address indexed newWallet);
function initialize(RwaTokenParams calldata params) public initializer {
__ERC20_init(params.name, params.symbol);
__ERC20Permit_init(params.name);
__AccessControl_init();
__Pausable_init();
__ReentrancyGuard_init();
require(params.burnWallet != address(0), "RwaToken: Burn wallet cannot be zero");
require(params.batchWallet != address(0), "RwaToken: Batch wallet cannot be zero");
require(params.airdropWallet != address(0), "RwaToken: Airdrop wallet cannot be zero");
require(params.supportWallet != address(0), "RwaToken: Support wallet cannot be zero");
burnWallet = params.burnWallet;
batchWallet = params.batchWallet;
airdropWallet = params.airdropWallet;
supportWallet = params.supportWallet;
_tokenImageUrl = params.tokenImageUrl;
_websiteUrl = params.websiteUrl;
address deployer = _msgSender();
_grantRole(DEFAULT_ADMIN_ROLE, deployer);
_grantRole(PAUSER_ROLE, deployer);
_grantRole(FREEZER_ROLE, deployer);
_grantRole(DISTRIBUTOR_ROLE, deployer);
_grantRole(RESCUER_ROLE, deployer);
_grantRole(CONFIGURATOR_ROLE, deployer);
_mint(deployer, params.initialSupply);
}
modifier whenNotFrozen(address account) {
require(!frozenWallets[account], "RwaToken: Wallet is frozen");
_;
}
/**
* @dev Central hook for all transfer-related checks.
*/
// CORRECTED: Simplified override. The compiler now understands which function to override.
function _update(address from, address to, uint256 value) internal virtual override {
require(!paused(), "RwaToken: token transfer while paused");
if (from == address(0) || to == address(0)) {
super._update(from, to, value);
return;
}
require(from != to, "RwaToken: Self-transfer is not allowed");
require(!frozenWallets[from], "RwaToken: Sender wallet is frozen");
require(!frozenWallets[to], "RwaToken: Receiver wallet is frozen");
if (from == supportWallet || to == supportWallet) {
super._update(from, to, value);
return;
}
require(!hasRole(DISTRIBUTOR_ROLE, from), "RwaToken: Distributors must use funding functions");
require(to != burnWallet, "RwaToken: Use burn() instead of direct transfer");
require(to != batchWallet, "RwaToken: Use fundBatchWallet() instead of direct transfer");
require(to != airdropWallet, "RwaToken: Use fundAirdropWallet() instead of direct transfer");
super._update(from, to, value);
}
function decimals() public pure override returns (uint8) { return 3; }
function tokenImageUrl() public view returns (string memory) { return _tokenImageUrl; }
function websiteUrl() public view returns (string memory) { return _websiteUrl; }
function burn(uint256 amount) public whenNotFrozen(_msgSender()) {
require(amount > 0, "RwaToken: Burn amount must be > 0");
_burn(_msgSender(), amount);
}
function pause() public onlyRole(PAUSER_ROLE) { _pause(); }
function unpause() public onlyRole(PAUSER_ROLE) { _unpause(); }
function setTokenImageUrl(string memory newUrl) public onlyRole(CONFIGURATOR_ROLE) {
_tokenImageUrl = newUrl;
emit TokenImageUrlUpdated(newUrl);
}
function setWebsiteUrl(string memory newUrl) public onlyRole(CONFIGURATOR_ROLE) {
_websiteUrl = newUrl;
emit WebsiteUrlUpdated(newUrl);
}
function setSupportWallet(address newWallet) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(newWallet != address(0), "RwaToken: Invalid address");
supportWallet = newWallet;
emit SupportWalletUpdated(newWallet);
}
function freezeWallet(address account, bool freeze) public onlyRole(FREEZER_ROLE) {
require(account != address(0), "RwaToken: Cannot freeze zero address");
frozenWallets[account] = freeze;
if (freeze) emit WalletFrozen(account);
else emit WalletUnfrozen(account);
}
function fundBatchWallet(uint256 amount) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant {
require(amount > 0, "RwaToken: Amount must be > 0");
uint256 currentBalance = balanceOf(batchWallet);
if (currentBalance > 0) _burn(batchWallet, currentBalance);
_transfer(_msgSender(), batchWallet, amount);
emit BatchWalletFunded(amount);
}
function fundAirdropWallet(uint256 amount) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant {
require(amount > 0, "RwaToken: Amount must be > 0");
uint256 currentBalance = balanceOf(airdropWallet);
if (currentBalance > 0) _burn(airdropWallet, currentBalance);
_transfer(_msgSender(), airdropWallet, amount);
emit AirdropWalletFunded(amount);
}
function distributeFromBatch(address[] calldata users, uint256[] calldata amounts) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant {
distributeFrom(batchWallet, users, amounts);
}
function distributeFromAirdrop(address[] calldata users, uint256[] calldata amounts) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant {
distributeFrom(airdropWallet, users, amounts);
}
function distributeFrom(address fromWallet, address[] calldata users, uint256[] calldata amounts) internal {
uint256 usersLength = users.length;
require(usersLength > 0 && usersLength <= DISTRIBUTION_LIMIT && usersLength == amounts.length, "RwaToken: Invalid distribution arrays");
uint256 totalAmount = 0;
for (uint256 i = 0; i < usersLength; ) {
totalAmount += amounts[i];
unchecked { ++i; }
}
require(balanceOf(fromWallet) >= totalAmount, "RwaToken: Insufficient funds in source wallet");
for (uint256 i = 0; i < usersLength; ) {
require(users[i] != address(0), "RwaToken: Invalid recipient");
_transfer(fromWallet, users[i], amounts[i]);
unchecked { ++i; }
}
emit TokensDistributed(fromWallet, totalAmount, usersLength);
}
function rescueErc20(address tokenAddress, address to, uint256 amount) public onlyRole(RESCUER_ROLE) {
require(amount > 0, "RwaToken: Amount must be > 0");
require(tokenAddress != address(this), "RwaToken: Cannot rescue self");
IERC20(tokenAddress).safeTransfer(to, amount);
emit TokensRescued(tokenAddress, to, amount);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment