├── .prettierrc ├── .solhintignore ├── .npmignore ├── .eslintignore ├── .prettierignore ├── .gitignore ├── .solhint.json ├── tsconfig.json ├── scripts ├── deploy-fuji.ts ├── deploy-rinkeby.ts ├── test-deploy-fuji-lz.js └── test-deploy-rinkeby-lz.js ├── test ├── nft_test.js ├── lz-test-fuji.js ├── marketplace_test.js └── lz-test.js ├── .eslintrc.js ├── contracts ├── interfaces │ ├── ILayerZeroReceiver.sol │ ├── ILayerZeroUserApplicationConfig.sol │ ├── IONFT721.sol │ └── ILayerZeroEndpoint.sol ├── LZ │ ├── UniversalONFT721.sol │ ├── NonblockingLzApp.sol │ ├── LzApp.sol │ └── ONFT721.sol └── core │ ├── Marketplace.sol │ ├── NFT.sol │ └── testsending.sol ├── package.json ├── hardhat.config.ts └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.ts 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | 7 | #Hardhat files 8 | cache 9 | artifacts 10 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "declaration": true 9 | }, 10 | "include": ["./scripts", "./test", "./typechain"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /scripts/deploy-fuji.ts: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | 3 | async function deploy_nft() { 4 | const testNFT = await hre.ethers.getContractFactory("TestNFT"); 5 | const nft = await testNFT.deploy("TESTNFT","TEST","0x93f54D755A063cE7bB9e6Ac47Eccc8e33411d706",0,100); 6 | await nft.deployed(); 7 | console.log("TestNFT deployed to:", nft.address); 8 | } 9 | 10 | deploy_nft().catch((error) => { 11 | console.error(error); 12 | process.exitCode = 1; 13 | }); -------------------------------------------------------------------------------- /scripts/deploy-rinkeby.ts: -------------------------------------------------------------------------------- 1 | 2 | async function main() { 3 | const TestMarketplace = await hre.ethers.getContractFactory("TestMarketplace"); 4 | const testMarketplace = await TestMarketplace.deploy( 5 | "0x79a63d6d8BBD5c6dfc774dA79bCcD948EAcb53FA" 6 | ); 7 | await testMarketplace.deployed(); 8 | console.log("testMarketplace deployed to:", testMarketplace.address); 9 | } 10 | main().catch((error) => { 11 | console.error(error); 12 | process.exitCode = 1; 13 | }); -------------------------------------------------------------------------------- /scripts/test-deploy-fuji-lz.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | async function main() { 3 | const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1"); 4 | const layerZeroDemo1 = await LayerZeroDemo1.deploy( 5 | "0x93f54D755A063cE7bB9e6Ac47Eccc8e33411d706" 6 | ); 7 | await layerZeroDemo1.deployed(); 8 | console.log("layerZeroDemo1 deployed to:", layerZeroDemo1.address); 9 | } 10 | main().catch((error) => { 11 | console.error(error); 12 | process.exitCode = 1; 13 | }); -------------------------------------------------------------------------------- /scripts/test-deploy-rinkeby-lz.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | async function main() { 3 | const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1"); 4 | const layerZeroDemo1 = await LayerZeroDemo1.deploy( 5 | "0x79a63d6d8BBD5c6dfc774dA79bCcD948EAcb53FA" 6 | ); 7 | await layerZeroDemo1.deployed(); 8 | console.log("layerZeroDemo1 deployed to:", layerZeroDemo1.address); 9 | } 10 | main().catch((error) => { 11 | console.error(error); 12 | process.exitCode = 1; 13 | }); -------------------------------------------------------------------------------- /test/nft_test.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const { formatBytes32String } = require("ethers/lib/utils"); 3 | const { ethers } = require("ethers"); 4 | 5 | async function main() { 6 | 7 | const TestNFT = await hre.ethers.getContractFactory("TestNFT"); 8 | const testNFT = await TestNFT.attach( 9 | "0x5A1DC699765a6044Cce67f9985a8291b476F5Bb6" 10 | ); 11 | 12 | const counter = await testNFT.counter(); 13 | console.log(counter); 14 | } 15 | main().catch((error) => { 16 | console.error(error); 17 | process.exitCode = 1; 18 | }); -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | plugins: ["@typescript-eslint"], 9 | extends: [ 10 | "standard", 11 | "plugin:prettier/recommended", 12 | "plugin:node/recommended", 13 | ], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | rules: { 19 | "node/no-unsupported-features/es-syntax": [ 20 | "error", 21 | { ignores: ["modules"] }, 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /test/lz-test-fuji.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const { ethers } = require("ethers"); 3 | async function main() { 4 | const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1"); 5 | const layerZeroDemo1 = await LayerZeroDemo1.attach( 6 | "0x819d5e4b469b428DC542b13a3319EDfFD58aC2E2" 7 | ); 8 | const count = await layerZeroDemo1.messageCount(); 9 | const msg = await layerZeroDemo1.message(); 10 | console.log(count); 11 | console.log(ethers.utils.toUtf8String(msg)); 12 | } 13 | main().catch((error) => { 14 | console.error(error); 15 | process.exitCode = 1; 16 | }); -------------------------------------------------------------------------------- /contracts/interfaces/ILayerZeroReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface ILayerZeroReceiver { 6 | // @notice LayerZero endpoint will invoke this function to deliver the message on the destination 7 | // @param _srcChainId - the source endpoint identifier 8 | // @param _srcAddress - the source sending contract address from the source chain 9 | // @param _nonce - the ordered message nonce 10 | // @param _payload - the signed payload is the UA bytes has encoded to be sent 11 | function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64 _nonce, bytes calldata _payload) external; 12 | } -------------------------------------------------------------------------------- /test/marketplace_test.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const { formatBytes32String } = require("ethers/lib/utils"); 3 | const { ethers } = require("ethers"); 4 | 5 | async function main() { 6 | const TestMarketplace = await hre.ethers.getContractFactory("TestMarketplace"); 7 | const testMarketplace = await TestMarketplace.attach( 8 | "0x6b49795FF24F2d23631d97E6fA235f9E2AF00911" 9 | ); 10 | 11 | const fees = await testMarketplace.estimateFees( 12 | 10006, 13 | "0x025EF5a2d6AF68E229fC9A49681a64ce0787D520", 14 | formatBytes32String("Hello LayerZero"), 15 | false, 16 | [] 17 | ); 18 | console.log(ethers.utils.formatEther(fees[0].toString())); 19 | 20 | await testMarketplace.incrementCounter( 21 | 10006, 22 | { value: ethers.utils.parseEther(ethers.utils.formatEther(fees[0].toString())) } 23 | ); 24 | 25 | } 26 | main().catch((error) => { 27 | console.error(error); 28 | process.exitCode = 1; 29 | }); -------------------------------------------------------------------------------- /test/lz-test.js: -------------------------------------------------------------------------------- 1 | const { formatBytes32String } = require("ethers/lib/utils"); 2 | const { ethers } = require("ethers"); 3 | const hre = require("hardhat"); 4 | async function main() { 5 | const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1"); 6 | const layerZeroDemo1 = await LayerZeroDemo1.attach( 7 | "0x5a6ef98aDcf9dF1903956cCfA46a00Bff6628FA0" 8 | ); 9 | const fees = await layerZeroDemo1.estimateFees( 10 | 10006, 11 | "0x819d5e4b469b428DC542b13a3319EDfFD58aC2E2", 12 | formatBytes32String("Hello LayerZero"), 13 | false, 14 | [] 15 | ); 16 | console.log(ethers.utils.formatEther(fees[0].toString())); 17 | await layerZeroDemo1.sendMsg( 18 | 10006, 19 | "0x819d5e4b469b428DC542b13a3319EDfFD58aC2E2", 20 | formatBytes32String("Hello LayerZero"), 21 | { value: ethers.utils.parseEther(ethers.utils.formatEther(fees[0].toString())) } 22 | ); 23 | } 24 | main().catch((error) => { 25 | console.error(error); 26 | process.exitCode = 1; 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /contracts/LZ/UniversalONFT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8; 4 | 5 | import "./ONFT721.sol"; 6 | 7 | /// @title Interface of the UniversalONFT standard 8 | contract UniversalONFT721 is ONFT721 { 9 | uint public nextMintId; 10 | uint public maxMintId; 11 | 12 | /// @notice Constructor for the UniversalONFT 13 | /// @param _name the name of the token 14 | /// @param _symbol the token symbol 15 | /// @param _layerZeroEndpoint handles message transmission across chains 16 | /// @param _startMintId the starting mint number on this chain 17 | /// @param _endMintId the max number of mints on this chain 18 | constructor(string memory _name, string memory _symbol, address _layerZeroEndpoint, uint _startMintId, uint _endMintId) ONFT721(_name, _symbol, _layerZeroEndpoint) { 19 | nextMintId = _startMintId; 20 | maxMintId = _endMintId; 21 | } 22 | 23 | /// @notice Mint your ONFT 24 | function mint() external payable { 25 | require(nextMintId <= maxMintId, "ONFT: Max Mint limit reached"); 26 | 27 | uint newId = nextMintId; 28 | nextMintId++; 29 | 30 | _safeMint(msg.sender, newId); 31 | } 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@nomiclabs/hardhat-ethers": "^2.0.5", 5 | "@nomiclabs/hardhat-etherscan": "^3.0.3", 6 | "@nomiclabs/hardhat-waffle": "^2.0.3", 7 | "@typechain/ethers-v5": "^7.2.0", 8 | "@typechain/hardhat": "^2.3.1", 9 | "@types/chai": "^4.3.1", 10 | "@types/expect": "^24.3.0", 11 | "@types/mocha": "^9.1.1", 12 | "@types/node": "^12.20.50", 13 | "@typescript-eslint/eslint-plugin": "^4.33.0", 14 | "@typescript-eslint/parser": "^4.33.0", 15 | "chai": "^4.3.6", 16 | "dotenv": "^10.0.0", 17 | "eslint": "^7.32.0", 18 | "eslint-config-prettier": "^8.5.0", 19 | "eslint-config-standard": "^16.0.3", 20 | "eslint-plugin-import": "^2.26.0", 21 | "eslint-plugin-node": "^11.1.0", 22 | "eslint-plugin-prettier": "^3.4.1", 23 | "eslint-plugin-promise": "^5.2.0", 24 | "ethereum-waffle": "^3.4.4", 25 | "ethers": "^5.6.4", 26 | "hardhat": "^2.9.3", 27 | "hardhat-gas-reporter": "^1.0.8", 28 | "mocha": "^10.0.0", 29 | "prettier": "^2.6.2", 30 | "prettier-plugin-solidity": "^1.0.0-beta.13", 31 | "solhint": "^3.3.7", 32 | "solidity-coverage": "^0.7.21", 33 | "ts-node": "^10.7.0", 34 | "typechain": "^5.2.0", 35 | "typescript": "^4.6.4" 36 | }, 37 | "dependencies": { 38 | "@openzeppelin/contracts": "^4.6.0" 39 | }, 40 | "scripts": { 41 | "test": "node ./node_modules/mocha/bin/mocha" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/core/Marketplace.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../LZ/NonblockingLzApp.sol"; 6 | import "hardhat/console.sol"; 7 | import "../interfaces/ILayerZeroReceiver.sol"; 8 | 9 | contract TestMarketplace is NonblockingLzApp { 10 | 11 | uint public counter; 12 | ILayerZeroEndpoint public endpoint; 13 | 14 | 15 | // Endpoint.sol estimateFees() returns the fees for the message 16 | function estimateFees( 17 | uint16 _dstChainId, 18 | address _userApplication, 19 | bytes calldata _payload, 20 | bool _payInZRO, 21 | bytes calldata _adapterParams 22 | ) external view returns (uint256 nativeFee, uint256 zroFee) { 23 | return 24 | endpoint.estimateFees( 25 | _dstChainId, 26 | _userApplication, 27 | _payload, 28 | _payInZRO, 29 | _adapterParams 30 | ); 31 | } 32 | 33 | constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) { 34 | endpoint = ILayerZeroEndpoint(_lzEndpoint); 35 | } 36 | 37 | function _nonblockingLzReceive(uint16, bytes memory, uint64, bytes memory) internal override { 38 | counter += 1; 39 | } 40 | 41 | function incrementCounter(uint16 _dstChainId) public payable { 42 | _lzSend(_dstChainId, bytes("Hello"), payable(msg.sender), 0x025EF5a2d6AF68E229fC9A49681a64ce0787D520, bytes("")); 43 | } 44 | } -------------------------------------------------------------------------------- /contracts/interfaces/ILayerZeroUserApplicationConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface ILayerZeroUserApplicationConfig { 6 | // @notice set the configuration of the LayerZero messaging library of the specified version 7 | // @param _version - messaging library version 8 | // @param _chainId - the chainId for the pending config change 9 | // @param _configType - type of configuration. every messaging library has its own convention. 10 | // @param _config - configuration in the bytes. can encode arbitrary content. 11 | function setConfig(uint16 _version, uint16 _chainId, uint _configType, bytes calldata _config) external; 12 | 13 | // @notice set the send() LayerZero messaging library version to _version 14 | // @param _version - new messaging library version 15 | function setSendVersion(uint16 _version) external; 16 | 17 | // @notice set the lzReceive() LayerZero messaging library version to _version 18 | // @param _version - new messaging library version 19 | function setReceiveVersion(uint16 _version) external; 20 | 21 | // @notice Only when the UA needs to resume the message flow in blocking mode and clear the stored payload 22 | // @param _srcChainId - the chainId of the source chain 23 | // @param _srcAddress - the contract address of the source contract at the source chain 24 | function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external; 25 | } -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | 3 | import { HardhatUserConfig, task } from "hardhat/config"; 4 | import "@nomiclabs/hardhat-etherscan"; 5 | import "@nomiclabs/hardhat-waffle"; 6 | import "@typechain/hardhat"; 7 | import "hardhat-gas-reporter"; 8 | import "solidity-coverage"; 9 | 10 | dotenv.config(); 11 | 12 | // This is a sample Hardhat task. To learn how to create your own go to 13 | // https://hardhat.org/guides/create-task.html 14 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 15 | const accounts = await hre.ethers.getSigners(); 16 | 17 | for (const account of accounts) { 18 | console.log(account.address); 19 | } 20 | }); 21 | 22 | // You need to export an object to set up your config 23 | // Go to https://hardhat.org/config/ to learn more 24 | 25 | const config: HardhatUserConfig = { 26 | solidity: "0.8.4", 27 | networks: { 28 | fuji: { 29 | url: process.env.FUJI_URL || "", 30 | accounts: 31 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 32 | }, 33 | rinkeby: { 34 | url: process.env.RINKEBY_URL || "", 35 | accounts: 36 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 37 | gas: 30000000, 38 | gasPrice: 8000000000, 39 | // minGasPrice: 8000000000 40 | }, 41 | }, 42 | gasReporter: { 43 | enabled: process.env.REPORT_GAS !== undefined, 44 | currency: "USD", 45 | }, 46 | etherscan: { 47 | apiKey: process.env.ETHERSCAN_API_KEY, 48 | }, 49 | }; 50 | 51 | export default config; 52 | -------------------------------------------------------------------------------- /contracts/core/NFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../LZ/UniversalONFT721.sol"; 6 | import "../interfaces/ILayerZeroReceiver.sol"; 7 | 8 | /// @title A LayerZero UniversalONFT example 9 | /// @notice You can use this to mint ONFT and send nftIds across chain. 10 | /// Each contract deployed to a chain should carefully set a `_startMintIndex` and a `_maxMint` 11 | /// value to set a range of allowed mintable nftIds (so that no two chains can mint the same id!) 12 | contract TestNFT is UniversalONFT721 { 13 | 14 | uint public counter; 15 | ILayerZeroEndpoint public endpoint; 16 | 17 | function _nonblockingLzReceive(uint16, bytes memory, uint64, bytes memory) internal override { 18 | counter += 1; 19 | } 20 | 21 | function estimateFees( 22 | uint16 _dstChainId, 23 | address _userApplication, 24 | bytes calldata _payload, 25 | bool _payInZRO, 26 | bytes calldata _adapterParams 27 | ) external view returns (uint256 nativeFee, uint256 zroFee) { 28 | return 29 | endpoint.estimateFees( 30 | _dstChainId, 31 | _userApplication, 32 | _payload, 33 | _payInZRO, 34 | _adapterParams 35 | ); 36 | } 37 | 38 | constructor(string memory _name, string memory _symbol, address _layerZeroEndpoint, uint _startMintId, uint _endMintId) UniversalONFT721(_name,_symbol, _layerZeroEndpoint, _startMintId, _endMintId) { 39 | endpoint = ILayerZeroEndpoint(_layerZeroEndpoint); 40 | } 41 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Sample Hardhat Project 2 | 3 | This project demonstrates an advanced Hardhat use case, integrating other tools commonly used alongside Hardhat in the ecosystem. 4 | 5 | The project comes with a sample contract, a test for that contract, a sample script that deploys that contract, and an example of a task implementation, which simply lists the available accounts. It also comes with a variety of other tools, preconfigured to work with the project code. 6 | 7 | Try running some of the following tasks: 8 | 9 | ```shell 10 | npx hardhat accounts 11 | npx hardhat compile 12 | npx hardhat clean 13 | npx hardhat test 14 | npx hardhat node 15 | npx hardhat help 16 | REPORT_GAS=true npx hardhat test 17 | npx hardhat coverage 18 | npx hardhat run scripts/deploy.ts 19 | TS_NODE_FILES=true npx ts-node scripts/deploy.ts 20 | npx eslint '**/*.{js,ts}' 21 | npx eslint '**/*.{js,ts}' --fix 22 | npx prettier '**/*.{json,sol,md}' --check 23 | npx prettier '**/*.{json,sol,md}' --write 24 | npx solhint 'contracts/**/*.sol' 25 | npx solhint 'contracts/**/*.sol' --fix 26 | ``` 27 | 28 | # Contract Addresses 29 | 30 | NFT Contract Address on Fuji - 0x025EF5a2d6AF68E229fC9A49681a64ce0787D520 31 | Explorer - https://testnet.snowtrace.io/address/0x025EF5a2d6AF68E229fC9A49681a64ce0787D520 32 | 33 | 34 | Test Marketplace Contract Addesseses on Rinkebey - 0x6b49795FF24F2d23631d97E6fA235f9E2AF00911 35 | Explorer - https://rinkeby.etherscan.io/address/0x6b49795FF24F2d23631d97E6fA235f9E2AF00911 36 | 37 | 38 | 39 | LZ - Rinkby - 0x5a6ef98aDcf9dF1903956cCfA46a00Bff6628FA0 40 | LZ - Fuji - 0x819d5e4b469b428DC542b13a3319EDfFD58aC2E2 -------------------------------------------------------------------------------- /contracts/core/testsending.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | pragma abicoder v2; 4 | 5 | import "../interfaces/ILayerZeroEndpoint.sol"; 6 | import "../interfaces/ILayerZeroReceiver.sol"; 7 | import "hardhat/console.sol"; 8 | 9 | 10 | contract LayerZeroDemo1 is ILayerZeroReceiver { 11 | event ReceiveMsg( 12 | uint16 _srcChainId, 13 | address _from, 14 | uint16 _count, 15 | bytes _payload 16 | ); 17 | ILayerZeroEndpoint public endpoint; 18 | uint16 public messageCount; 19 | bytes public message; 20 | constructor(address _endpoint) { 21 | endpoint = ILayerZeroEndpoint(_endpoint); 22 | } 23 | function sendMsg( 24 | uint16 _dstChainId, 25 | bytes calldata _destination, 26 | bytes calldata payload 27 | ) public payable { 28 | endpoint.send{value: msg.value}( 29 | _dstChainId, 30 | _destination, 31 | payload, 32 | payable(msg.sender), 33 | address(this), 34 | bytes("") 35 | ); 36 | } 37 | function lzReceive( 38 | uint16 _srcChainId, 39 | bytes memory _from, 40 | uint64, 41 | bytes memory _payload 42 | ) external override { 43 | require(msg.sender == address(endpoint)); 44 | address from; 45 | assembly { 46 | from := mload(add(_from, 20)) 47 | } 48 | if ( 49 | keccak256(abi.encodePacked((_payload))) == 50 | keccak256(abi.encodePacked((bytes10("ff")))) 51 | ) { 52 | endpoint.receivePayload( 53 | 1, 54 | bytes(""), 55 | address(0x0), 56 | 1, 57 | 1, 58 | bytes("") 59 | ); 60 | } 61 | message = _payload; 62 | messageCount += 1; 63 | emit ReceiveMsg(_srcChainId, from, messageCount, message); 64 | } 65 | // Endpoint.sol estimateFees() returns the fees for the message 66 | function estimateFees( 67 | uint16 _dstChainId, 68 | address _userApplication, 69 | bytes calldata _payload, 70 | bool _payInZRO, 71 | bytes calldata _adapterParams 72 | ) external view returns (uint256 nativeFee, uint256 zroFee) { 73 | return 74 | endpoint.estimateFees( 75 | _dstChainId, 76 | _userApplication, 77 | _payload, 78 | _payInZRO, 79 | _adapterParams 80 | ); 81 | } 82 | } -------------------------------------------------------------------------------- /contracts/interfaces/IONFT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | /** 8 | * @dev Interface of the ONFT standard 9 | */ 10 | interface IONFT721 is IERC721 { 11 | /** 12 | * @dev estimate send token `_tokenId` to (`_dstChainId`, `_toAddress`) 13 | * _dstChainId - L0 defined chain id to send tokens too 14 | * _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 15 | * _tokenId - token Id to transfer 16 | * _useZro - indicates to use zro to pay L0 fees 17 | * _adapterParams - flexible bytes array to indicate messaging adapter services in L0 18 | */ 19 | function estimateSendFee(uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, bool _useZro, bytes calldata _adapterParams) external view returns (uint nativeFee, uint zroFee); 20 | 21 | /** 22 | * @dev send token `_tokenId` to (`_dstChainId`, `_toAddress`) 23 | * `_toAddress` can be any size depending on the `dstChainId`. 24 | * `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token) 25 | * `_adapterParams` is a flexible bytes array to indicate messaging adapter services 26 | */ 27 | function send(uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) external payable; 28 | 29 | /** 30 | * @dev send token `_tokenId` to (`_dstChainId`, `_toAddress`) from `_from` 31 | * `_toAddress` can be any size depending on the `dstChainId`. 32 | * `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token) 33 | * `_adapterParams` is a flexible bytes array to indicate messaging adapter services 34 | */ 35 | function sendFrom(address _from, uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) external payable; 36 | 37 | /** 38 | * @dev Emitted when `_tokenId` are moved from the `_sender` to (`_dstChainId`, `_toAddress`) 39 | * `_nonce` is the outbound nonce from 40 | */ 41 | event SendToChain(address indexed _sender, uint16 indexed _dstChainId, bytes indexed _toAddress, uint _tokenId, uint64 _nonce); 42 | 43 | /** 44 | * @dev Emitted when `_tokenId` are sent from `_srcChainId` to the `_toAddress` at this chain. `_nonce` is the inbound nonce. 45 | */ 46 | event ReceiveFromChain(uint16 _srcChainId, address _toAddress, uint _tokenId, uint64 _nonce); 47 | } -------------------------------------------------------------------------------- /contracts/LZ/NonblockingLzApp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./LzApp.sol"; 6 | 7 | /* 8 | * the default LayerZero messaging behaviour is blocking, i.e. any failed message will block the channel 9 | * this abstract class try-catch all fail messages and store locally for future retry. hence, non-blocking 10 | * NOTE: if the srcAddress is not configured properly, it will still block the message pathway from (srcChainId, srcAddress) 11 | */ 12 | abstract contract NonblockingLzApp is LzApp { 13 | constructor(address _endpoint) LzApp(_endpoint) {} 14 | 15 | mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) public failedMessages; 16 | 17 | event MessageFailed(uint16 _srcChainId, bytes _srcAddress, uint64 _nonce, bytes _payload); 18 | 19 | // overriding the virtual function in LzReceiver 20 | function _blockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual override { 21 | // try-catch all errors/exceptions 22 | try this.nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload) { 23 | // do nothing 24 | } catch { 25 | // error / exception 26 | failedMessages[_srcChainId][_srcAddress][_nonce] = keccak256(_payload); 27 | emit MessageFailed(_srcChainId, _srcAddress, _nonce, _payload); 28 | } 29 | } 30 | 31 | function nonblockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) public virtual { 32 | // only internal transaction 33 | require(_msgSender() == address(this), "LzReceiver: caller must be LzApp"); 34 | _nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); 35 | } 36 | 37 | //@notice override this function 38 | function _nonblockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual; 39 | 40 | function retryMessage(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes calldata _payload) public payable virtual { 41 | // assert there is message to retry 42 | bytes32 payloadHash = failedMessages[_srcChainId][_srcAddress][_nonce]; 43 | require(payloadHash != bytes32(0), "LzReceiver: no stored message"); 44 | require(keccak256(_payload) == payloadHash, "LzReceiver: invalid payload"); 45 | // clear the stored message 46 | failedMessages[_srcChainId][_srcAddress][_nonce] = bytes32(0); 47 | // execute the message. revert if it fails again 48 | _nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); 49 | } 50 | } -------------------------------------------------------------------------------- /contracts/LZ/LzApp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "../interfaces/ILayerZeroReceiver.sol"; 7 | import "../interfaces/ILayerZeroUserApplicationConfig.sol"; 8 | import "../interfaces/ILayerZeroEndpoint.sol"; 9 | 10 | /* 11 | * a generic LzReceiver implementation 12 | */ 13 | abstract contract LzApp is Ownable, ILayerZeroReceiver, ILayerZeroUserApplicationConfig { 14 | ILayerZeroEndpoint public immutable lzEndpoint; 15 | 16 | mapping(uint16 => bytes) public trustedRemoteLookup; 17 | 18 | event SetTrustedRemote(uint16 _srcChainId, bytes _srcAddress); 19 | 20 | constructor(address _endpoint) { 21 | lzEndpoint = ILayerZeroEndpoint(_endpoint); 22 | } 23 | 24 | function lzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) public virtual override { 25 | // lzReceive must be called by the endpoint for security 26 | require(_msgSender() == address(lzEndpoint)); 27 | 28 | bytes memory trustedRemote = trustedRemoteLookup[_srcChainId]; 29 | // if will still block the message pathway from (srcChainId, srcAddress). should not receive message from untrusted remote. 30 | require(_srcAddress.length == trustedRemote.length && keccak256(_srcAddress) == keccak256(trustedRemote), "LzReceiver: invalid source sending contract"); 31 | 32 | _blockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); 33 | } 34 | 35 | // abstract function - the default behaviour of LayerZero is blocking. See: NonblockingLzApp if you dont need to enforce ordered messaging 36 | function _blockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual; 37 | 38 | function _lzSend(uint16 _dstChainId, bytes memory _payload, address payable _refundAddress, address _zroPaymentAddress, bytes memory _adapterParams) internal virtual { 39 | bytes memory trustedRemote = trustedRemoteLookup[_dstChainId]; 40 | require(trustedRemote.length != 0, "LzSend: destination chain is not a trusted source."); 41 | lzEndpoint.send{value: msg.value}(_dstChainId, trustedRemote, _payload, _refundAddress, _zroPaymentAddress, _adapterParams); 42 | } 43 | 44 | //---------------------------UserApplication config---------------------------------------- 45 | function getConfig(uint16 _version, uint16 _chainId, address, uint _configType) external view returns (bytes memory) { 46 | return lzEndpoint.getConfig(_version, _chainId, address(this), _configType); 47 | } 48 | 49 | // generic config for LayerZero user Application 50 | function setConfig(uint16 _version, uint16 _chainId, uint _configType, bytes calldata _config) external override onlyOwner { 51 | lzEndpoint.setConfig(_version, _chainId, _configType, _config); 52 | } 53 | 54 | function setSendVersion(uint16 _version) external override onlyOwner { 55 | lzEndpoint.setSendVersion(_version); 56 | } 57 | 58 | function setReceiveVersion(uint16 _version) external override onlyOwner { 59 | lzEndpoint.setReceiveVersion(_version); 60 | } 61 | 62 | function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external override onlyOwner { 63 | lzEndpoint.forceResumeReceive(_srcChainId, _srcAddress); 64 | } 65 | 66 | // allow owner to set it multiple times. 67 | function setTrustedRemote(uint16 _srcChainId, bytes calldata _srcAddress) external onlyOwner { 68 | trustedRemoteLookup[_srcChainId] = _srcAddress; 69 | emit SetTrustedRemote(_srcChainId, _srcAddress); 70 | } 71 | 72 | //--------------------------- VIEW FUNCTION ---------------------------------------- 73 | 74 | function isTrustedRemote(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool) { 75 | bytes memory trustedSource = trustedRemoteLookup[_srcChainId]; 76 | return keccak256(trustedSource) == keccak256(_srcAddress); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /contracts/LZ/ONFT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../interfaces/IONFT721.sol"; 6 | import "./NonblockingLzApp.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 8 | 9 | // NOTE: this ONFT contract has no public minting logic. 10 | // must implement your own minting logic in child classes 11 | contract ONFT721 is IONFT721, NonblockingLzApp, ERC721 { 12 | 13 | constructor(string memory _name, string memory _symbol, address _lzEndpoint) ERC721(_name, _symbol) NonblockingLzApp(_lzEndpoint) {} 14 | 15 | function estimateSendFee(uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, bool _useZro, bytes calldata _adapterParams) public view virtual override returns (uint nativeFee, uint zroFee) { 16 | // mock the payload for send() 17 | bytes memory payload = abi.encode(_toAddress, _tokenId); 18 | return lzEndpoint.estimateFees(_dstChainId, address(this), payload, _useZro, _adapterParams); 19 | } 20 | 21 | function sendFrom(address _from, uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) public payable virtual override { 22 | _send(_from, _dstChainId, _toAddress, _tokenId, _refundAddress, _zroPaymentAddress, _adapterParams); 23 | } 24 | 25 | function send(uint16 _dstChainId, bytes calldata _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) public payable virtual override { 26 | _send(_msgSender(), _dstChainId, _toAddress, _tokenId, _refundAddress, _zroPaymentAddress, _adapterParams); 27 | } 28 | 29 | function _send(address _from, uint16 _dstChainId, bytes memory _toAddress, uint _tokenId, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) internal virtual { 30 | require(_isApprovedOrOwner(_msgSender(), _tokenId), "ONFT721: send caller is not owner nor approved"); 31 | require(ERC721.ownerOf(_tokenId) == _from, "ONFT721: send from incorrect owner"); 32 | _beforeSend(_from, _dstChainId, _toAddress, _tokenId); 33 | 34 | bytes memory payload = abi.encode(_toAddress, _tokenId); 35 | _lzSend(_dstChainId, payload, _refundAddress, _zroPaymentAddress, _adapterParams); 36 | 37 | uint64 nonce = lzEndpoint.getOutboundNonce(_dstChainId, address(this)); 38 | emit SendToChain(_from, _dstChainId, _toAddress, _tokenId, nonce); 39 | _afterSend(_from, _dstChainId, _toAddress, _tokenId); 40 | } 41 | 42 | function _nonblockingLzReceive(uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload) internal virtual override { 43 | _beforeReceive(_srcChainId, _srcAddress, _payload); 44 | 45 | // decode and load the toAddress 46 | (bytes memory toAddressBytes, uint tokenId) = abi.decode(_payload, (bytes, uint)); 47 | address toAddress; 48 | assembly { 49 | toAddress := mload(add(toAddressBytes, 20)) 50 | } 51 | 52 | _afterReceive(_srcChainId, toAddress, tokenId); 53 | 54 | emit ReceiveFromChain(_srcChainId, toAddress, tokenId, _nonce); 55 | } 56 | 57 | function _beforeSend( 58 | address, /* _from */ 59 | uint16, /* _dstChainId */ 60 | bytes memory, /* _toAddress */ 61 | uint _tokenId 62 | ) internal virtual { 63 | _burn(_tokenId); 64 | } 65 | 66 | function _afterSend( 67 | address, /* _from */ 68 | uint16, /* _dstChainId */ 69 | bytes memory, /* _toAddress */ 70 | uint /* _tokenId */ 71 | ) internal virtual {} 72 | 73 | function _beforeReceive( 74 | uint16, /* _srcChainId */ 75 | bytes memory, /* _srcAddress */ 76 | bytes memory /* _payload */ 77 | ) internal virtual {} 78 | 79 | function _afterReceive( 80 | uint16, /* _srcChainId */ 81 | address _toAddress, 82 | uint _tokenId 83 | ) internal virtual { 84 | _safeMint(_toAddress, _tokenId); 85 | } 86 | } -------------------------------------------------------------------------------- /contracts/interfaces/ILayerZeroEndpoint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./ILayerZeroUserApplicationConfig.sol"; 6 | 7 | interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig { 8 | // @notice send a LayerZero message to the specified address at a LayerZero endpoint. 9 | // @param _dstChainId - the destination chain identifier 10 | // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains 11 | // @param _payload - a custom bytes payload to send to the destination contract 12 | // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address 13 | // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction 14 | // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination 15 | function send(uint16 _dstChainId, bytes calldata _destination, bytes calldata _payload, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) external payable; 16 | 17 | // @notice used by the messaging library to publish verified payload 18 | // @param _srcChainId - the source chain identifier 19 | // @param _srcAddress - the source contract (as bytes) at the source chain 20 | // @param _dstAddress - the address on destination chain 21 | // @param _nonce - the unbound message ordering nonce 22 | // @param _gasLimit - the gas limit for external contract execution 23 | // @param _payload - verified payload to send to the destination contract 24 | function receivePayload(uint16 _srcChainId, bytes calldata _srcAddress, address _dstAddress, uint64 _nonce, uint _gasLimit, bytes calldata _payload) external; 25 | 26 | // @notice get the inboundNonce of a receiver from a source chain which could be EVM or non-EVM chain 27 | // @param _srcChainId - the source chain identifier 28 | // @param _srcAddress - the source chain contract address 29 | function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (uint64); 30 | 31 | // @notice get the outboundNonce from this source chain which, consequently, is always an EVM 32 | // @param _srcAddress - the source chain contract address 33 | function getOutboundNonce(uint16 _dstChainId, address _srcAddress) external view returns (uint64); 34 | 35 | // @notice gets a quote in source native gas, for the amount that send() requires to pay for message delivery 36 | // @param _dstChainId - the destination chain identifier 37 | // @param _userApplication - the user app address on this EVM chain 38 | // @param _payload - the custom message to send over LayerZero 39 | // @param _payInZRO - if false, user app pays the protocol fee in native token 40 | // @param _adapterParam - parameters for the adapter service, e.g. send some dust native token to dstChain 41 | function estimateFees(uint16 _dstChainId, address _userApplication, bytes calldata _payload, bool _payInZRO, bytes calldata _adapterParam) external view returns (uint nativeFee, uint zroFee); 42 | 43 | // @notice get this Endpoint's immutable source identifier 44 | function getChainId() external view returns (uint16); 45 | 46 | // @notice the interface to retry failed message on this Endpoint destination 47 | // @param _srcChainId - the source chain identifier 48 | // @param _srcAddress - the source chain contract address 49 | // @param _payload - the payload to be retried 50 | function retryPayload(uint16 _srcChainId, bytes calldata _srcAddress, bytes calldata _payload) external; 51 | 52 | // @notice query if any STORED payload (message blocking) at the endpoint. 53 | // @param _srcChainId - the source chain identifier 54 | // @param _srcAddress - the source chain contract address 55 | function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool); 56 | 57 | // @notice query if the _libraryAddress is valid for sending msgs. 58 | // @param _userApplication - the user app address on this EVM chain 59 | function getSendLibraryAddress(address _userApplication) external view returns (address); 60 | 61 | // @notice query if the _libraryAddress is valid for receiving msgs. 62 | // @param _userApplication - the user app address on this EVM chain 63 | function getReceiveLibraryAddress(address _userApplication) external view returns (address); 64 | 65 | // @notice query if the non-reentrancy guard for send() is on 66 | // @return true if the guard is on. false otherwise 67 | function isSendingPayload() external view returns (bool); 68 | 69 | // @notice query if the non-reentrancy guard for receive() is on 70 | // @return true if the guard is on. false otherwise 71 | function isReceivingPayload() external view returns (bool); 72 | 73 | // @notice get the configuration of the LayerZero messaging library of the specified version 74 | // @param _version - messaging library version 75 | // @param _chainId - the chainId for the pending config change 76 | // @param _userApplication - the contract address of the user application 77 | // @param _configType - type of configuration. every messaging library has its own convention. 78 | function getConfig(uint16 _version, uint16 _chainId, address _userApplication, uint _configType) external view returns (bytes memory); 79 | 80 | // @notice get the send() LayerZero messaging library version 81 | // @param _userApplication - the contract address of the user application 82 | function getSendVersion(address _userApplication) external view returns (uint16); 83 | 84 | // @notice get the lzReceive() LayerZero messaging library version 85 | // @param _userApplication - the contract address of the user application 86 | function getReceiveVersion(address _userApplication) external view returns (uint16); 87 | } --------------------------------------------------------------------------------