Libro blanco / Documentación Técnica FIV LATAM COIN (FIV)
Resumen Ejecutivo
FIV LATAM COIN es un token ERC-20 avanzado diseñado para potenciar una economía descentralizada y fortalecer la adopción de pagos digitales en empresas, marketplaces y plataformas comerciales.
Gracias a su integración con negocios, FIV no solo ofrece un mecanismo de inversión, sino que también brinda descuentos exclusivos, beneficios comerciales y una red sólida de aceptación, fortaleciendo su utilidad como método de pago.
Con un enfoque claro hacia la seguridad y sostenibilidad, FIV se posiciona como una herramienta fundamental en el ecosistema de inversión y tecnología blockchain de FIV LATAM, un proyecto que busca el crecimiento y desarrollo económico digital en Latinoamérica y a nivel global.
Este documento describe la arquitectura, las funcionalidades clave y las aplicaciones prácticas del token FIV, destacando su papel en la inclusión financiera y la innovación digital.
1. Equipo
– Renzo Montero: Fundador, CEO y desarrollador de software
– Brayán Montero: Director de Operaciones
– Vianni Torrealba: Marketing Director
2. Visión y Misión
🔹 Visión: Empoderar a individuos, empresas y comunidades mediante herramientas financieras descentralizadas, transparentes y seguras.
🔹 Misión: Crear un ecosistema blockchain autosostenible con incentivos reales para sus participantes, mediante un token robusto, gobernado por su comunidad y respaldado por tecnología de última generación.
3. El Problema y la Solución
Problemas actuales:
– Falta de confianza en sistemas financieros centralizados.
– Inflación y devaluación en monedas fiat.
– Limitación para acceder a inversiones de proyectos cripto
– Limitación participación democrática en decisiones de proyectos cripto.
Soluciones de FIV LATAM COIN:
– Infraestructura propia en desarrollo, incluyendo wallet no custodia.
– Staking con recompensas justas y sostenibles.
– Adopción en comercios y plataformas digitales que aceptarán FIV como método de pago con descuentos exclusivos. – Vesting programado para asegurar una liberación gradual y sostenible de tokens a largo plazo.
4. Tecnología y Arquitectura del Token
FIV LATAM COIN es un token ERC-20 Upgradeable desarrollado con OpenZeppelin, con las siguientes características:
📅 Token estándar ERC-20 con actualizaciones sin perder datos.
🔹 Sistema de staking con recompensas ajustables.
📈 Eventos para trazabilidad de transacciones, votaciones y recompensas.
🔠 Compatible con Ethereum, BNB Chain, Polygon, Avalanche, entre otras redes EVM.
5. Economía del Token
Característica Valor
Nombre del token FIV LATAM COIN
Símbolo FIV
Suministro inicial 100,000,000 FIV
Suministro máximo 220,000,000 FIV
Tasa de recompensas 20% APR (ajustable)
Límite total recompensas de 20,000,000 FIV
Incentivos para negocios Empresas que acepten FIV recibirán bonificaciones y descuentos
6. Sistema de Staking y Recompensas
🔹 Los holders pueden bloquear sus tokens para participar en el staking.
🔹 Las recompensas se calculan por tiempo de staking y monto bloqueado.
🔹 Límite total de recompensas: 20M FIV.
🔹 Tasa de recompensa predeterminada: 20% APR (ajustable por el owner).
🔹 Penalizaciones por retiro temprano:
- Se debe esperar mínimo 24 horas para poder reclamar recompensas.
- Unstake está restringido al menos 24 horas después del stake.
- Penalización del 10% si el usuario retira antes de 7 días, monto penalizado enviado a
treasuryWallet
. - En caso de emergencia (contrato pausado), los usuarios pueden hacer unstake sin penalización ni recompensa.
7. Roadmap del Proyecto
Fase Objetivos
Q1 2025 🔹 Desarrollo del contrato inteligente
🔹 Despliegue en testnet
🔹 Revisión técnica
Q2 2025 🔄 Auditoría externa
🚀 Despliegue en mainnet
🌐 Sitio web oficial y whitepaper
Q3 2025 📢 Campañas de marketing
🧹 Integración con exchanges DEX
💬 Comunidad y votaciones
Q4 2025 🛠️ Gobernanza activa y primeras propuestas
📈 Listado en CoinMarketCap y CoinGecko
Q1 2026 🧱 Implementación de casos de uso reales (pagos, marketplace)
🤝 Alianzas estratégicas
8. Conclusiones
FIV LATAM COIN es más que un token: es una herramienta poderosa para la construcción de un sistema financiero descentralizado, justo y transparente. Con una arquitectura sólida, mecanismos de recompensas e incentivos claros, FIV busca liderar el cambio hacia una nueva economía digital en América Latina y el mundo.
9. Anexos técnicos
- Lenguaje: Solidity 0.8.20
- Bibliotecas usadas: OpenZeppelin (ERC20, Ownable, Pausable, ReentrancyGuard, UUPSUpgradeable)
- Interfaz compatible: ERC20, IERC20Metadata
Eventos definidos:
Staked(address indexed user, uint256 amount)
Unstaked(address indexed user, uint256 amount, uint256 penalty)
RewardClaimed(address indexed user, uint256 amount)
TokensReleased(address indexed user, uint256 amount)
(vesting)TreasuryWalletUpdated(address indexed oldWallet, address indexed newWallet)
10. Información del Contrato en BNB Chain
Para asegurar la transparencia total del proyecto, el contrato inteligente de FIV LATAM COIN ha sido desplegado bajo un patrón de upgradeabilidad UUPS.
✅ Dirección del contrato proxy (interfaz activa para usuarios):
0x994A99E728b10709FA7bF27AECEbE4F987254073
📦 Dirección del contrato de implementación (lógica del contrato):
0x8b5038305Ad2087904F83f8b94622EF696707f5F
Ambas direcciones están verificadas en BscScan y accesibles públicamente para inspección del código, historial de transacciones, eventos emitidos y cualquier interacción on-chain.
📄 Ver el código completo del Smart Contract
// SPDX-License-Identifier: MIT
/**
* @title FIV LATAM Coin (FIV)
* @dev ERC20 token contract featuring staking, vesting, and upgradeable mechanisms designed for the FIV ecosystem.
*
* Features:
* - Staking system with reward distribution and early withdrawal penalties
* - Annual token vesting with scheduled releases to the treasury wallet
* - Emergency pause and unpause functionality (affects staking only)
* - UUPS (Universal Upgradeable Proxy Standard) upgradeability support
*
* Key Parameters:
* - Initial supply: 100,000,000 FIV
* - Maximum supply cap: 220,000,000 FIV
* - Reward pool cap: 20,000,000 FIV
* - Maximum staking reward rate: 20% per year (accrues daily)
*
* Additional Functionality:
* - Rewards accrue daily and can be claimed once every 24 hours
* - 10% penalty applied for early unstaking (before 7 days)
* - Minimum staking duration of 24 hours
* - 25-year vesting schedule utilizing decreasing annual divisors
*
* Authorization & Security:
* - Ownable pattern for administrative control, limited to critical operations
* - Upgrades restricted to the owner through the UUPS pattern
* - Treasury wallet address configurable by the owner
*
* Governance:
* - Multisignature governance managed externally (via Gnosis Safe multisig)
*
* Developed by: Renzo Montero
* Website: https://fivlatam.com/
* Year: 2025
*/
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.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-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
contract FIVLATAMCoin is Initializable, ERC20Upgradeable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable, ERC165Upgradeable {
// ===================== CONSTANTS =====================
uint256 public constant INITIAL_SUPPLY = 100000000 * 10**18; // Minted at deployment
uint256 public constant VESTING_SUPPLY = 100000000 * 10**18; // Reference only: intended for 25-year vesting, released gradually
uint256 public constant MAX_SUPPLY = 220000000 * 10**18; // Hard cap
uint256 public constant MAX_REWARD_RATE = 20; // Max 20% APY
uint256 public constant REWARD_SUPPLY_LIMIT = 20000000 * 10**18; // Max tokens for staking rewards
uint256 public constant RELEASE_INTERVAL = 365 days; // Vesting release frequency
uint256 public constant MIN_STAKE_DURATION = 1 days; // Minimum lock period
uint256 public constant MIN_STAKE_AMOUNT = 1e15; // Minimum stake: 0.001 FIV
uint256 public constant EARLY_UNSTAKE_DURATION = 7 days; // Duration within which penalty applies
uint256 public constant EARLY_UNSTAKE_PENALTY_RATE = 10; // 10% penalty for early unstake
uint256 public constant CLAIM_INTERVAL = 1 days; // Time between allowed reward claims
uint256 public constant MAX_ACCUMULABLE_REWARD = 100000 * 1e18; // Max pending reward per stake to avoid overflow
uint256 public constant MAX_ACCUMULATION_DURATION = 90 days;
// ===================== CONFIGURABLE VARIABLES =====================
uint256 public rewardRate;
uint256[] private releaseDivisors;
uint256 public minReward;
// ===================== VESTING MONITORING =====================
uint256 public currentYear;
uint256 public lastReleaseTime;
// ===================== STAKING STATE =====================
/// @dev Represents an individual stake position tied to a specific index per user.
struct Stake {
uint256 amount;
uint256 stakedAt;
uint256 lastClaimed;
uint256 pendingReward;
}
mapping(address => Stake[]) private userStakes;
uint256 public totalStakedTokens;
uint256 public rewardDistributed;
// ===================== TOKEN VESTING =====================
address public treasuryWallet;
uint256 public vestingStart;
// ===================== EVENTS =====================
// Emitted when a user stakes tokens
event Staked(address indexed user, uint256 amount);
// Emitted when a user unstakes tokens and possibly receives a penalty
event Unstaked(address indexed user, uint256 amount, uint256 penaltyAmount);
// Emitted when a user claims staking rewards
event RewardClaimed(address indexed user, uint256 indexed stakeIndex, uint256 amount);
// Emitted when the reward pool has no tokens left
event RewardPoolDepleted();
// Emitted when tokens are released from vesting
event TokensReleased(uint256 year, uint256 amount);
// Emitted when the treasury wallet is updated
event TreasuryWalletUpdated(address indexed oldWallet, address indexed newWallet);
// Emitted when the staking reward rate is updated
event RewardRateUpdated(uint256 oldRate, uint256 newRate);
// Emitted when a user performs an emergency withdrawal
event EmergencyWithdrawal(address indexed user, uint256 amount);
// Emitted when rewards are calculated and accumulated for a user
event RewardAccumulated(address indexed user, uint256 stakeIndex, uint256 accumulated);
// Emitted when a user attempts to stake beyond the allowed limit
event StakeLimitReached(address indexed user, uint256 attemptedAmount);
// Emitted for soft validation/logging purposes, e.g., when a treasury is a contract
event Log(string message);
// Emitted once to mark the start of the vesting schedule
event VestingStarted(uint256 timestamp);
// ===================== INITIALIZATION =====================
function initialize(address _treasuryWallet) public initializer {
require(_treasuryWallet != address(0), "ERR1"); // ERR1: Invalid treasury wallet
__ERC20_init("FIV LATAM COIN", "FIV");
__Ownable_init();
__Pausable_init();
__ReentrancyGuard_init();
__ERC20Permit_init("FIV LATAM COIN");
__UUPSUpgradeable_init();
_mint(msg.sender, INITIAL_SUPPLY);
rewardRate = 20;
minReward = 1e15;
treasuryWallet = _treasuryWallet;
vestingStart = block.timestamp;
emit VestingStarted(vestingStart);
lastReleaseTime = block.timestamp;
currentYear = 0;
releaseDivisors = [
10, 11, 12, 13, 15,
16, 18, 19, 21, 24,
26, 28, 31, 34, 38,
42, 46, 51, 56, 62,
69, 76, 84, 93, 102
];
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
function _calculateReward(Stake memory userStake) internal view returns (uint256) {
if (userStake.amount == 0 || userStake.lastClaimed >= block.timestamp) {
return 0;
}
uint256 durationInSeconds = block.timestamp - userStake.lastClaimed;
uint256 yearlyRate = rewardRate; // Se toma directamente del estado del contrato
uint256 annualReward = (userStake.amount * yearlyRate) / 100;
uint256 reward = (annualReward * durationInSeconds) / 365 days;
return reward;
}
uint256 public constant TIMESTAMP_TOLERANCE = 60;
function _hasReachedTimeWithTolerance(uint256 current, uint256 target) internal pure returns (bool) {
return current + TIMESTAMP_TOLERANCE >= target;
}
// ===================== STAKING =====================
function stake(uint256 amount) external whenNotPaused nonReentrant {
// Prevent staking zero tokens
require(amount > 0, "ERR2"); // ERR2: Cannot stake zero tokens
// Enforce minimum stake amount
require(amount >= MIN_STAKE_AMOUNT, "ERR3"); // ERR3: Amount below minimum stake
// Ensure the user has sufficient balance
require(balanceOf(msg.sender) >= amount, "ERR4"); // ERR4: Insufficient balance to stake
// Enforce maximum number of active stakes per user
if (userStakes[msg.sender].length >= 10) {
emit StakeLimitReached(msg.sender, amount);
revert("ERR5"); // ERR5: Stake limit reached
}
// Transfer tokens from the user to the contract
_transfer(msg.sender, address(this), amount);
// Record the stake details
userStakes[msg.sender].push(Stake({
amount: amount,
stakedAt: block.timestamp,
lastClaimed: block.timestamp,
pendingReward: 0
}));
// Update global staking total
totalStakedTokens += amount;
emit Staked(msg.sender, amount);
}
function emergencyUnstake() external whenPaused nonReentrant {
Stake[] storage stakesLocal = userStakes[msg.sender];
// Ensure the user has active stakes
require(stakesLocal.length > 0, "ERR6"); // ERR6: No staked tokens to withdraw
uint256 totalAmount = 0;
// Sum the total amount staked by the user
for (uint256 i = 0; i < stakesLocal.length; i++) {
totalAmount += stakesLocal[i].amount;
}
// Ensure there is something to withdraw
require(totalAmount > 0, "ERR7"); // ERR7: No staked tokens to withdraw
// Ensure the contract has enough balance to return the funds
require(balanceOf(address(this)) >= totalAmount, "ERR8"); // ERR8: Insufficient contract balance
// Effects: clear the user's stake data before making external calls
delete userStakes[msg.sender];
totalStakedTokens -= totalAmount;
// Interactions: transfer the tokens back to the user
_transfer(address(this), msg.sender, totalAmount);
emit EmergencyWithdrawal(msg.sender, totalAmount);
}
function unstake(uint256 stakeIndex, uint256 amount) external whenNotPaused nonReentrant {
require(stakeIndex < userStakes[msg.sender].length, "ERR9"); // ERR9: Invalid stake index
require(amount > 0, "ERR10"); // ERR10: Cannot unstake zero tokens
Stake storage userStake = userStakes[msg.sender][stakeIndex];
require(userStake.amount >= amount, "ERR11"); // ERR11: Not enough staked tokens in this stake
uint256 originalAmount = amount;
uint256 penaltyAmount = 0;
// Mint reward before reducing the stake, if eligible
uint256 reward = _calculateReward(userStake);
if (
reward >= minReward &&
rewardDistributed + reward <= REWARD_SUPPLY_LIMIT &&
totalSupply() + reward <= MAX_SUPPLY
) {
rewardDistributed += reward;
userStake.lastClaimed = block.timestamp;
_mint(msg.sender, reward);
emit RewardClaimed(msg.sender, stakeIndex, reward);
}
// Check minimum staking duration with tolerance
if (!_hasReachedTimeWithTolerance(block.timestamp, userStake.stakedAt + MIN_STAKE_DURATION)) {
revert("ERR12"); // ERR12: Staking must be held for at least 24 hours
}
// Early unstake penalty applies
else if (block.timestamp < userStake.stakedAt + EARLY_UNSTAKE_DURATION) {
penaltyAmount = (amount * EARLY_UNSTAKE_PENALTY_RATE) / 100;
amount -= penaltyAmount;
_transfer(address(this), treasuryWallet, penaltyAmount);
}
userStake.amount -= originalAmount;
totalStakedTokens -= originalAmount;
if (userStake.amount == 0) {
// Remove the stake by swapping with last and popping to avoid gaps
uint256 lastIndex = userStakes[msg.sender].length - 1;
if (stakeIndex != lastIndex) {
userStakes[msg.sender][stakeIndex] = userStakes[msg.sender][lastIndex];
}
userStakes[msg.sender].pop();
}
_transfer(address(this), msg.sender, amount);
emit Unstaked(msg.sender, originalAmount, penaltyAmount);
}
function claimReward() external whenNotPaused nonReentrant {
Stake[] storage stakesLocal = userStakes[msg.sender];
uint256 stakeLen = stakesLocal.length;
require(stakeLen > 0, "ERR16"); // ERR16: No tokens staked
uint256 totalReward = 0;
uint256 dailyRate = rewardRate * 1e18;
uint256 currentTimestamp = block.timestamp;
for (uint256 i = 0; i < stakeLen; i++) {
Stake storage s = stakesLocal[i];
if (s.amount == 0) continue;
uint256 targetTimestamp = s.lastClaimed + CLAIM_INTERVAL;
// Check if the minimum claim interval has passed (with tolerance)
if (!_hasReachedTimeWithTolerance(currentTimestamp, targetTimestamp)) continue;
// Limit accumulation to avoid indefinite reward growth
uint256 timeElapsed = currentTimestamp - s.lastClaimed;
if (timeElapsed > MAX_ACCUMULATION_DURATION) {
timeElapsed = MAX_ACCUMULATION_DURATION;
}
// Calculate proportional reward based on time elapsed
uint256 reward = (s.amount * dailyRate * timeElapsed) / (365 * 1e18 * 1 days);
reward += s.pendingReward;
// If reward is below minimum, accumulate it
if (reward < minReward) {
uint256 newPending = s.pendingReward + reward;
s.pendingReward = newPending > MAX_ACCUMULABLE_REWARD
? MAX_ACCUMULABLE_REWARD
: newPending;
emit RewardAccumulated(msg.sender, i, reward);
continue;
}
// If reward is valid, update state
totalReward += reward;
s.pendingReward = 0;
s.lastClaimed = currentTimestamp;
emit RewardClaimed(msg.sender, i, reward);
}
require(totalReward > 0, "ERR13"); // ERR13: No rewards to claim
require(rewardDistributed + totalReward <= REWARD_SUPPLY_LIMIT, "ERR14"); // ERR14: Reward pool exhausted
require(totalSupply() + totalReward <= MAX_SUPPLY, "ERR15"); // ERR15: Max supply exceeded
rewardDistributed += totalReward;
_mint(msg.sender, totalReward);
}
// ===================== VIEW FUNCTIONS =====================
function getRewardAmount(address userAddress) external view returns (uint256) {
Stake[] storage stakesLocal = userStakes[userAddress];
if (
stakesLocal.length == 0 ||
rewardRate > MAX_REWARD_RATE ||
rewardDistributed >= REWARD_SUPPLY_LIMIT
) {
return 0;
}
uint256 totalReward = 0;
uint256 dailyRate = rewardRate * 1e18;
uint256 currentTimestamp = block.timestamp;
for (uint256 i = 0; i < stakesLocal.length; i++) {
Stake storage s = stakesLocal[i];
if (s.amount == 0) continue;
if (!_hasReachedTimeWithTolerance(currentTimestamp, s.lastClaimed + CLAIM_INTERVAL)) {
continue;
}
uint256 timeElapsed = currentTimestamp - s.lastClaimed;
if (timeElapsed > MAX_ACCUMULATION_DURATION) {
timeElapsed = MAX_ACCUMULATION_DURATION;
}
uint256 reward = (s.amount * dailyRate * timeElapsed) / (365 * 1e18 * 1 days);
reward += s.pendingReward;
if (reward < minReward) {
continue;
}
totalReward += reward;
}
uint256 remainingReward = REWARD_SUPPLY_LIMIT - rewardDistributed;
return totalReward > remainingReward ? remainingReward : totalReward;
}
function getStakeAmount(address user) external view returns (uint256) {
Stake[] storage stakesLocal = userStakes[user];
uint256 totalAmount = 0;
for (uint256 i = 0; i < stakesLocal.length; i++) {
totalAmount += stakesLocal[i].amount;
}
return totalAmount;
}
function getUserStakeDetails(address user) external view returns (
uint256[] memory amounts,
uint256[] memory stakedAts,
uint256[] memory lastClaimeds,
uint256[] memory pendingRewards
) {
Stake[] storage stakesLocal = userStakes[user];
uint256 len = stakesLocal.length;
amounts = new uint256[](len);
stakedAts = new uint256[](len);
lastClaimeds = new uint256[](len);
pendingRewards = new uint256[](len);
for (uint256 i = 0; i < len; i++) {
Stake storage s = stakesLocal[i];
amounts[i] = s.amount;
stakedAts[i] = s.stakedAt;
lastClaimeds[i] = s.lastClaimed;
pendingRewards[i] = s.pendingReward;
}
}
function getStakes(address user) external view returns (Stake[] memory) {
return userStakes[user];
}
function getRemainingRewardPool() public view virtual returns (uint256) {
return REWARD_SUPPLY_LIMIT - rewardDistributed;
}
// ===================== TOKEN RELEASE =====================
function releaseScheduledTokens() external onlyOwner {
require(_hasReachedTimeWithTolerance(block.timestamp, lastReleaseTime + RELEASE_INTERVAL), "ERR17"); // ERR17: Not time to release yet
require(currentYear < 25, "ERR18"); // ERR18: Vesting completed
require(currentYear < releaseDivisors.length, "ERR19"); // ERR19: Invalid release year
uint256 amount = 100000000 * 10**18 / releaseDivisors[currentYear];
require(amount > 0, "ERR20"); // ERR20: Amount must be greater than zero
require(totalSupply() + amount <= MAX_SUPPLY, "ERR21"); // ERR21: Exceeds max supply
_mint(treasuryWallet, amount);
lastReleaseTime = block.timestamp;
currentYear++;
emit TokensReleased(currentYear, amount);
}
function getNextScheduledReleaseInfo() external view returns (
uint256 nextAmount,
uint256 nextTimestamp,
uint256 currentYearActual
) {
currentYearActual = currentYear;
if (currentYearActual >= releaseDivisors.length) {
return (0, 0, currentYearActual);
}
nextAmount = 100000000 * 10**18 / releaseDivisors[currentYearActual];
nextTimestamp = lastReleaseTime + RELEASE_INTERVAL;
}
// ===================== OWNER FUNCTIONS =====================
/// @notice Ownership renouncement is intentionally disabled.
/// @dev This function exists only to satisfy the Ownable interface.
/// In this contract, ownership is expected to be managed externally (e.g., via Gnosis Safe).
/// Ownership should not be renounced to retain upgradeability and governance control.
function renounceOwnership() public view override onlyOwner {
revert("ERR22"); // ERR22: Renouncing ownership is disabled
}
function setTreasuryWallet(address newWallet) external onlyOwner {
require(newWallet != address(0), "ERR23"); // ERR23: Invalid address
address oldWallet = treasuryWallet;
treasuryWallet = newWallet;
emit TreasuryWalletUpdated(oldWallet, newWallet);
// Soft check: emits a log if the new treasury is a contract (likely a multisig wallet)
if (AddressUpgradeable.isContract(newWallet)) {
emit Log("New treasuryWallet is a contract, likely a multisig");
}
}
function setRewardRate(uint256 newRate) external onlyOwner {
require(newRate > 0 && newRate <= MAX_REWARD_RATE, "ERR24"); // ERR24: Invalid reward rate
uint256 oldRate = rewardRate;
rewardRate = newRate;
emit RewardRateUpdated(oldRate, newRate);
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return super.supportsInterface(interfaceId);
}
// ===================== UPGRADE GAP =====================
// Reserve storage space to allow for layout changes in the future
uint256[50] private __gap;
}