// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract PureBi { // --- 0. BRANDING --- string public constant name = "PureBilingualVersation Research Registry"; string public constant symbol = "PURE BI-VERSATION"; // --- 1. IDENTITY & REPUTATION (PER DIALECT) --- mapping(address => bool) public isRegistered; // user -> dialect -> xp mapping(address => mapping(string => uint256)) public userXP; // user -> dialect -> role mapping(address => mapping(string => string)) public userRole; // Global admin checking mapping(address => bool) public isAdmin; address public labAdmin; // The Gasless Relayer (Python Backend) // --- 2. REGISTRY & LICENSING --- struct DialectEntry { uint256 id; string phrase; string dialect; string ipfsCid; address contributor; string licenseType; uint256 verificationCount; uint256 rejectionCount; bool isVerified; bool isBurned; } uint256 public entryCounter; mapping(uint256 => DialectEntry) public entries; // Consensus Tracking (entry_id -> user -> bool) mapping(uint256 => mapping(address => bool)) public hasVoted; // The Burn Ward Tracking mapping(uint256 => string) public burnReasons; mapping(uint256 => address[]) public rejectorsList; mapping(uint256 => uint256) public appealCount; mapping(uint256 => mapping(address => bool)) public hasAppealed; // --- 3. BACKUP SYSTEM --- struct DataSnapshot { uint256 timestamp; string dataType; string ipfsCid; string description; } mapping(string => DataSnapshot[]) public backups; // --- 4. EVENTS --- event EntryProposed(uint256 indexed id, string phrase, address indexed proposer); event EntryVerified(uint256 indexed id, address indexed verifier); event EntryRejected(uint256 indexed id, address indexed rejector, string reason); event EntryBurned(uint256 indexed id, address indexed contributor, string reason); event EntryAppealed(uint256 indexed id, address indexed appealer, bool success); event ReputationMinted(address indexed researcher, string dialect, uint256 amount); event RankUpgraded(address indexed researcher, string dialect, string newRole); event BackupCreated(string indexed dataKey, string ipfsCid, uint256 timestamp); constructor() { labAdmin = msg.sender; // The Python Backend Wallet isAdmin[msg.sender] = true; } // --- MODIFIERS --- modifier onlyRelayer() { require(msg.sender == labAdmin, "Only the Python Relayer can execute transactions"); _; } // --- CORE FUNCTIONS --- function proposeEntry(string memory _phrase, string memory _dialect, string memory _ipfsCid, string memory _license, address _contributor) public onlyRelayer returns (uint256) { entryCounter++; entries[entryCounter] = DialectEntry(entryCounter, _phrase, _dialect, _ipfsCid, _contributor, _license, 0, 0, false, false); emit EntryProposed(entryCounter, _phrase, _contributor); if (!isRegistered[_contributor]) { isRegistered[_contributor] = true; } // Auto-register brand new React users as Initiates in this dialect if they have no role if (bytes(userRole[_contributor][_dialect]).length == 0 && !isAdmin[_contributor]) { userRole[_contributor][_dialect] = "Initiate"; } return entryCounter; } function verifyEntry(uint256 _id, address _verifier) public onlyRelayer { DialectEntry storage entry = entries[_id]; require(!entry.isVerified && !entry.isBurned, "Entry already resolved"); require(entry.contributor != _verifier, "Cannot verify own entry"); require(!hasVoted[_id][_verifier], "User already voted"); string memory role = userRole[_verifier][entry.dialect]; bool isGod = isAdmin[_verifier]; bool isOracle = keccak256(bytes(role)) == keccak256(bytes("Oracle")); require(isGod || isOracle, "Only Admins or Oracles can verify"); hasVoted[_id][_verifier] = true; _mintReputation(_verifier, entry.dialect, 10); // +10 XP for doing the audit if (isGod) { _applyVerification(_id); } else { entry.verificationCount++; if (entry.verificationCount >= 2) { _applyVerification(_id); } } } function _applyVerification(uint256 _id) internal { entries[_id].isVerified = true; _mintReputation(entries[_id].contributor, entries[_id].dialect, 50); // +50 XP to the original submitter emit EntryVerified(_id, msg.sender); } function rejectEntry(uint256 _id, address _rejector, string memory _reason) public onlyRelayer { DialectEntry storage entry = entries[_id]; require(!entry.isVerified && !entry.isBurned, "Entry already resolved"); require(entry.contributor != _rejector, "Cannot reject own entry"); require(!hasVoted[_id][_rejector], "User already voted"); string memory role = userRole[_rejector][entry.dialect]; bool isGod = isAdmin[_rejector]; bool isOracle = keccak256(bytes(role)) == keccak256(bytes("Oracle")); require(isGod || isOracle, "Only Admins or Oracles can reject"); hasVoted[_id][_rejector] = true; rejectorsList[_id].push(_rejector); // Track for potential penalty _mintReputation(_rejector, entry.dialect, 10); // +10 XP for doing the audit emit EntryRejected(_id, _rejector, _reason); if (isGod) { _applyBurn(_id, _reason); } else { entry.rejectionCount++; if (entry.rejectionCount >= 3) { _applyBurn(_id, _reason); } } } function _applyBurn(uint256 _id, string memory _reason) internal { entries[_id].isBurned = true; burnReasons[_id] = _reason; address badActor = entries[_id].contributor; string memory dialect = entries[_id].dialect; // Burn -10 XP from the contributor if (userXP[badActor][dialect] >= 10) { userXP[badActor][dialect] -= 10; } else { userXP[badActor][dialect] = 0; } _updateRank(badActor, dialect); emit EntryBurned(_id, badActor, _reason); } function appealEntry(uint256 _id, address _appealer) public onlyRelayer { DialectEntry storage entry = entries[_id]; require(entry.isBurned, "Entry is not burned"); require(!hasAppealed[_id][_appealer], "User already appealed"); string memory role = userRole[_appealer][entry.dialect]; bool isGod = isAdmin[_appealer]; bool isOracle = keccak256(bytes(role)) == keccak256(bytes("Oracle")); require(isGod || isOracle, "Only Admins or Oracles can appeal"); hasAppealed[_id][_appealer] = true; appealCount[_id]++; // +10 XP for auditing the graveyard _mintReputation(_appealer, entry.dialect, 10); if (isGod || appealCount[_id] >= 2) { // Overturn the rejection! entry.isBurned = false; entry.isVerified = true; // Restore submitter's 50 XP + 10 XP penalty refund _mintReputation(entry.contributor, entry.dialect, 60); // Penalize the original rejectors -15 XP each address[] memory rejectors = rejectorsList[_id]; for(uint i=0; i= 15) { userXP[r][entry.dialect] -= 15; } else { userXP[r][entry.dialect] = 0; } _updateRank(r, entry.dialect); } emit EntryAppealed(_id, _appealer, true); } else { emit EntryAppealed(_id, _appealer, false); } } function createBackup(string memory _dataKey, string memory _dataType, string memory _ipfsCid, string memory _description) public onlyRelayer { DataSnapshot memory newSnap = DataSnapshot(block.timestamp, _dataType, _ipfsCid, _description); backups[_dataKey].push(newSnap); emit BackupCreated(_dataKey, _ipfsCid, block.timestamp); } // --- INTERNAL REPUTATION & RANKING LOGIC --- function _mintReputation(address _user, string memory _dialect, uint256 _amount) internal { userXP[_user][_dialect] += _amount; emit ReputationMinted(_user, _dialect, _amount); _updateRank(_user, _dialect); } function _updateRank(address _user, string memory _dialect) internal { if (isAdmin[_user]) return; uint256 score = userXP[_user][_dialect]; string memory newRole; if (score >= 1500) { newRole = "Oracle"; } else if (score >= 800) { newRole = "Archivist"; } else if (score >= 300) { newRole = "Linguist"; } else if (score >= 100) { newRole = "Scout"; } else { newRole = "Initiate"; } if (keccak256(abi.encodePacked(userRole[_user][_dialect])) != keccak256(abi.encodePacked(newRole))) { userRole[_user][_dialect] = newRole; emit RankUpgraded(_user, _dialect, newRole); } } // --- MIGRATION QUERIES --- function batchMigrateXP(address[] memory _users, string[] memory _dialects, uint256[] memory _xps) public onlyRelayer { require(_users.length == _dialects.length && _users.length == _xps.length, "Array lengths mismatch"); for(uint i=0; i<_users.length; i++){ userXP[_users[i]][_dialects[i]] += _xps[i]; _updateRank(_users[i], _dialects[i]); emit ReputationMinted(_users[i], _dialects[i], _xps[i]); } } // --- HELPER QUERIES --- function getRole(address _user, string memory _dialect) public view returns (string memory) { if (isAdmin[_user]) return "Admin"; if (bytes(userRole[_user][_dialect]).length == 0) return "Initiate"; return userRole[_user][_dialect]; } function getXP(address _user, string memory _dialect) public view returns (uint256) { return userXP[_user][_dialect]; } }