File size: 8,212 Bytes
963d10b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract PureBi_Jun13 { 
    
    // --- 0. BRANDING ---
    string public constant name = "PureBilingual Versation June 13, 2026 Research Registry";
    string public constant symbol = "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;

    // --- 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);
    event EntryBurned(uint256 indexed id, address indexed contributor);
    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) 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;
        _mintReputation(_rejector, entry.dialect, 10); // +10 XP for doing the audit

        if (isGod) {
            _applyBurn(_id);
        } else {
            entry.rejectionCount++;
            if (entry.rejectionCount >= 3) {
                _applyBurn(_id);
            }
        }
    }

    function _applyBurn(uint256 _id) internal {
        entries[_id].isBurned = true;
        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);
    }

    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];
    }
}