@evvm/testnet-contracts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +166 -0
  2. package/README.md +216 -0
  3. package/package.json +51 -0
  4. package/src/contracts/evvm/Evvm.sol +1327 -0
  5. package/src/contracts/evvm/EvvmLegacy.sol +1553 -0
  6. package/src/contracts/evvm/lib/ErrorsLib.sol +17 -0
  7. package/src/contracts/evvm/lib/EvvmStorage.sol +60 -0
  8. package/src/contracts/evvm/lib/EvvmStructs.sol +64 -0
  9. package/src/contracts/evvm/lib/SignatureUtils.sol +124 -0
  10. package/src/contracts/nameService/NameService.sol +1751 -0
  11. package/src/contracts/nameService/lib/ErrorsLib.sol +27 -0
  12. package/src/contracts/nameService/lib/SignatureUtils.sol +239 -0
  13. package/src/contracts/staking/Estimator.sol +358 -0
  14. package/src/contracts/staking/Staking.sol +1148 -0
  15. package/src/contracts/staking/lib/ErrorsLib.sol +19 -0
  16. package/src/contracts/staking/lib/SignatureUtils.sol +68 -0
  17. package/src/contracts/treasury/Treasury.sol +104 -0
  18. package/src/contracts/treasury/lib/ErrorsLib.sol +11 -0
  19. package/src/contracts/treasuryTwoChains/TreasuryExternalChainStation.sol +551 -0
  20. package/src/contracts/treasuryTwoChains/TreasuryHostChainStation.sol +512 -0
  21. package/src/contracts/treasuryTwoChains/lib/ErrorsLib.sol +15 -0
  22. package/src/contracts/treasuryTwoChains/lib/ExternalChainStationStructs.sol +41 -0
  23. package/src/contracts/treasuryTwoChains/lib/HostChainStationStructs.sol +52 -0
  24. package/src/contracts/treasuryTwoChains/lib/SignatureUtils.sol +47 -0
  25. package/src/interfaces/IEstimator.sol +102 -0
  26. package/src/interfaces/IEvvm.sol +195 -0
  27. package/src/interfaces/INameService.sol +283 -0
  28. package/src/interfaces/IStaking.sol +202 -0
  29. package/src/interfaces/ITreasury.sol +17 -0
  30. package/src/interfaces/ITreasuryExternalChainStation.sol +262 -0
  31. package/src/interfaces/ITreasuryHostChainStation.sol +251 -0
  32. package/src/lib/AdvancedStrings.sol +77 -0
  33. package/src/lib/Erc191TestBuilder.sol +402 -0
  34. package/src/lib/SignatureRecover.sol +56 -0
