@@ -79,6 +79,8 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
7979 // ERC20 Events are inherited from IERC20 (Transfer, Approval)
8080
8181 // ERC721 Events (using different names to avoid conflicts with ERC20)
82+ // event Transfer(address indexed from, address indexed to, uint256 value);
83+ event ERC20Transfer (address indexed from , address indexed to , uint256 value );
8284 event ERC721Transfer (address indexed from , address indexed to , uint256 indexed id );
8385 event ERC721Approval (address indexed owner , address indexed spender , uint256 indexed id );
8486 event ApprovalForAll (address indexed owner , address indexed operator , bool approved );
@@ -202,12 +204,9 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
202204 }
203205
204206 function ownerOf (uint256 id_ ) public view virtual override (IERC404 ) returns (address ) {
205- if (! _isValidTokenId (id_)) {
206- revert InvalidTokenId ();
207- }
208-
209207 TokenStorage storage $ = _getS ();
210- TokenData storage t = $.tokens[id_];
208+ uint256 tokenId = _normalizeTokenId (id_);
209+ TokenData storage t = $.tokens[tokenId];
211210
212211 if (! t.exists) {
213212 revert NotFound ();
@@ -223,7 +222,13 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
223222
224223 function getApproved (uint256 id_ ) public view virtual returns (address ) {
225224 TokenStorage storage $ = _getS ();
226- return $.getApproved[id_];
225+ uint256 tokenId = _normalizeTokenId (id_);
226+
227+ if (! $.tokens[tokenId].exists) {
228+ revert NotFound ();
229+ }
230+
231+ return $.getApproved[tokenId];
227232 }
228233
229234 function isApprovedForAll (address owner_ , address operator_ ) public view virtual override (IERC404 ) returns (bool ) {
@@ -366,25 +371,29 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
366371 $.balances[to_] += value_;
367372 }
368373
369- emit Transfer (from_, to_, value_);
374+ emit ERC20Transfer (from_, to_, value_);
370375 }
371376
372377 /// @notice Transfer an ERC721 token
373378 function _transferERC721 (address from_ , address to_ , uint256 id_ ) internal virtual {
374379 TokenStorage storage $ = _getS ();
375- TokenData storage t = $.tokens[id_];
376-
377- if (from_ != ownerOf (id_)) {
380+ uint256 tokenId = _normalizeTokenId (id_);
381+ TokenData storage t = $.tokens[tokenId];
382+
383+ if (! t.exists) {
384+ revert NotFound ();
385+ }
386+ if (from_ != t.owner) {
378387 revert Unauthorized ();
379388 }
380389
381390 if (from_ != address (0 )) {
382391 // Clear approval
383- delete $.getApproved[id_ ];
392+ delete $.getApproved[tokenId ];
384393
385394 // Remove from sender's owned list
386395 uint256 lastTokenId = $.owned[from_][$.owned[from_].length - 1 ];
387- if (lastTokenId != id_ ) {
396+ if (lastTokenId != tokenId ) {
388397 uint256 updatedIndex = t.index;
389398 $.owned[from_][updatedIndex] = lastTokenId;
390399 $.tokens[lastTokenId].index = uint88 (updatedIndex);
@@ -399,9 +408,9 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
399408 }
400409 t.owner = to_;
401410 t.index = uint88 (newIndex);
402- $.owned[to_].push (id_ );
411+ $.owned[to_].push (tokenId );
403412
404- emit ERC721Transfer (from_, to_, id_ );
413+ emit ERC721Transfer (from_, to_, tokenId );
405414 }
406415
407416 /// @notice Mint ERC20 tokens without triggering NFT creation
@@ -424,7 +433,7 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
424433 TokenStorage storage $ = _getS ();
425434
426435 // Add the ID_ENCODING_PREFIX to the provided ID
427- uint256 id = ID_ENCODING_PREFIX + nftId_;
436+ uint256 id = _encodeMintId ( nftId_) ;
428437
429438 TokenData storage t = $.tokens[id];
430439
@@ -444,8 +453,37 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
444453 // HELPER FUNCTIONS
445454 // =============================================================
446455
447- function _isValidTokenId (uint256 id_ ) internal pure returns (bool ) {
448- return id_ > ID_ENCODING_PREFIX && id_ != type (uint256 ).max;
456+ /// @dev Normalizes caller input into the encoded tokenId form (adds prefix for human mint IDs).
457+ function _normalizeTokenId (uint256 id_ ) internal pure returns (uint256 tokenId_ ) {
458+ if (id_ == 0 || id_ == type (uint256 ).max) {
459+ revert InvalidTokenId ();
460+ }
461+
462+ tokenId_ = id_ < ID_ENCODING_PREFIX ? _encodeMintId (id_) : id_;
463+
464+ if (tokenId_ <= ID_ENCODING_PREFIX || tokenId_ == type (uint256 ).max) {
465+ revert InvalidTokenId ();
466+ }
467+ }
468+
469+ /// @dev Encodes a human-friendly mintId into the full tokenId with prefix.
470+ function _encodeMintId (uint256 mintId_ ) internal pure returns (uint256 ) {
471+ if (mintId_ == 0 || mintId_ >= ID_ENCODING_PREFIX) {
472+ revert InvalidTokenId ();
473+ }
474+ uint256 tokenId = ID_ENCODING_PREFIX + mintId_;
475+ if (tokenId == type (uint256 ).max) {
476+ revert InvalidTokenId ();
477+ }
478+ return tokenId;
479+ }
480+
481+ /// @dev Decodes an encoded tokenId back to the human-friendly mintId.
482+ function _decodeTokenId (uint256 tokenId_ ) internal pure returns (uint256 ) {
483+ if (tokenId_ <= ID_ENCODING_PREFIX || tokenId_ == type (uint256 ).max) {
484+ revert InvalidTokenId ();
485+ }
486+ return tokenId_ - ID_ENCODING_PREFIX;
449487 }
450488
451489 // =============================================================
0 commit comments