@@ -7,26 +7,35 @@ import {ERC721Burnable} from "openzeppelin-contracts/contracts/token/ERC721/exte
77import {ERC721Enumerable } from "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol " ;
88import {ERC721Pausable } from "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol " ;
99import {ERC721URIStorage } from "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol " ;
10- import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol " ;
1110
1211import {IFraxiversarryErrors} from "./interfaces/IFraxiversarryErrors.sol " ;
1312import {IFraxiversarryEvents} from "./interfaces/IFraxiversarryEvents.sol " ;
1413import {IERC6454 } from "./interfaces/IERC6454.sol " ;
1514import {IERC7590 } from "./interfaces/IERC7590.sol " ;
1615import {IERC4906 } from "openzeppelin-contracts/contracts/interfaces/IERC4906.sol " ;
1716
17+ import {ONFT721Core} from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721Core.sol " ;
18+ import {IONFT721, SendParam} from "@layerzerolabs/onft-evm/contracts/onft721/interfaces/IONFT721.sol " ;
19+ import {ONFT721MsgCodec} from "@layerzerolabs/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol " ;
20+ import {ONFTComposeMsgCodec} from "@layerzerolabs/onft-evm/contracts/libs/ONFTComposeMsgCodec.sol " ;
21+ import {IOAppMsgInspector} from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.sol " ;
22+ import {Origin} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol " ;
23+
1824contract Fraxiversarry is
1925 ERC721 ,
2026 ERC721Enumerable ,
2127 ERC721URIStorage ,
2228 ERC721Pausable ,
23- Ownable ,
2429 ERC721Burnable ,
2530 IERC6454 ,
2631 IERC7590 ,
2732 IFraxiversarryErrors ,
28- IFraxiversarryEvents
33+ IFraxiversarryEvents ,
34+ ONFT721Core
2935{
36+ using ONFT721MsgCodec for bytes ;
37+ using ONFT721MsgCodec for bytes32 ;
38+
3039 address public constant WFRAX_ADDRESS = 0xFc00000000000000000000000000000000000002 ;
3140
3241 mapping (address erc20 = > uint256 price ) public mintPrices;
@@ -48,6 +57,8 @@ contract Fraxiversarry is
4857 uint256 public giftMintingLimit;
4958 uint256 public giftMintingPrice;
5059
60+ bool private _isBridgeOperation;
61+
5162 enum TokenType {
5263 NONEXISTENT, // 0 - Token does not exist
5364 BASE, // 1 - NFTs that are minted using ERC20 tokens
@@ -59,7 +70,10 @@ contract Fraxiversarry is
5970 string private giftTokenUri;
6071 string private premiumTokenUri;
6172
62- constructor (address initialOwner ) ERC721 ("Fraxiversarry " , "FRAX5Y " ) Ownable (initialOwner) {
73+ constructor (address initialOwner , address lzEndpoint )
74+ ERC721 ("Fraxiversarry " , "FRAX5Y " )
75+ ONFT721Core (lzEndpoint, initialOwner)
76+ {
6377 mintingLimit = 12_000 ;
6478 giftMintingLimit = 50_000 ;
6579 giftMintingPrice = 50 * 1e18 ; // 50 WFRAX
@@ -460,23 +474,33 @@ contract Fraxiversarry is
460474 emit TokenUnfused (msg .sender , tokenId1, tokenId2, tokenId3, tokenId4, premiumTokenId);
461475 }
462476
477+ // ********** ONFT functional overrides **********
478+
479+ function token () external view override returns (address ) {
480+ return address (this );
481+ }
482+
483+ function approvalRequired () public view override returns (bool ) {
484+ return false ;
485+ }
486+
463487 // ********** Internal functions to facilitate the ERC6454 functionality **********
464488
465489 function _soulboundCheck (uint256 tokenId ) internal view {
466- if (isNonTransferrable[tokenId]) revert CannotTransferSoulboundToken ();
490+ if (! _isBridgeOperation && isNonTransferrable[tokenId]) revert CannotTransferSoulboundToken ();
467491 }
468492
469493 // ********** Internal functions to facilitate the ERC7590 functionality **********
470494
471495 function _transferHeldERC20FromToken (address erc20Contract , uint256 tokenId , address to , uint256 amount ) internal {
472- IERC20 token = IERC20 (erc20Contract);
496+ IERC20 erc20Token = IERC20 (erc20Contract);
473497
474498 if (erc20Balances[tokenId][erc20Contract] < amount) revert InsufficientBalance ();
475499
476500 erc20Balances[tokenId][erc20Contract] -= amount;
477501 transferOutNonces[tokenId]++ ;
478502
479- if (! token .transfer (to, amount)) revert TransferFailed ();
503+ if (! erc20Token .transfer (to, amount)) revert TransferFailed ();
480504
481505 emit TransferredERC20 (erc20Contract, tokenId, to, amount);
482506 }
@@ -488,19 +512,122 @@ contract Fraxiversarry is
488512 }
489513
490514 function _transferERC20ToToken (address erc20Contract , uint256 tokenId , address from , uint256 amount ) internal {
491- IERC20 token = IERC20 (erc20Contract);
515+ IERC20 erc20Token = IERC20 (erc20Contract);
492516
493- if (token .allowance (from, address (this )) < amount) revert InsufficientAllowance ();
494- if (token .balanceOf (from) < amount) revert InsufficientBalance ();
517+ if (erc20Token .allowance (from, address (this )) < amount) revert InsufficientAllowance ();
518+ if (erc20Token .balanceOf (from) < amount) revert InsufficientBalance ();
495519
496- if (! token .transferFrom (from, address (this ), amount)) revert TransferFailed ();
520+ if (! erc20Token .transferFrom (from, address (this ), amount)) revert TransferFailed ();
497521
498522 erc20Balances[tokenId][erc20Contract] += amount;
499523
500524 emit ReceivedERC20 (erc20Contract, tokenId, from, amount);
501525 }
502526
503- // The following functions are overrides required by Solidity.
527+ // ********** Internal functions to facilitate the ONFT operations **********
528+
529+ function _bridgeBurn (address owner , uint256 tokenId ) internal {
530+ _isBridgeOperation = true ;
531+ // Token should only be burned, but the state including ERC20 balances should be preserved
532+ _update (address (0 ), tokenId, owner);
533+ _isBridgeOperation = false ;
534+ }
535+
536+ function _debit (
537+ address from ,
538+ uint256 tokenId ,
539+ uint32 /*dstEid*/
540+ )
541+ internal
542+ override
543+ {
544+ address owner = ownerOf (tokenId);
545+
546+ if (from != owner && ! isApprovedForAll (owner, from) && getApproved (tokenId) != from) {
547+ revert ERC721InsufficientApproval (from, tokenId);
548+ }
549+
550+ _bridgeBurn (owner, tokenId);
551+ }
552+
553+ function _credit (
554+ address to ,
555+ uint256 tokenId ,
556+ uint32 /*srcEid*/
557+ )
558+ internal
559+ override
560+ {
561+ if (_ownerOf (tokenId) != address (0 )) revert TokenAlreadyExists (tokenId);
562+
563+ _isBridgeOperation = true ;
564+ _update (to, tokenId, address (0 ));
565+ _isBridgeOperation = false ;
566+ }
567+
568+ function _buildMsgAndOptions (SendParam calldata sendParam )
569+ internal
570+ view
571+ override
572+ returns (bytes memory message , bytes memory options )
573+ {
574+ if (sendParam.to == bytes32 (0 )) revert InvalidReceiver ();
575+
576+ string memory tokenUri = tokenURI (sendParam.tokenId);
577+ bool isSoulbound = isNonTransferrable[sendParam.tokenId];
578+ bytes memory composedMessage = abi.encode (tokenUri, isSoulbound);
579+
580+ if (isSoulbound && sendParam.to.bytes32ToAddress () != ownerOf (sendParam.tokenId)) {
581+ revert CannotTransferSoulboundToken ();
582+ }
583+
584+ bool hasCompose;
585+ (message, hasCompose) = ONFT721MsgCodec.encode (sendParam.to, sendParam.tokenId, composedMessage);
586+
587+ uint16 msgType = hasCompose ? SEND_AND_COMPOSE : SEND;
588+ options = combineOptions (sendParam.dstEid, msgType, sendParam.extraOptions);
589+
590+ address inspector = msgInspector;
591+ if (inspector != address (0 )) IOAppMsgInspector (inspector).inspect (message, options);
592+ }
593+
594+ function _lzReceive (Origin calldata origin , bytes32 guid , bytes calldata message , address , bytes calldata )
595+ internal
596+ override
597+ {
598+ address toAddress = message.sendTo ().bytes32ToAddress ();
599+ uint256 tokenId = message.tokenId ();
600+
601+ if (! message.isComposed ()) revert MissingComposedMessage ();
602+
603+ bytes memory rawCompose = message.composeMsg ();
604+ bytes memory rawMessage = rawCompose;
605+ uint256 len;
606+ assembly {
607+ len := mload (rawCompose)
608+ // shift pointer forward by 32 bytes (skip fromOApp word)
609+ rawMessage := add (rawMessage, 32 )
610+ // set length = originalLength - 32
611+ mstore (rawMessage, sub (len, 32 ))
612+ }
613+
614+ (string memory tokenUri , bool isSoulbound ) = abi.decode (rawMessage, (string , bool ));
615+
616+ _credit (toAddress, tokenId, origin.srcEid);
617+ _setTokenURI (tokenId, tokenUri);
618+ isNonTransferrable[tokenId] = isSoulbound;
619+
620+ bytes32 composeFrom = ONFTComposeMsgCodec.addressToBytes32 (address (this ));
621+ bytes memory composeInnerMsg = abi.encode (tokenUri, isSoulbound);
622+ bytes memory composeMsg = abi.encodePacked (composeFrom, composeInnerMsg);
623+
624+ bytes memory composedMsgEncoded = ONFTComposeMsgCodec.encode (origin.nonce, origin.srcEid, composeMsg);
625+ endpoint.sendCompose (toAddress, guid, 0 , composedMsgEncoded);
626+
627+ emit ONFTReceived (guid, origin.srcEid, toAddress, tokenId);
628+ }
629+
630+ // ********** The following functions are overrides required by Solidity. **********
504631
505632 function _update (address to , uint256 tokenId , address auth )
506633 internal
@@ -531,6 +658,7 @@ contract Fraxiversarry is
531658 returns (bool )
532659 {
533660 return super .supportsInterface (interfaceId) || interfaceId == type (IERC7590 ).interfaceId
534- || interfaceId == type (IERC6454 ).interfaceId || interfaceId == type (IERC4906 ).interfaceId;
661+ || interfaceId == type (IERC6454 ).interfaceId || interfaceId == type (IERC4906 ).interfaceId
662+ || interfaceId == type (IONFT721).interfaceId;
535663 }
536664}
0 commit comments