@@ -0,0 +1,1751 @@
1
+ // SPDX-License-Identifier: EVVM-NONCOMMERCIAL-1.0
2
+ // Full license terms available at: https://www.evvm.info/docs/EVVMNoncommercialLicense
3
+
4
+ pragma solidity ^0.8.0;
5
+ /**
6
+ _ _
7
+ | \ | |
8
+ | \| | __ _ _ __ ___ ___
9
+ | . ` |/ _` | '_ ` _ \ / _ \
10
+ | |\ | (_| | | | | | | __/
11
+ \_| \_/\__,_|_| |_| |_|\___|
12
+
13
+
14
+ _____ _
15
+ / ___| (_)
16
+ \ `--. ___ _ ____ ___ ___ ___
17
+ `--. \/ _ | '__\ \ / | |/ __/ _ \
18
+ /\__/ | __| | \ V /| | (_| __/
19
+ \____/ \___|_| \_/ |_|\___\___|
20
+
21
+
22
+ ████████╗███████╗███████╗████████╗███╗ ██╗███████╗████████╗
23
+ ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝████╗ ██║██╔════╝╚══██╔══╝
24
+ ██║ █████╗ ███████╗ ██║ ██╔██╗ ██║█████╗ ██║
25
+ ██║ ██╔══╝ ╚════██║ ██║ ██║╚██╗██║██╔══╝ ██║
26
+ ██║ ███████╗███████║ ██║ ██║ ╚████║███████╗ ██║
27
+ ╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝
28
+ *
29
+ * @title EVVM Name Service Contract
30
+ * @author jistro.eth ariutokintumi.eth
31
+ * @notice This contract manages username registration and domain name services for the EVVM ecosystem
32
+ * @dev Provides a comprehensive domain name system with features including:
33
+ *
34
+ * Core Features:
35
+ * - Username registration with pre-registration protection against front-running
36
+ * - Custom metadata management with schema-based data storage
37
+ * - Username trading system with offers and marketplace functionality
38
+ * - Renewal system with dynamic pricing based on market demand
39
+ * - Time-delayed governance for administrative functions
40
+ *
41
+ * Registration Process:
42
+ * 1. Pre-register: Commit to a username hash to prevent front-running
43
+ * 2. Register: Reveal the username and complete registration within 30 minutes
44
+ * 3. Manage: Add custom metadata, handle offers, and renew as needed
45
+ *
46
+ * Security Features:
47
+ * - Signature verification for all operations
48
+ * - Nonce-based replay protection
49
+ * - Time-locked administrative changes
50
+ * - Integration with EVVM core for secure payments
51
+ *
52
+ * Economic Model:
53
+ * - Registration costs 100x EVVM reward amount
54
+ * - Custom metadata operations cost 10x EVVM reward amount
55
+ * - Renewal pricing varies based on market demand and timing
56
+ * - Marketplace takes 0.5% fee on username sales
57
+ */
58
+
59
+ import {Evvm} from "@evvm/testnet-contracts/contracts/evvm/Evvm.sol";
60
+ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
61
+ import {AdvancedStrings} from "@evvm/testnet-contracts/lib/AdvancedStrings.sol";
62
+ import {ErrorsLib} from "@evvm/testnet-contracts/contracts/nameService/lib/ErrorsLib.sol";
63
+ import {SignatureUtils} from "@evvm/testnet-contracts/contracts/nameService/lib/SignatureUtils.sol";
64
+
65
+ contract NameService {
66
+ /**
67
+ * @dev Struct for managing address change proposals with time delay
68
+ * @param current Currently active address
69
+ * @param proposal Proposed new address waiting for approval
70
+ * @param timeToAccept Timestamp when the proposal can be accepted
71
+ */
72
+ struct AddressTypeProposal {
73
+ address current;
74
+ address proposal;
75
+ uint256 timeToAccept;
76
+ }
77
+
78
+ /**
79
+ * @dev Struct for managing uint256 value proposals with time delay
80
+ * @param current Currently active value
81
+ * @param proposal Proposed new value waiting for approval
82
+ * @param timeToAccept Timestamp when the proposal can be accepted
83
+ */
84
+ struct UintTypeProposal {
85
+ uint256 current;
86
+ uint256 proposal;
87
+ uint256 timeToAccept;
88
+ }
89
+
90
+ /**
91
+ * @dev Struct for managing boolean flag changes with time delay
92
+ * @param flag Current boolean state
93
+ * @param timeToAcceptChange Timestamp when the flag change can be executed
94
+ */
95
+ struct BoolTypeProposal {
96
+ bool flag;
97
+ uint256 timeToAcceptChange;
98
+ }
99
+
100
+ /**
101
+ * @dev Core metadata for each registered identity/username
102
+ * @param owner Address that owns this identity
103
+ * @param expireDate Timestamp when the registration expires
104
+ * @param customMetadataMaxSlots Number of custom metadata entries stored
105
+ * @param offerMaxSlots Maximum number of offers that have been made
106
+ * @param flagNotAUsername Flag indicating if this is a pre-registration (0x01) or actual username (0x00)
107
+ */
108
+ struct IdentityBaseMetadata {
109
+ address owner;
110
+ uint256 expireDate;
111
+ uint256 customMetadataMaxSlots;
112
+ uint256 offerMaxSlots;
113
+ bytes1 flagNotAUsername;
114
+ }
115
+
116
+ /// @dev Mapping from username to its core metadata and registration details
117
+ mapping(string username => IdentityBaseMetadata basicMetadata)
118
+ private identityDetails;
119
+
120
+ /**
121
+ * @dev Metadata for marketplace offers on usernames
122
+ * @param offerer Address making the offer
123
+ * @param expireDate Timestamp when the offer expires
124
+ * @param amount Amount offered in Principal Tokens (after 0.5% marketplace fee deduction)
125
+ */
126
+ struct OfferMetadata {
127
+ address offerer;
128
+ uint256 expireDate;
129
+ uint256 amount;
130
+ }
131
+
132
+ /// @dev Nested mapping: username => offer ID => offer details
133
+ mapping(string username => mapping(uint256 id => OfferMetadata))
134
+ private usernameOffers;
135
+
136
+ /// @dev Nested mapping: username => metadata key => custom value string
137
+ mapping(string username => mapping(uint256 numberKey => string customValue))
138
+ private identityCustomMetadata;
139
+
140
+ /// @dev Mapping to track used nonces per address to prevent replay attacks
141
+ mapping(address => mapping(uint256 => bool)) private nameServiceNonce;
142
+
143
+ /// @dev Proposal system for token withdrawal amounts with time delay
144
+ UintTypeProposal amountToWithdrawTokens;
145
+
146
+ /// @dev Proposal system for EVVM contract address changes with time delay
147
+ AddressTypeProposal evvmAddress;
148
+
149
+ /// @dev Proposal system for admin address changes with time delay
150
+ AddressTypeProposal admin;
151
+
152
+ /// @dev Constant address representing the Principal Token in the EVVM ecosystem
153
+ address private constant PRINCIPAL_TOKEN_ADDRESS =
154
+ 0x0000000000000000000000000000000000000001;
155
+
156
+ /// @dev Amount of Principal Tokens locked in pending marketplace offers
157
+ uint256 private principalTokenTokenLockedForWithdrawOffers;
158
+
159
+ /// @dev Restricts function access to the current admin address only
160
+ modifier onlyAdmin() {
161
+ if (msg.sender != admin.current) revert ErrorsLib.SenderIsNotAdmin();
162
+
163
+ _;
164
+ }
165
+
166
+ /// @dev Verifies that the caller owns the specified identity/username
167
+ modifier onlyOwnerOfIdentity(address _user, string memory _identity) {
168
+ if (identityDetails[_identity].owner != _user)
169
+ revert ErrorsLib.UserIsNotOwnerOfIdentity();
170
+
171
+ _;
172
+ }
173
+
174
+ /// @dev Ensures the nonce hasn't been used before to prevent replay attacks
175
+ modifier verifyIfNonceIsAvailable(address _user, uint256 _nonce) {
176
+ if (nameServiceNonce[_user][_nonce])
177
+ revert ErrorsLib.NonceAlreadyUsed();
178
+
179
+ _;
180
+ }
181
+
182
+ /**
183
+ * @notice Initializes the NameService contract
184
+ * @dev Sets up the EVVM integration and initial admin
185
+ * @param _evvmAddress Address of the EVVM core contract for payment processing
186
+ * @param _initialOwner Address that will have admin privileges
187
+ */
188
+ constructor(address _evvmAddress, address _initialOwner) {
189
+ evvmAddress.current = _evvmAddress;
190
+ admin.current = _initialOwner;
191
+ }
192
+
193
+ /**
194
+ * @notice Pre-registers a username hash to prevent front-running attacks
195
+ * @dev Creates a temporary reservation that can be registered 30 minutes later
196
+ * @param user Address of the user making the pre-registration
197
+ * @param hashPreRegisteredUsername Keccak256 hash of username + random number
198
+ * @param nonce Unique nonce to prevent replay attacks
199
+ * @param signature Signature proving authorization for this operation
200
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
201
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
202
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
203
+ * @param signature_EVVM Signature for the EVVM payment transaction
204
+ */
205
+ function preRegistrationUsername(
206
+ address user,
207
+ bytes32 hashPreRegisteredUsername,
208
+ uint256 nonce,
209
+ bytes memory signature,
210
+ uint256 priorityFee_EVVM,
211
+ uint256 nonce_EVVM,
212
+ bool priorityFlag_EVVM,
213
+ bytes memory signature_EVVM
214
+ ) public verifyIfNonceIsAvailable(user, nonce) {
215
+ if (
216
+ !SignatureUtils.verifyMessageSignedForPreRegistrationUsername(
217
+ Evvm(evvmAddress.current).getEvvmID(),
218
+ user,
219
+ hashPreRegisteredUsername,
220
+ nonce,
221
+ signature
222
+ )
223
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
224
+
225
+ if (priorityFee_EVVM > 0) {
226
+ makePay(
227
+ user,
228
+ 0,
229
+ priorityFee_EVVM,
230
+ nonce_EVVM,
231
+ priorityFlag_EVVM,
232
+ signature_EVVM
233
+ );
234
+ }
235
+
236
+ string memory key = string.concat(
237
+ "@",
238
+ AdvancedStrings.bytes32ToString(hashPreRegisteredUsername)
239
+ );
240
+
241
+ identityDetails[key] = IdentityBaseMetadata({
242
+ owner: user,
243
+ expireDate: block.timestamp + 30 minutes,
244
+ customMetadataMaxSlots: 0,
245
+ offerMaxSlots: 0,
246
+ flagNotAUsername: 0x01
247
+ });
248
+
249
+ nameServiceNonce[user][nonce] = true;
250
+
251
+ if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
252
+ makeCaPay(
253
+ msg.sender,
254
+ Evvm(evvmAddress.current).getRewardAmount() + priorityFee_EVVM
255
+ );
256
+ }
257
+ }
258
+
259
+ /**
260
+ * @notice Completes username registration using a pre-registration commitment
261
+ * @dev Must be called after the pre-registration period (30 minutes) has reached
262
+ * @param user Address of the user completing the registration
263
+ * @param username The actual username being registered (revealed from hash)
264
+ * @param clowNumber Random number used in the pre-registration hash
265
+ * @param nonce Unique nonce to prevent replay attacks
266
+ * @param signature Signature proving authorization for this operation
267
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
268
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
269
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
270
+ * @param signature_EVVM Signature for the EVVM payment transaction
271
+ */
272
+ function registrationUsername(
273
+ address user,
274
+ string memory username,
275
+ uint256 clowNumber,
276
+ uint256 nonce,
277
+ bytes memory signature,
278
+ uint256 priorityFee_EVVM,
279
+ uint256 nonce_EVVM,
280
+ bool priorityFlag_EVVM,
281
+ bytes memory signature_EVVM
282
+ ) public verifyIfNonceIsAvailable(user, nonce) {
283
+ if (admin.current != user) isValidUsername(username);
284
+
285
+ if (!isUsernameAvailable(username)) {
286
+ revert ErrorsLib.UsernameAlreadyRegistered();
287
+ }
288
+
289
+ if (
290
+ !SignatureUtils.verifyMessageSignedForRegistrationUsername(
291
+ Evvm(evvmAddress.current).getEvvmID(),
292
+ user,
293
+ username,
294
+ clowNumber,
295
+ nonce,
296
+ signature
297
+ )
298
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
299
+
300
+ makePay(
301
+ user,
302
+ getPricePerRegistration(),
303
+ priorityFee_EVVM,
304
+ nonce_EVVM,
305
+ priorityFlag_EVVM,
306
+ signature_EVVM
307
+ );
308
+
309
+ string memory _key = string.concat(
310
+ "@",
311
+ AdvancedStrings.bytes32ToString(hashUsername(username, clowNumber))
312
+ );
313
+
314
+ if (
315
+ identityDetails[_key].owner != user ||
316
+ identityDetails[_key].expireDate > block.timestamp
317
+ ) revert ErrorsLib.PreRegistrationNotValid();
318
+
319
+ identityDetails[username] = IdentityBaseMetadata({
320
+ owner: user,
321
+ expireDate: block.timestamp + 366 days,
322
+ customMetadataMaxSlots: 0,
323
+ offerMaxSlots: 0,
324
+ flagNotAUsername: 0x00
325
+ });
326
+
327
+ nameServiceNonce[user][nonce] = true;
328
+
329
+ if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
330
+ makeCaPay(
331
+ msg.sender,
332
+ (50 * Evvm(evvmAddress.current).getRewardAmount()) +
333
+ priorityFee_EVVM
334
+ );
335
+ }
336
+
337
+ delete identityDetails[_key];
338
+ }
339
+
340
+ /**
341
+ * @notice Creates a marketplace offer to purchase a username
342
+ * @dev Locks the offer amount in the contract until withdrawn or accepted
343
+ * @param user Address making the offer
344
+ * @param username Target username for the offer
345
+ * @param expireDate Timestamp when the offer expires
346
+ * @param amount Amount being offered in Principal Tokens
347
+ * @param nonce Unique nonce to prevent replay attacks
348
+ * @param signature Signature proving authorization for this operation
349
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
350
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
351
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
352
+ * @param signature_EVVM Signature for the EVVM payment transaction
353
+ * @return offerID Unique identifier for the created offer
354
+ */
355
+ function makeOffer(
356
+ address user,
357
+ string memory username,
358
+ uint256 expireDate,
359
+ uint256 amount,
360
+ uint256 nonce,
361
+ bytes memory signature,
362
+ uint256 priorityFee_EVVM,
363
+ uint256 nonce_EVVM,
364
+ bool priorityFlag_EVVM,
365
+ bytes memory signature_EVVM
366
+ ) public verifyIfNonceIsAvailable(user, nonce) returns (uint256 offerID) {
367
+ if (
368
+ identityDetails[username].flagNotAUsername == 0x01 ||
369
+ !verifyIfIdentityExists(username) ||
370
+ amount == 0 ||
371
+ expireDate <= block.timestamp
372
+ ) revert ErrorsLib.PreRegistrationNotValid();
373
+
374
+ if (
375
+ !SignatureUtils.verifyMessageSignedForMakeOffer(
376
+ Evvm(evvmAddress.current).getEvvmID(),
377
+ user,
378
+ username,
379
+ expireDate,
380
+ amount,
381
+ nonce,
382
+ signature
383
+ )
384
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
385
+
386
+ makePay(
387
+ user,
388
+ amount,
389
+ priorityFee_EVVM,
390
+ nonce_EVVM,
391
+ priorityFlag_EVVM,
392
+ signature_EVVM
393
+ );
394
+
395
+ while (usernameOffers[username][offerID].offerer != address(0)) {
396
+ offerID++;
397
+ }
398
+
399
+ usernameOffers[username][offerID] = OfferMetadata({
400
+ offerer: user,
401
+ expireDate: expireDate,
402
+ amount: ((amount * 995) / 1000)
403
+ });
404
+
405
+ makeCaPay(
406
+ msg.sender,
407
+ Evvm(evvmAddress.current).getRewardAmount() +
408
+ ((amount * 125) / 100_000) +
409
+ priorityFee_EVVM
410
+ );
411
+ principalTokenTokenLockedForWithdrawOffers +=
412
+ ((amount * 995) / 1000) +
413
+ (amount / 800);
414
+
415
+ if (offerID > identityDetails[username].offerMaxSlots) {
416
+ identityDetails[username].offerMaxSlots++;
417
+ } else if (identityDetails[username].offerMaxSlots == 0) {
418
+ identityDetails[username].offerMaxSlots++;
419
+ }
420
+
421
+ nameServiceNonce[user][nonce] = true;
422
+ }
423
+
424
+ /**
425
+ * @notice Withdraws a marketplace offer and refunds the locked tokens
426
+ * @dev Can only be called by the offer creator before expiration
427
+ * @param user Address that made the original offer
428
+ * @param username Username the offer was made for
429
+ * @param offerID Unique identifier of the offer to withdraw
430
+ * @param nonce Unique nonce to prevent replay attacks
431
+ * @param signature Signature proving authorization for this operation
432
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
433
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
434
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
435
+ * @param signature_EVVM Signature for the EVVM payment transaction
436
+ */
437
+ function withdrawOffer(
438
+ address user,
439
+ string memory username,
440
+ uint256 offerID,
441
+ uint256 nonce,
442
+ bytes memory signature,
443
+ uint256 priorityFee_EVVM,
444
+ uint256 nonce_EVVM,
445
+ bool priorityFlag_EVVM,
446
+ bytes memory signature_EVVM
447
+ ) public verifyIfNonceIsAvailable(user, nonce) {
448
+ if (usernameOffers[username][offerID].offerer != user)
449
+ revert ErrorsLib.UserIsNotOwnerOfOffer();
450
+
451
+ if (
452
+ !SignatureUtils.verifyMessageSignedForWithdrawOffer(
453
+ Evvm(evvmAddress.current).getEvvmID(),
454
+ user,
455
+ username,
456
+ offerID,
457
+ nonce,
458
+ signature
459
+ )
460
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
461
+
462
+ if (priorityFee_EVVM > 0) {
463
+ makePay(
464
+ user,
465
+ 0,
466
+ priorityFee_EVVM,
467
+ nonce_EVVM,
468
+ priorityFlag_EVVM,
469
+ signature_EVVM
470
+ );
471
+ }
472
+
473
+ makeCaPay(user, usernameOffers[username][offerID].amount);
474
+
475
+ usernameOffers[username][offerID].offerer = address(0);
476
+
477
+ makeCaPay(
478
+ msg.sender,
479
+ Evvm(evvmAddress.current).getRewardAmount() +
480
+ ((usernameOffers[username][offerID].amount * 1) / 796) +
481
+ priorityFee_EVVM
482
+ );
483
+
484
+ principalTokenTokenLockedForWithdrawOffers -=
485
+ (usernameOffers[username][offerID].amount) +
486
+ (((usernameOffers[username][offerID].amount * 1) / 199) / 4);
487
+
488
+ nameServiceNonce[user][nonce] = true;
489
+ }
490
+
491
+ /**
492
+ * @notice Accepts a marketplace offer and transfers username ownership
493
+ * @dev Can only be called by the current username owner before offer expiration
494
+ * @param user Address of the current username owner
495
+ * @param username Username being sold
496
+ * @param offerID Unique identifier of the offer to accept
497
+ * @param nonce Unique nonce to prevent replay attacks
498
+ * @param signature Signature proving authorization for this operation
499
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
500
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
501
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
502
+ * @param signature_EVVM Signature for the EVVM payment transaction
503
+ */
504
+ function acceptOffer(
505
+ address user,
506
+ string memory username,
507
+ uint256 offerID,
508
+ uint256 nonce,
509
+ bytes memory signature,
510
+ uint256 priorityFee_EVVM,
511
+ uint256 nonce_EVVM,
512
+ bool priorityFlag_EVVM,
513
+ bytes memory signature_EVVM
514
+ )
515
+ public
516
+ onlyOwnerOfIdentity(user, username)
517
+ verifyIfNonceIsAvailable(user, nonce)
518
+ {
519
+ if (
520
+ usernameOffers[username][offerID].offerer == address(0) ||
521
+ usernameOffers[username][offerID].expireDate < block.timestamp
522
+ ) revert ErrorsLib.AcceptOfferVerificationFailed();
523
+
524
+ if (
525
+ !SignatureUtils.verifyMessageSignedForAcceptOffer(
526
+ Evvm(evvmAddress.current).getEvvmID(),
527
+ user,
528
+ username,
529
+ offerID,
530
+ nonce,
531
+ signature
532
+ )
533
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
534
+
535
+ if (priorityFee_EVVM > 0) {
536
+ makePay(
537
+ user,
538
+ 0,
539
+ priorityFee_EVVM,
540
+ nonce_EVVM,
541
+ priorityFlag_EVVM,
542
+ signature_EVVM
543
+ );
544
+ }
545
+
546
+ makeCaPay(user, usernameOffers[username][offerID].amount);
547
+
548
+ identityDetails[username].owner = usernameOffers[username][offerID]
549
+ .offerer;
550
+
551
+ usernameOffers[username][offerID].offerer = address(0);
552
+
553
+ if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
554
+ makeCaPay(
555
+ msg.sender,
556
+ (Evvm(evvmAddress.current).getRewardAmount()) +
557
+ (((usernameOffers[username][offerID].amount * 1) / 199) /
558
+ 4) +
559
+ priorityFee_EVVM
560
+ );
561
+ }
562
+
563
+ principalTokenTokenLockedForWithdrawOffers -=
564
+ (usernameOffers[username][offerID].amount) +
565
+ (((usernameOffers[username][offerID].amount * 1) / 199) / 4);
566
+
567
+ nameServiceNonce[user][nonce] = true;
568
+ }
569
+
570
+ /**
571
+ * @notice Renews a username registration for another year
572
+ * @dev Pricing varies based on timing and market demand for the username
573
+ *
574
+ * Pricing Rules:
575
+ * - Free renewal if done within 1 year of expiration (limited time offer)
576
+ * - Variable cost based on highest active offer (minimum 500 Principal Token)
577
+ * - Fixed 500,000 Principal Token if renewed more than 1 year before expiration
578
+ * - Can be renewed up to 100 years in advance
579
+ *
580
+ * @param user Address of the username owner
581
+ * @param username Username to renew
582
+ * @param nonce Unique nonce to prevent replay attacks
583
+ * @param signature Signature proving authorization for this operation
584
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
585
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
586
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
587
+ * @param signature_EVVM Signature for the EVVM payment transaction
588
+ */
589
+ function renewUsername(
590
+ address user,
591
+ string memory username,
592
+ uint256 nonce,
593
+ bytes memory signature,
594
+ uint256 priorityFee_EVVM,
595
+ uint256 nonce_EVVM,
596
+ bool priorityFlag_EVVM,
597
+ bytes memory signature_EVVM
598
+ )
599
+ public
600
+ onlyOwnerOfIdentity(user, username)
601
+ verifyIfNonceIsAvailable(user, nonce)
602
+ {
603
+ if (
604
+ identityDetails[username].flagNotAUsername == 0x01 ||
605
+ identityDetails[username].expireDate > block.timestamp + 36500 days
606
+ ) revert ErrorsLib.RenewUsernameVerificationFailed();
607
+
608
+ if (
609
+ !SignatureUtils.verifyMessageSignedForRenewUsername(
610
+ Evvm(evvmAddress.current).getEvvmID(),
611
+ user,
612
+ username,
613
+ nonce,
614
+ signature
615
+ )
616
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
617
+
618
+ uint256 priceOfRenew = seePriceToRenew(username);
619
+
620
+ makePay(
621
+ user,
622
+ priceOfRenew,
623
+ priorityFee_EVVM,
624
+ nonce_EVVM,
625
+ priorityFlag_EVVM,
626
+ signature_EVVM
627
+ );
628
+
629
+ if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
630
+ makeCaPay(
631
+ msg.sender,
632
+ Evvm(evvmAddress.current).getRewardAmount() +
633
+ ((priceOfRenew * 50) / 100) +
634
+ priorityFee_EVVM
635
+ );
636
+ }
637
+
638
+ identityDetails[username].expireDate += 366 days;
639
+ nameServiceNonce[user][nonce] = true;
640
+ }
641
+
642
+ /**
643
+ * @notice Adds custom metadata to a username following a standardized schema format
644
+ * @dev Metadata follows format: [schema]:[subschema]>[value]
645
+ *
646
+ * Standard Format Examples:
647
+ * - memberOf:>EVVM
648
+ * - socialMedia:x>jistro (Twitter/X handle)
649
+ * - email:dev>jistro[at]evvm.org (development email)
650
+ * - email:callme>contact[at]jistro.xyz (contact email)
651
+ *
652
+ * Schema Guidelines:
653
+ * - Based on https://schema.org/docs/schemas.html
654
+ * - ':' separates schema from subschema
655
+ * - '>' separates metadata from value
656
+ * - Pad with spaces if schema/subschema < 5 characters
657
+ * - Use "socialMedia" for social networks with network name as subschema
658
+ *
659
+ * @param user Address of the username owner
660
+ * @param identity Username to add metadata to
661
+ * @param value Metadata string following the standardized format
662
+ * @param nonce Unique nonce to prevent replay attacks
663
+ * @param signature Signature proving authorization for this operation
664
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
665
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
666
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
667
+ * @param signature_EVVM Signature for the EVVM payment transaction
668
+ */
669
+ function addCustomMetadata(
670
+ address user,
671
+ string memory identity,
672
+ string memory value,
673
+ uint256 nonce,
674
+ bytes memory signature,
675
+ uint256 priorityFee_EVVM,
676
+ uint256 nonce_EVVM,
677
+ bool priorityFlag_EVVM,
678
+ bytes memory signature_EVVM
679
+ )
680
+ public
681
+ onlyOwnerOfIdentity(user, identity)
682
+ verifyIfNonceIsAvailable(user, nonce)
683
+ {
684
+ if (bytes(value).length == 0) revert ErrorsLib.EmptyCustomMetadata();
685
+
686
+ if (
687
+ !SignatureUtils.verifyMessageSignedForAddCustomMetadata(
688
+ Evvm(evvmAddress.current).getEvvmID(),
689
+ user,
690
+ identity,
691
+ value,
692
+ nonce,
693
+ signature
694
+ )
695
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
696
+
697
+ makePay(
698
+ user,
699
+ getPriceToAddCustomMetadata(),
700
+ priorityFee_EVVM,
701
+ nonce_EVVM,
702
+ priorityFlag_EVVM,
703
+ signature_EVVM
704
+ );
705
+
706
+ if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
707
+ makeCaPay(
708
+ msg.sender,
709
+ (5 * Evvm(evvmAddress.current).getRewardAmount()) +
710
+ ((getPriceToAddCustomMetadata() * 50) / 100) +
711
+ priorityFee_EVVM
712
+ );
713
+ }
714
+
715
+ identityCustomMetadata[identity][
716
+ identityDetails[identity].customMetadataMaxSlots
717
+ ] = value;
718
+
719
+ identityDetails[identity].customMetadataMaxSlots++;
720
+ nameServiceNonce[user][nonce] = true;
721
+ }
722
+
723
+ /**
724
+ * @notice Removes a specific custom metadata entry by key and reorders the array
725
+ * @dev Shifts all subsequent metadata entries to fill the gap after removal
726
+ * @param user Address of the username owner
727
+ * @param identity Username to remove metadata from
728
+ * @param key Index of the metadata entry to remove
729
+ * @param nonce Unique nonce to prevent replay attacks
730
+ * @param signature Signature proving authorization for this operation
731
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
732
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
733
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
734
+ * @param signature_EVVM Signature for the EVVM payment transaction
735
+ */
736
+ function removeCustomMetadata(
737
+ address user,
738
+ string memory identity,
739
+ uint256 key,
740
+ uint256 nonce,
741
+ bytes memory signature,
742
+ uint256 priorityFee_EVVM,
743
+ uint256 nonce_EVVM,
744
+ bool priorityFlag_EVVM,
745
+ bytes memory signature_EVVM
746
+ )
747
+ public
748
+ onlyOwnerOfIdentity(user, identity)
749
+ verifyIfNonceIsAvailable(user, nonce)
750
+ {
751
+ if (
752
+ !SignatureUtils.verifyMessageSignedForRemoveCustomMetadata(
753
+ Evvm(evvmAddress.current).getEvvmID(),
754
+ user,
755
+ identity,
756
+ key,
757
+ nonce,
758
+ signature
759
+ )
760
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
761
+
762
+ if (identityDetails[identity].customMetadataMaxSlots <= key)
763
+ revert ErrorsLib.InvalidKey();
764
+
765
+ makePay(
766
+ user,
767
+ getPriceToRemoveCustomMetadata(),
768
+ priorityFee_EVVM,
769
+ nonce_EVVM,
770
+ priorityFlag_EVVM,
771
+ signature_EVVM
772
+ );
773
+
774
+ if (identityDetails[identity].customMetadataMaxSlots == key) {
775
+ delete identityCustomMetadata[identity][key];
776
+ } else {
777
+ for (
778
+ uint256 i = key;
779
+ i < identityDetails[identity].customMetadataMaxSlots;
780
+ i++
781
+ ) {
782
+ identityCustomMetadata[identity][i] = identityCustomMetadata[
783
+ identity
784
+ ][i + 1];
785
+ }
786
+ delete identityCustomMetadata[identity][
787
+ identityDetails[identity].customMetadataMaxSlots
788
+ ];
789
+ }
790
+ identityDetails[identity].customMetadataMaxSlots--;
791
+ nameServiceNonce[user][nonce] = true;
792
+ if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
793
+ makeCaPay(
794
+ msg.sender,
795
+ (5 * Evvm(evvmAddress.current).getRewardAmount()) +
796
+ priorityFee_EVVM
797
+ );
798
+ }
799
+ }
800
+
801
+ /**
802
+ * @notice Removes all custom metadata entries for a username
803
+ * @dev More gas-efficient than removing entries individually
804
+ * @param user Address of the username owner
805
+ * @param identity Username to flush all metadata from
806
+ * @param nonce Unique nonce to prevent replay attacks
807
+ * @param signature Signature proving authorization for this operation
808
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
809
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
810
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
811
+ * @param signature_EVVM Signature for the EVVM payment transaction
812
+ */
813
+ function flushCustomMetadata(
814
+ address user,
815
+ string memory identity,
816
+ uint256 nonce,
817
+ bytes memory signature,
818
+ uint256 priorityFee_EVVM,
819
+ uint256 nonce_EVVM,
820
+ bool priorityFlag_EVVM,
821
+ bytes memory signature_EVVM
822
+ )
823
+ public
824
+ onlyOwnerOfIdentity(user, identity)
825
+ verifyIfNonceIsAvailable(user, nonce)
826
+ {
827
+ if (
828
+ !SignatureUtils.verifyMessageSignedForFlushCustomMetadata(
829
+ Evvm(evvmAddress.current).getEvvmID(),
830
+ user,
831
+ identity,
832
+ nonce,
833
+ signature
834
+ )
835
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
836
+
837
+ if (identityDetails[identity].customMetadataMaxSlots == 0)
838
+ revert ErrorsLib.EmptyCustomMetadata();
839
+
840
+ makePay(
841
+ user,
842
+ getPriceToFlushCustomMetadata(identity),
843
+ priorityFee_EVVM,
844
+ nonce_EVVM,
845
+ priorityFlag_EVVM,
846
+ signature_EVVM
847
+ );
848
+
849
+ for (
850
+ uint256 i = 0;
851
+ i < identityDetails[identity].customMetadataMaxSlots;
852
+ i++
853
+ ) {
854
+ delete identityCustomMetadata[identity][i];
855
+ }
856
+
857
+ if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
858
+ makeCaPay(
859
+ msg.sender,
860
+ ((5 * Evvm(evvmAddress.current).getRewardAmount()) *
861
+ identityDetails[identity].customMetadataMaxSlots) +
862
+ priorityFee_EVVM
863
+ );
864
+ }
865
+
866
+ identityDetails[identity].customMetadataMaxSlots = 0;
867
+ nameServiceNonce[user][nonce] = true;
868
+ }
869
+
870
+ /**
871
+ * @notice Completely removes a username registration and all associated data
872
+ * @dev Deletes the username, all custom metadata, and makes it available for re-registration
873
+ * @param user Address of the username owner
874
+ * @param username Username to completely remove from the system
875
+ * @param nonce Unique nonce to prevent replay attacks
876
+ * @param signature Signature proving authorization for this operation
877
+ * @param priorityFee_EVVM Priority fee for faster transaction processing
878
+ * @param nonce_EVVM Nonce for the EVVM payment transaction
879
+ * @param priorityFlag_EVVM True for async payment, false for sync payment
880
+ * @param signature_EVVM Signature for the EVVM payment transaction
881
+ */
882
+ function flushUsername(
883
+ address user,
884
+ string memory username,
885
+ uint256 nonce,
886
+ bytes memory signature,
887
+ uint256 priorityFee_EVVM,
888
+ uint256 nonce_EVVM,
889
+ bool priorityFlag_EVVM,
890
+ bytes memory signature_EVVM
891
+ )
892
+ public
893
+ verifyIfNonceIsAvailable(user, nonce)
894
+ onlyOwnerOfIdentity(user, username)
895
+ {
896
+ if (
897
+ block.timestamp >= identityDetails[username].expireDate ||
898
+ identityDetails[username].flagNotAUsername == 0x01
899
+ ) revert ErrorsLib.FlushUsernameVerificationFailed();
900
+
901
+ if (
902
+ !SignatureUtils.verifyMessageSignedForFlushUsername(
903
+ Evvm(evvmAddress.current).getEvvmID(),
904
+ user,
905
+ username,
906
+ nonce,
907
+ signature
908
+ )
909
+ ) revert ErrorsLib.InvalidSignatureOnNameService();
910
+
911
+ makePay(
912
+ user,
913
+ getPriceToFlushUsername(username),
914
+ priorityFee_EVVM,
915
+ nonce_EVVM,
916
+ priorityFlag_EVVM,
917
+ signature_EVVM
918
+ );
919
+
920
+ for (
921
+ uint256 i = 0;
922
+ i < identityDetails[username].customMetadataMaxSlots;
923
+ i++
924
+ ) {
925
+ delete identityCustomMetadata[username][i];
926
+ }
927
+
928
+ makeCaPay(
929
+ msg.sender,
930
+ ((5 * Evvm(evvmAddress.current).getRewardAmount()) *
931
+ identityDetails[username].customMetadataMaxSlots) +
932
+ priorityFee_EVVM
933
+ );
934
+
935
+ identityDetails[username] = IdentityBaseMetadata({
936
+ owner: address(0),
937
+ expireDate: 0,
938
+ customMetadataMaxSlots: 0,
939
+ offerMaxSlots: identityDetails[username].offerMaxSlots,
940
+ flagNotAUsername: 0x00
941
+ });
942
+ nameServiceNonce[user][nonce] = true;
943
+ }
944
+
945
+ //█ Administrative Functions with Time-Delayed Governance ████████████████████████████████████
946
+
947
+ /**
948
+ * @notice Proposes a new admin address with 1-day time delay
949
+ * @dev Part of the time-delayed governance system for admin changes
950
+ * @param _adminToPropose Address of the proposed new admin
951
+ */
952
+ function proposeAdmin(address _adminToPropose) public onlyAdmin {
953
+ if (_adminToPropose == address(0) || _adminToPropose == admin.current) {
954
+ revert();
955
+ }
956
+
957
+ admin.proposal = _adminToPropose;
958
+ admin.timeToAccept = block.timestamp + 1 days;
959
+ }
960
+
961
+ /**
962
+ * @notice Cancels the current admin proposal
963
+ * @dev Only the current admin can cancel pending proposals
964
+ */
965
+ function cancelProposeAdmin() public onlyAdmin {
966
+ admin.proposal = address(0);
967
+ admin.timeToAccept = 0;
968
+ }
969
+
970
+ /**
971
+ * @notice Accepts the admin proposal and becomes the new admin
972
+ * @dev Can only be called by the proposed admin after the time delay has passed
973
+ */
974
+ function acceptProposeAdmin() public {
975
+ if (admin.proposal != msg.sender) {
976
+ revert();
977
+ }
978
+ if (block.timestamp < admin.timeToAccept) {
979
+ revert();
980
+ }
981
+
982
+ admin = AddressTypeProposal({
983
+ current: admin.proposal,
984
+ proposal: address(0),
985
+ timeToAccept: 0
986
+ });
987
+ }
988
+
989
+ /**
990
+ * @notice Proposes to withdraw Principal Tokens from the contract
991
+ * @dev Amount must be available after reserving funds for operations and locked offers
992
+ * @param _amount Amount of Principal Tokens to withdraw
993
+ */
994
+ function proposeWithdrawPrincipalTokens(uint256 _amount) public onlyAdmin {
995
+ if (
996
+ Evvm(evvmAddress.current).getBalance(
997
+ address(this),
998
+ PRINCIPAL_TOKEN_ADDRESS
999
+ ) -
1000
+ (5083 +
1001
+ Evvm(evvmAddress.current).getRewardAmount() +
1002
+ principalTokenTokenLockedForWithdrawOffers) <
1003
+ _amount ||
1004
+ _amount == 0
1005
+ ) {
1006
+ revert();
1007
+ }
1008
+
1009
+ amountToWithdrawTokens.proposal = _amount;
1010
+ amountToWithdrawTokens.timeToAccept = block.timestamp + 1 days;
1011
+ }
1012
+
1013
+ /**
1014
+ * @notice Cancels the pending token withdrawal proposal
1015
+ * @dev Only the current admin can cancel pending proposals
1016
+ */
1017
+ function cancelWithdrawPrincipalTokens() public onlyAdmin {
1018
+ amountToWithdrawTokens.proposal = 0;
1019
+ amountToWithdrawTokens.timeToAccept = 0;
1020
+ }
1021
+
1022
+ /**
1023
+ * @notice Executes the approved token withdrawal
1024
+ * @dev Can only be called after the time delay has passed
1025
+ */
1026
+ function claimWithdrawPrincipalTokens() public onlyAdmin {
1027
+ if (block.timestamp < amountToWithdrawTokens.timeToAccept) {
1028
+ revert();
1029
+ }
1030
+
1031
+ makeCaPay(admin.current, amountToWithdrawTokens.proposal);
1032
+
1033
+ amountToWithdrawTokens.proposal = 0;
1034
+ amountToWithdrawTokens.timeToAccept = 0;
1035
+ }
1036
+
1037
+ /**
1038
+ * @notice Proposes to change the EVVM contract address
1039
+ * @dev Critical function that affects payment processing integration
1040
+ * @param _newEvvmAddress Address of the new EVVM contract
1041
+ */
1042
+ function proposeChangeEvvmAddress(
1043
+ address _newEvvmAddress
1044
+ ) public onlyAdmin {
1045
+ if (_newEvvmAddress == address(0)) {
1046
+ revert();
1047
+ }
1048
+ evvmAddress.proposal = _newEvvmAddress;
1049
+ evvmAddress.timeToAccept = block.timestamp + 1 days;
1050
+ }
1051
+
1052
+ /**
1053
+ * @notice Cancels the pending EVVM address change proposal
1054
+ * @dev Only the current admin can cancel pending proposals
1055
+ */
1056
+ function cancelChangeEvvmAddress() public onlyAdmin {
1057
+ evvmAddress.proposal = address(0);
1058
+ evvmAddress.timeToAccept = 0;
1059
+ }
1060
+
1061
+ /**
1062
+ * @notice Executes the approved EVVM address change
1063
+ * @dev Can only be called after the time delay has passed
1064
+ */
1065
+ function acceptChangeEvvmAddress() public onlyAdmin {
1066
+ if (block.timestamp < evvmAddress.timeToAccept) {
1067
+ revert();
1068
+ }
1069
+ evvmAddress = AddressTypeProposal({
1070
+ current: evvmAddress.proposal,
1071
+ proposal: address(0),
1072
+ timeToAccept: 0
1073
+ });
1074
+ }
1075
+
1076
+ //█ Utility Functions ████████████████████████████████████████████████████████████████████████
1077
+
1078
+ //█ EVVM Payment Integration ██████████████████████████████████████████████
1079
+
1080
+ /**
1081
+ * @notice Internal function to handle payments through the EVVM contract
1082
+ * @dev Supports both synchronous and asynchronous payment modes
1083
+ * @param user Address making the payment
1084
+ * @param amount Amount to pay in Principal Tokens
1085
+ * @param priorityFee Additional priority fee for faster processing
1086
+ * @param nonce Nonce for the EVVM transaction
1087
+ * @param priorityFlag True for async payment, false for sync payment
1088
+ * @param signature Signature authorizing the payment
1089
+ */
1090
+ function makePay(
1091
+ address user,
1092
+ uint256 amount,
1093
+ uint256 priorityFee,
1094
+ uint256 nonce,
1095
+ bool priorityFlag,
1096
+ bytes memory signature
1097
+ ) internal {
1098
+ Evvm(evvmAddress.current).pay(
1099
+ user,
1100
+ address(this),
1101
+ "",
1102
+ PRINCIPAL_TOKEN_ADDRESS,
1103
+ amount,
1104
+ priorityFee,
1105
+ nonce,
1106
+ priorityFlag,
1107
+ address(this),
1108
+ signature
1109
+ );
1110
+ }
1111
+
1112
+ /**
1113
+ * @notice Internal function to distribute Principal Tokens to users
1114
+ * @dev Calls the EVVM contract's caPay function for token distribution
1115
+ * @param user Address to receive the tokens
1116
+ * @param amount Amount of Principal Tokens to distribute
1117
+ */
1118
+ function makeCaPay(address user, uint256 amount) internal {
1119
+ Evvm(evvmAddress.current).caPay(user, PRINCIPAL_TOKEN_ADDRESS, amount);
1120
+ }
1121
+
1122
+ //█ Identity Validation Functions ███████████████████████████████████████████████████████████████
1123
+
1124
+ /**
1125
+ * @notice Validates username format according to system rules
1126
+ * @dev Username must be at least 4 characters, start with a letter, and contain only letters/digits
1127
+ * @param username The username string to validate
1128
+ */
1129
+ function isValidUsername(string memory username) internal pure {
1130
+ bytes memory usernameBytes = bytes(username);
1131
+
1132
+ // Check if username length is at least 4 characters
1133
+ if (usernameBytes.length < 4) revert ErrorsLib.InvalidUsername(0x01);
1134
+
1135
+ // Check if username begins with a letter
1136
+ if (!_isLetter(usernameBytes[0]))
1137
+ revert ErrorsLib.InvalidUsername(0x02);
1138
+
1139
+ // Iterate through each character in the username
1140
+ for (uint256 i = 0; i < usernameBytes.length; i++) {
1141
+ // Check if character is not a digit or letter
1142
+ if (!_isDigit(usernameBytes[i]) && !_isLetter(usernameBytes[i])) {
1143
+ revert ErrorsLib.InvalidUsername(0x03);
1144
+ }
1145
+ }
1146
+ }
1147
+
1148
+ /**
1149
+ * @notice Validates phone number format
1150
+ * @dev Phone number must be 6-19 digits only
1151
+ * @param _phoneNumber The phone number string to validate
1152
+ * @return True if valid phone number format
1153
+ */
1154
+ function isValidPhoneNumberNumber(
1155
+ string memory _phoneNumber
1156
+ ) internal pure returns (bool) {
1157
+ bytes memory _telephoneNumberBytes = bytes(_phoneNumber);
1158
+ if (
1159
+ _telephoneNumberBytes.length < 20 &&
1160
+ _telephoneNumberBytes.length > 5
1161
+ ) {
1162
+ revert();
1163
+ }
1164
+ for (uint256 i = 0; i < _telephoneNumberBytes.length; i++) {
1165
+ if (!_isDigit(_telephoneNumberBytes[i])) {
1166
+ revert();
1167
+ }
1168
+ }
1169
+ return true;
1170
+ }
1171
+
1172
+ /**
1173
+ * @notice Validates email address format
1174
+ * @dev Checks for proper email structure: prefix(3+ chars) + @ + domain(3+ chars) + . + TLD(2+ chars)
1175
+ * @param _email The email address string to validate
1176
+ * @return True if valid email format
1177
+ */
1178
+ function isValidEmail(string memory _email) internal pure returns (bool) {
1179
+ bytes memory _emailBytes = bytes(_email);
1180
+ uint256 lengthCount = 0;
1181
+ bytes1 flagVerify = 0x00;
1182
+ for (uint point = 0; point < _emailBytes.length; point++) {
1183
+ //step 1 0x00 prefix
1184
+ if (flagVerify == 0x00) {
1185
+ if (_isOnlyEmailPrefixCharacters(_emailBytes[point])) {
1186
+ lengthCount++;
1187
+ } else {
1188
+ if (_isAAt(_emailBytes[point])) {
1189
+ flagVerify = 0x01;
1190
+ } else {
1191
+ revert();
1192
+ }
1193
+ }
1194
+ }
1195
+
1196
+ //step 2 0x01 count the prefix length
1197
+ if (flagVerify == 0x01) {
1198
+ if (lengthCount < 3) {
1199
+ revert();
1200
+ } else {
1201
+ flagVerify = 0x02;
1202
+ lengthCount = 0;
1203
+ point++;
1204
+ }
1205
+ }
1206
+
1207
+ //step 3 0x02 domain name
1208
+ if (flagVerify == 0x02) {
1209
+ if (_isLetter(_emailBytes[point])) {
1210
+ lengthCount++;
1211
+ } else {
1212
+ if (_isAPoint(_emailBytes[point])) {
1213
+ flagVerify = 0x03;
1214
+ } else {
1215
+ revert();
1216
+ }
1217
+ }
1218
+ }
1219
+
1220
+ //step 4 0x03 count the domain name length
1221
+ if (flagVerify == 0x03) {
1222
+ if (lengthCount < 3) {
1223
+ revert();
1224
+ } else {
1225
+ flagVerify = 0x04;
1226
+ lengthCount = 0;
1227
+ point++;
1228
+ }
1229
+ }
1230
+
1231
+ //step 5 0x04 top level domain
1232
+ if (flagVerify == 0x04) {
1233
+ if (_isLetter(_emailBytes[point])) {
1234
+ lengthCount++;
1235
+ } else {
1236
+ if (_isAPoint(_emailBytes[point])) {
1237
+ if (lengthCount < 2) {
1238
+ revert();
1239
+ } else {
1240
+ lengthCount = 0;
1241
+ }
1242
+ } else {
1243
+ revert();
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+
1249
+ if (flagVerify != 0x04) {
1250
+ revert();
1251
+ }
1252
+
1253
+ return true;
1254
+ }
1255
+
1256
+ /// @dev Checks if a byte represents a digit (0-9)
1257
+ function _isDigit(bytes1 character) private pure returns (bool) {
1258
+ return (character >= 0x30 && character <= 0x39); // ASCII range for digits 0-9
1259
+ }
1260
+
1261
+ /// @dev Checks if a byte represents a letter (A-Z or a-z)
1262
+ function _isLetter(bytes1 character) private pure returns (bool) {
1263
+ return ((character >= 0x41 && character <= 0x5A) ||
1264
+ (character >= 0x61 && character <= 0x7A)); // ASCII ranges for letters A-Z and a-z
1265
+ }
1266
+
1267
+ /// @dev Checks if a byte represents any symbol character
1268
+ function _isAnySimbol(bytes1 character) private pure returns (bool) {
1269
+ return ((character >= 0x21 && character <= 0x2F) || /// @dev includes characters from "!" to "/"
1270
+ (character >= 0x3A && character <= 0x40) || /// @dev includes characters from ":" to "@"
1271
+ (character >= 0x5B && character <= 0x60) || /// @dev includes characters from "[" to "`"
1272
+ (character >= 0x7B && character <= 0x7E)); /// @dev includes characters from "{" to "~"
1273
+ }
1274
+
1275
+ /// @dev Checks if a byte is valid for email prefix (letters, digits, and specific symbols)
1276
+ function _isOnlyEmailPrefixCharacters(
1277
+ bytes1 character
1278
+ ) private pure returns (bool) {
1279
+ return (_isLetter(character) ||
1280
+ _isDigit(character) ||
1281
+ (character >= 0x21 && character <= 0x2F) || /// @dev includes characters from "!" to "/"
1282
+ (character >= 0x3A && character <= 0x3F) || /// @dev includes characters from ":" to "?"
1283
+ (character >= 0x5B && character <= 0x60) || /// @dev includes characters from "[" to "`"
1284
+ (character >= 0x7B && character <= 0x7E)); /// @dev includes characters from "{" to "~"
1285
+ }
1286
+
1287
+ /// @dev Checks if a byte represents a period/dot character (.)
1288
+ function _isAPoint(bytes1 character) private pure returns (bool) {
1289
+ return character == 0x2E;
1290
+ }
1291
+
1292
+ /// @dev Checks if a byte represents an at symbol (@)
1293
+ function _isAAt(bytes1 character) private pure returns (bool) {
1294
+ return character == 0x40;
1295
+ }
1296
+
1297
+ //█ Username Hashing Functions ███████████████████████████████████████████████████████████████████
1298
+
1299
+ /**
1300
+ * @notice Creates a hash of username and random number for pre-registration
1301
+ * @dev Used in the commit-reveal scheme to prevent front-running attacks
1302
+ * @param _username The username to hash
1303
+ * @param _randomNumber Random number to add entropy
1304
+ * @return Hash of the username and random number
1305
+ */
1306
+ function hashUsername(
1307
+ string memory _username,
1308
+ uint256 _randomNumber
1309
+ ) public pure returns (bytes32) {
1310
+ return keccak256(abi.encodePacked(_username, _randomNumber));
1311
+ }
1312
+
1313
+ //█ View Functions - Public Data Access ██████████████████████████████████████████████████████████
1314
+
1315
+ //█ Service Functions ████████████████████████████████████████████████████████████████
1316
+
1317
+ /**
1318
+ * @notice Checks if an identity exists in the system
1319
+ * @dev Handles both pre-registrations and actual username registrations
1320
+ * @param _identity The identity/username to check
1321
+ * @return True if the identity exists and is valid
1322
+ */
1323
+ function verifyIfIdentityExists(
1324
+ string memory _identity
1325
+ ) public view returns (bool) {
1326
+ if (identityDetails[_identity].flagNotAUsername == 0x01) {
1327
+ if (
1328
+ identityDetails[_identity].owner == address(0) ||
1329
+ identityDetails[_identity].expireDate != 0
1330
+ ) {
1331
+ return false;
1332
+ } else {
1333
+ return true;
1334
+ }
1335
+ } else {
1336
+ if (identityDetails[_identity].expireDate == 0) {
1337
+ return false;
1338
+ } else {
1339
+ return true;
1340
+ }
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * @notice Strictly verifies if an identity exists and reverts if not found
1346
+ * @dev More strict version that reverts instead of returning false
1347
+ * @param _username The username to verify
1348
+ * @return True if the username exists (will revert if not)
1349
+ */
1350
+ function strictVerifyIfIdentityExist(
1351
+ string memory _username
1352
+ ) public view returns (bool) {
1353
+ if (identityDetails[_username].flagNotAUsername == 0x01) {
1354
+ if (
1355
+ identityDetails[_username].owner == address(0) ||
1356
+ identityDetails[_username].expireDate != 0
1357
+ ) {
1358
+ revert();
1359
+ } else {
1360
+ return true;
1361
+ }
1362
+ } else {
1363
+ if (identityDetails[_username].expireDate == 0) {
1364
+ revert();
1365
+ } else {
1366
+ return true;
1367
+ }
1368
+ }
1369
+ }
1370
+
1371
+ /**
1372
+ * @notice Gets the owner address of a registered identity
1373
+ * @dev Returns the current owner address for any valid identity
1374
+ * @param _username The username to query
1375
+ * @return Address of the username owner
1376
+ */
1377
+ function getOwnerOfIdentity(
1378
+ string memory _username
1379
+ ) public view returns (address) {
1380
+ return identityDetails[_username].owner;
1381
+ }
1382
+
1383
+ /**
1384
+ * @notice Verifies identity exists and returns owner address
1385
+ * @dev Combines strict verification with owner lookup in one call
1386
+ * @param _username The username to verify and get owner for
1387
+ * @return answer Address of the username owner (reverts if username doesn't exist)
1388
+ */
1389
+ function verifyStrictAndGetOwnerOfIdentity(
1390
+ string memory _username
1391
+ ) public view returns (address answer) {
1392
+ if (strictVerifyIfIdentityExist(_username)) {
1393
+ answer = identityDetails[_username].owner;
1394
+ }
1395
+ }
1396
+
1397
+ /**
1398
+ * @notice Calculates the cost to renew a username registration
1399
+ * @dev Pricing varies based on timing and market demand:
1400
+ * - Free if renewed before expiration (within grace period)
1401
+ * - Variable cost based on highest active offer (minimum 500 Principal Token)
1402
+ * - Fixed 500,000 Principal Token if renewed more than 1 year before expiration
1403
+ * @param _identity The username to calculate renewal price for
1404
+ * @return price The cost in Principal Tokens to renew the username
1405
+ */
1406
+ function seePriceToRenew(
1407
+ string memory _identity
1408
+ ) public view returns (uint256 price) {
1409
+ if (identityDetails[_identity].expireDate >= block.timestamp) {
1410
+ if (usernameOffers[_identity][0].expireDate != 0) {
1411
+ for (
1412
+ uint256 i = 0;
1413
+ i < identityDetails[_identity].offerMaxSlots;
1414
+ i++
1415
+ ) {
1416
+ if (
1417
+ usernameOffers[_identity][i].expireDate >
1418
+ block.timestamp &&
1419
+ usernameOffers[_identity][i].offerer != address(0)
1420
+ ) {
1421
+ if (usernameOffers[_identity][i].amount > price) {
1422
+ price = usernameOffers[_identity][i].amount;
1423
+ }
1424
+ }
1425
+ }
1426
+ }
1427
+ if (price == 0) {
1428
+ price = 500 * 10 ** 18;
1429
+ } else {
1430
+ uint256 principalTokenReward = Evvm(evvmAddress.current)
1431
+ .getRewardAmount();
1432
+ price = ((price * 5) / 1000) > (500000 * principalTokenReward)
1433
+ ? (500000 * principalTokenReward)
1434
+ : ((price * 5) / 1000);
1435
+ }
1436
+ } else {
1437
+ price = 500_000 * Evvm(evvmAddress.current).getRewardAmount();
1438
+ }
1439
+ }
1440
+
1441
+ /**
1442
+ * @notice Gets the current price to add custom metadata to a username
1443
+ * @dev Price is dynamic based on current EVVM reward amount
1444
+ * @return price Cost in Principal Tokens (10x current reward amount)
1445
+ */
1446
+ function getPriceToAddCustomMetadata() public view returns (uint256 price) {
1447
+ price = 10 * Evvm(evvmAddress.current).getRewardAmount();
1448
+ }
1449
+
1450
+ /**
1451
+ * @notice Gets the current price to remove a single custom metadata entry
1452
+ * @dev Price is dynamic based on current EVVM reward amount
1453
+ * @return price Cost in Principal Tokens (10x current reward amount)
1454
+ */
1455
+ function getPriceToRemoveCustomMetadata()
1456
+ public
1457
+ view
1458
+ returns (uint256 price)
1459
+ {
1460
+ price = 10 * Evvm(evvmAddress.current).getRewardAmount();
1461
+ }
1462
+
1463
+ /**
1464
+ * @notice Gets the cost to remove all custom metadata entries from a username
1465
+ * @dev Cost scales with the number of metadata entries to remove
1466
+ * @param _identity The username to calculate flush cost for
1467
+ * @return price Total cost in Principal Tokens (10x reward amount per metadata entry)
1468
+ */
1469
+ function getPriceToFlushCustomMetadata(
1470
+ string memory _identity
1471
+ ) public view returns (uint256 price) {
1472
+ price =
1473
+ (10 * Evvm(evvmAddress.current).getRewardAmount()) *
1474
+ identityDetails[_identity].customMetadataMaxSlots;
1475
+ }
1476
+
1477
+ /**
1478
+ * @notice Gets the cost to completely remove a username and all its data
1479
+ * @dev Includes cost for metadata removal plus base username deletion fee
1480
+ * @param _identity The username to calculate deletion cost for
1481
+ * @return price Total cost in Principal Tokens (metadata flush cost + 1x reward amount)
1482
+ */
1483
+ function getPriceToFlushUsername(
1484
+ string memory _identity
1485
+ ) public view returns (uint256 price) {
1486
+ price =
1487
+ ((10 * Evvm(evvmAddress.current).getRewardAmount()) *
1488
+ identityDetails[_identity].customMetadataMaxSlots) +
1489
+ Evvm(evvmAddress.current).getRewardAmount();
1490
+ }
1491
+
1492
+ //█ User Management Functions ████████████████████████████████████████████████████████████████████
1493
+
1494
+ /**
1495
+ * @notice Checks if a nonce has been used by a specific user
1496
+ * @dev Prevents replay attacks by tracking used nonces per user
1497
+ * @param _user Address of the user to check
1498
+ * @param _nonce Nonce value to verify
1499
+ * @return True if the nonce has been used, false if still available
1500
+ */
1501
+ function checkIfNameServiceNonceIsAvailable(
1502
+ address _user,
1503
+ uint256 _nonce
1504
+ ) public view returns (bool) {
1505
+ return nameServiceNonce[_user][_nonce];
1506
+ }
1507
+
1508
+ //█ Identity Availability Functions ██████████████████████████████████████████████████████████████
1509
+
1510
+ /**
1511
+ * @notice Checks if a username is available for registration
1512
+ * @dev A username is available if it was never registered or has been expired for 60+ days
1513
+ * @param _username The username to check availability for
1514
+ * @return True if the username is available for registration
1515
+ */
1516
+ function isUsernameAvailable(
1517
+ string memory _username
1518
+ ) public view returns (bool) {
1519
+ if (identityDetails[_username].expireDate == 0) {
1520
+ return true;
1521
+ } else {
1522
+ return
1523
+ identityDetails[_username].expireDate + 60 days <
1524
+ block.timestamp;
1525
+ }
1526
+ }
1527
+
1528
+ /**
1529
+ * @notice Gets basic identity information (owner and expiration date)
1530
+ * @dev Returns essential metadata for quick identity verification
1531
+ * @param _username The username to get basic info for
1532
+ * @return Owner address and expiration timestamp
1533
+ */
1534
+ function getIdentityBasicMetadata(
1535
+ string memory _username
1536
+ ) public view returns (address, uint256) {
1537
+ return (
1538
+ identityDetails[_username].owner,
1539
+ identityDetails[_username].expireDate
1540
+ );
1541
+ }
1542
+
1543
+ /**
1544
+ * @notice Gets the number of custom metadata entries for a username
1545
+ * @dev Returns the count of metadata slots currently used
1546
+ * @param _username The username to count metadata for
1547
+ * @return Number of custom metadata entries
1548
+ */
1549
+ function getAmountOfCustomMetadata(
1550
+ string memory _username
1551
+ ) public view returns (uint256) {
1552
+ return identityDetails[_username].customMetadataMaxSlots;
1553
+ }
1554
+
1555
+ /**
1556
+ * @notice Retrieves all custom metadata entries for a username
1557
+ * @dev Returns an array containing all metadata strings in order
1558
+ * @param _username The username to get metadata for
1559
+ * @return Array of all custom metadata strings
1560
+ */
1561
+ function getFullCustomMetadataOfIdentity(
1562
+ string memory _username
1563
+ ) public view returns (string[] memory) {
1564
+ string[] memory _customMetadata = new string[](
1565
+ identityDetails[_username].customMetadataMaxSlots
1566
+ );
1567
+ for (
1568
+ uint256 i = 0;
1569
+ i < identityDetails[_username].customMetadataMaxSlots;
1570
+ i++
1571
+ ) {
1572
+ _customMetadata[i] = identityCustomMetadata[_username][i];
1573
+ }
1574
+ return _customMetadata;
1575
+ }
1576
+
1577
+ /**
1578
+ * @notice Gets a specific custom metadata entry by index
1579
+ * @dev Retrieves metadata at a specific slot position
1580
+ * @param _username The username to get metadata from
1581
+ * @param _key The index of the metadata entry to retrieve
1582
+ * @return The metadata string at the specified index
1583
+ */
1584
+ function getSingleCustomMetadataOfIdentity(
1585
+ string memory _username,
1586
+ uint256 _key
1587
+ ) public view returns (string memory) {
1588
+ return identityCustomMetadata[_username][_key];
1589
+ }
1590
+
1591
+ /**
1592
+ * @notice Gets the maximum number of metadata slots available for a username
1593
+ * @dev Returns the total capacity for custom metadata entries
1594
+ * @param _username The username to check metadata capacity for
1595
+ * @return Maximum number of metadata slots
1596
+ */
1597
+ function getCustomMetadataMaxSlotsOfIdentity(
1598
+ string memory _username
1599
+ ) public view returns (uint256) {
1600
+ return identityDetails[_username].customMetadataMaxSlots;
1601
+ }
1602
+
1603
+ //█ Username Marketplace Functions ███████████████████████████████████████████████████████████████
1604
+
1605
+ /**
1606
+ * @notice Gets all offers made for a specific username
1607
+ * @dev Returns both active and expired offers that haven't been withdrawn
1608
+ * @param _username The username to get offers for
1609
+ * @return offers Array of all offer metadata structures
1610
+ */
1611
+ function getOffersOfUsername(
1612
+ string memory _username
1613
+ ) public view returns (OfferMetadata[] memory offers) {
1614
+ offers = new OfferMetadata[](identityDetails[_username].offerMaxSlots);
1615
+
1616
+ for (uint256 i = 0; i < identityDetails[_username].offerMaxSlots; i++) {
1617
+ offers[i] = usernameOffers[_username][i];
1618
+ }
1619
+ }
1620
+
1621
+ /**
1622
+ * @notice Gets a specific offer for a username by offer ID
1623
+ * @dev Retrieves detailed information about a particular offer
1624
+ * @param _username The username to get the offer from
1625
+ * @param _offerID The ID/index of the specific offer
1626
+ * @return offer The complete offer metadata structure
1627
+ */
1628
+ function getSingleOfferOfUsername(
1629
+ string memory _username,
1630
+ uint256 _offerID
1631
+ ) public view returns (OfferMetadata memory offer) {
1632
+ return usernameOffers[_username][_offerID];
1633
+ }
1634
+
1635
+ /**
1636
+ * @notice Counts the total number of offers made for a username
1637
+ * @dev Iterates through offers to find the actual count of non-empty slots
1638
+ * @param _username The username to count offers for
1639
+ * @return length Total number of offers that have been made
1640
+ */
1641
+ function getLengthOfOffersUsername(
1642
+ string memory _username
1643
+ ) public view returns (uint256 length) {
1644
+ do {
1645
+ length++;
1646
+ } while (usernameOffers[_username][length].expireDate != 0);
1647
+ }
1648
+
1649
+ /**
1650
+ * @notice Gets the expiration date of a username registration
1651
+ * @dev Returns the timestamp when the username registration expires
1652
+ * @param _identity The username to check expiration for
1653
+ * @return The expiration timestamp in seconds since Unix epoch
1654
+ */
1655
+ function getExpireDateOfIdentity(
1656
+ string memory _identity
1657
+ ) public view returns (uint256) {
1658
+ return identityDetails[_identity].expireDate;
1659
+ }
1660
+
1661
+ /**
1662
+ * @notice Gets the current price for registering a new username
1663
+ * @dev Price is dynamic and based on current EVVM reward amount (100x reward)
1664
+ * @return The current registration price in Principal Tokens
1665
+ */
1666
+ function getPricePerRegistration() public view returns (uint256) {
1667
+ return Evvm(evvmAddress.current).getRewardAmount() * 100;
1668
+ }
1669
+
1670
+ //█ Administrative Getters ███████████████████████████████████████████████████████████████████████
1671
+
1672
+ /**
1673
+ * @notice Gets the current admin address
1674
+ * @dev Returns the address with administrative privileges
1675
+ * @return The current admin address
1676
+ */
1677
+ function getAdmin() public view returns (address) {
1678
+ return admin.current;
1679
+ }
1680
+
1681
+ /**
1682
+ * @notice Gets complete admin information including pending proposals
1683
+ * @dev Returns current admin, proposed admin, and proposal acceptance deadline
1684
+ * @return currentAdmin Current administrative address
1685
+ * @return proposalAdmin Proposed new admin address (if any)
1686
+ * @return timeToAcceptAdmin Timestamp when proposal can be accepted
1687
+ */
1688
+ function getAdminFullDetails()
1689
+ public
1690
+ view
1691
+ returns (
1692
+ address currentAdmin,
1693
+ address proposalAdmin,
1694
+ uint256 timeToAcceptAdmin
1695
+ )
1696
+ {
1697
+ return (admin.current, admin.proposal, admin.timeToAccept);
1698
+ }
1699
+
1700
+ /**
1701
+ * @notice Gets information about pending token withdrawal proposals
1702
+ * @dev Returns proposed withdrawal amount and acceptance deadline
1703
+ * @return proposalAmountToWithdrawTokens Proposed withdrawal amount in Principal Tokens
1704
+ * @return timeToAcceptAmountToWithdrawTokens Timestamp when proposal can be executed
1705
+ */
1706
+ function getProposedWithdrawAmountFullDetails()
1707
+ public
1708
+ view
1709
+ returns (
1710
+ uint256 proposalAmountToWithdrawTokens,
1711
+ uint256 timeToAcceptAmountToWithdrawTokens
1712
+ )
1713
+ {
1714
+ return (
1715
+ amountToWithdrawTokens.proposal,
1716
+ amountToWithdrawTokens.timeToAccept
1717
+ );
1718
+ }
1719
+
1720
+ /**
1721
+ * @notice Gets the current EVVM contract address
1722
+ * @dev Returns the address of the EVVM contract used for payment processing
1723
+ * @return The current EVVM contract address
1724
+ */
1725
+ function getEvvmAddress() public view returns (address) {
1726
+ return evvmAddress.current;
1727
+ }
1728
+
1729
+ /**
1730
+ * @notice Gets complete EVVM address information including pending proposals
1731
+ * @dev Returns current EVVM address, proposed address, and proposal acceptance deadline
1732
+ * @return currentEvvmAddress Current EVVM contract address
1733
+ * @return proposalEvvmAddress Proposed new EVVM address (if any)
1734
+ * @return timeToAcceptEvvmAddress Timestamp when proposal can be accepted
1735
+ */
1736
+ function getEvvmAddressFullDetails()
1737
+ public
1738
+ view
1739
+ returns (
1740
+ address currentEvvmAddress,
1741
+ address proposalEvvmAddress,
1742
+ uint256 timeToAcceptEvvmAddress
1743
+ )
1744
+ {
1745
+ return (
1746
+ evvmAddress.current,
1747
+ evvmAddress.proposal,
1748
+ evvmAddress.timeToAccept
1749
+ );
1750
+ }
1751
+ }