diff --git a/examples/onft721/.env.example b/examples/onft721/.env.example
new file mode 100644
index 000000000..197ba1d67
--- /dev/null
+++ b/examples/onft721/.env.example
@@ -0,0 +1,15 @@
+# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-
+# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \
+# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-'
+#
+# Example environment configuration
+#
+# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-
+# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \
+# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-'
+
+# By default, the examples support both mnemonic-based and private key-based authentication
+#
+# You don't need to set both of these values, just pick the one that you prefer and set that one
+MNEMONIC=
+PRIVATE_KEY=
\ No newline at end of file
diff --git a/examples/onft721/.eslintignore b/examples/onft721/.eslintignore
new file mode 100644
index 000000000..ee9f768fd
--- /dev/null
+++ b/examples/onft721/.eslintignore
@@ -0,0 +1,10 @@
+artifacts
+cache
+dist
+node_modules
+out
+*.log
+*.sol
+*.yaml
+*.lock
+package-lock.json
\ No newline at end of file
diff --git a/examples/onft721/.eslintrc.js b/examples/onft721/.eslintrc.js
new file mode 100644
index 000000000..f0ea891fd
--- /dev/null
+++ b/examples/onft721/.eslintrc.js
@@ -0,0 +1,10 @@
+require('@rushstack/eslint-patch/modern-module-resolution');
+
+module.exports = {
+ extends: ['@layerzerolabs/eslint-config-next/recommended'],
+ rules: {
+ // @layerzerolabs/eslint-config-next defines rules for turborepo-based projects
+ // that are not relevant for this particular project
+ 'turbo/no-undeclared-env-vars': 'off',
+ },
+};
diff --git a/examples/onft721/.gitignore b/examples/onft721/.gitignore
new file mode 100644
index 000000000..e2face954
--- /dev/null
+++ b/examples/onft721/.gitignore
@@ -0,0 +1,24 @@
+node_modules
+.env
+coverage
+coverage.json
+typechain
+typechain-types
+
+# Hardhat files
+cache
+artifacts
+
+
+# LayerZero specific files
+.layerzero
+
+# foundry test compilation files
+out
+
+# pnpm
+pnpm-error.log
+
+# Editor and OS files
+.DS_Store
+.idea
diff --git a/examples/onft721/.nvmrc b/examples/onft721/.nvmrc
new file mode 100644
index 000000000..b714151ef
--- /dev/null
+++ b/examples/onft721/.nvmrc
@@ -0,0 +1 @@
+v18.18.0
\ No newline at end of file
diff --git a/examples/onft721/.prettierignore b/examples/onft721/.prettierignore
new file mode 100644
index 000000000..6e8232f5a
--- /dev/null
+++ b/examples/onft721/.prettierignore
@@ -0,0 +1,10 @@
+artifacts/
+cache/
+dist/
+node_modules/
+out/
+*.log
+*ignore
+*.yaml
+*.lock
+package-lock.json
\ No newline at end of file
diff --git a/examples/onft721/.prettierrc.js b/examples/onft721/.prettierrc.js
new file mode 100644
index 000000000..6f55b4019
--- /dev/null
+++ b/examples/onft721/.prettierrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ...require('@layerzerolabs/prettier-config-next'),
+};
diff --git a/examples/onft721/README.md b/examples/onft721/README.md
new file mode 100644
index 000000000..e0c05c7a2
--- /dev/null
+++ b/examples/onft721/README.md
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+ Homepage | Docs | Developers
+
+
+ONFT721 Example
+
+
+ Quickstart | Configuration | Message Execution Options | Endpoint Addresses
+
+
+Template project for getting started with LayerZero's ONFT721
contract development.
+
+:warning: ** This code is currently under audit and should not yet be used in production. **
+
+## 1) Developing Contracts
+
+#### Installing dependencies
+
+We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice):
+
+```bash
+pnpm install
+```
+
+#### Compiling your contracts
+
+This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both:
+
+```bash
+pnpm compile
+```
+
+If you prefer one over the other, you can use the tooling-specific commands:
+
+```bash
+pnpm compile:forge
+pnpm compile:hardhat
+```
+
+Or adjust the `package.json` to for example remove `forge` build:
+
+```diff
+- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat",
+- "compile:forge": "forge build",
+- "compile:hardhat": "hardhat compile",
++ "compile": "hardhat compile"
+```
+
+#### Running tests
+
+Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both:
+
+```bash
+pnpm test
+```
+
+If you prefer one over the other, you can use the tooling-specific commands:
+
+```bash
+pnpm test:forge
+pnpm test:hardhat
+```
+
+Or adjust the `package.json` to for example remove `hardhat` tests:
+
+```diff
+- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat",
+- "test:forge": "forge test",
+- "test:hardhat": "$npm_execpath hardhat test"
++ "test": "forge test"
+```
+
+## 2) Deploying Contracts
+
+Set up deployer wallet/account:
+
+- Rename `.env.example` -> `.env`
+- Choose your preferred means of setting up your deployer wallet/account:
+
+```
+MNEMONIC="test test test test test test test test test test test junk"
+or...
+PRIVATE_KEY="0xabc...def"
+```
+
+- Fund this address with the corresponding chain's native tokens you want to deploy to.
+
+To deploy your contracts to your desired blockchains, run the following command in your project's folder:
+
+```bash
+npx hardhat lz:deploy
+```
+
+More information about available CLI arguments can be found using the `--help` flag:
+
+```bash
+npx hardhat lz:deploy --help
+```
+
+By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication.
+
+
+
+
+ Join our community on Discord | Follow us on Twitter
+
diff --git a/examples/onft721/contracts/MyONFT721.sol b/examples/onft721/contracts/MyONFT721.sol
new file mode 100644
index 000000000..64350c3c6
--- /dev/null
+++ b/examples/onft721/contracts/MyONFT721.sol
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { ONFT721 } from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721.sol";
+
+contract MyONFT721 is ONFT721 {
+ constructor(
+ string memory _name,
+ string memory _symbol,
+ address _lzEndpoint,
+ address _delegate
+ ) ONFT721(_name, _symbol, _lzEndpoint, _delegate) {}
+}
diff --git a/examples/onft721/contracts/mocks/MyONFT721Mock.sol b/examples/onft721/contracts/mocks/MyONFT721Mock.sol
new file mode 100644
index 000000000..5e7f83f39
--- /dev/null
+++ b/examples/onft721/contracts/mocks/MyONFT721Mock.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { MyONFT721 } from "../MyONFT721.sol";
+
+// @dev WARNING: This is for testing purposes only
+contract MyONFT721Mock is MyONFT721 {
+ constructor(
+ string memory _name,
+ string memory _symbol,
+ address _lzEndpoint,
+ address _delegate
+ ) MyONFT721(_name, _symbol, _lzEndpoint, _delegate) {}
+
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+}
diff --git a/examples/onft721/deploy/MyONFT721.ts b/examples/onft721/deploy/MyONFT721.ts
new file mode 100644
index 000000000..8e80c4942
--- /dev/null
+++ b/examples/onft721/deploy/MyONFT721.ts
@@ -0,0 +1,53 @@
+import assert from 'assert'
+
+import { type DeployFunction } from 'hardhat-deploy/types'
+
+const contractName = 'MyONFT721'
+
+const deploy: DeployFunction = async (hre) => {
+ const { getNamedAccounts, deployments } = hre
+
+ const { deploy } = deployments
+ const { deployer } = await getNamedAccounts()
+
+ assert(deployer, 'Missing named deployer account')
+
+ console.log(`Network: ${hre.network.name}`)
+ console.log(`Deployer: ${deployer}`)
+
+ // This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2
+ //
+ // @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments
+ // from @layerzerolabs packages based on the configuration in your hardhat config
+ //
+ // For this to work correctly, your network config must define an eid property
+ // set to `EndpointId` as defined in @layerzerolabs/lz-definitions
+ //
+ // For example:
+ //
+ // networks: {
+ // fuji: {
+ // ...
+ // eid: EndpointId.AVALANCHE_V2_TESTNET
+ // }
+ // }
+ const endpointV2Deployment = await hre.deployments.get('EndpointV2')
+
+ const { address } = await deploy(contractName, {
+ from: deployer,
+ args: [
+ 'MyONFT721', // name
+ 'ONFT', // symbol
+ endpointV2Deployment.address, // LayerZero's EndpointV2 address
+ deployer, // owner
+ ],
+ log: true,
+ skipIfAlreadyDeployed: false,
+ })
+
+ console.log(`Deployed contract: ${contractName}, network: ${hre.network.name}, address: ${address}`)
+}
+
+deploy.tags = [contractName]
+
+export default deploy
diff --git a/examples/onft721/foundry.toml b/examples/onft721/foundry.toml
new file mode 100644
index 000000000..7608c64b3
--- /dev/null
+++ b/examples/onft721/foundry.toml
@@ -0,0 +1,27 @@
+[profile.default]
+solc-version = '0.8.22'
+src = 'contracts'
+out = 'out'
+test = 'test/foundry'
+cache_path = 'cache'
+libs = [
+ # We provide a set of useful contract utilities
+ # in the lib directory of @layerzerolabs/toolbox-foundry:
+ #
+ # - forge-std
+ # - ds-test
+ # - solidity-bytes-utils
+ 'node_modules/@layerzerolabs/toolbox-foundry/lib',
+ 'node_modules',
+]
+
+remappings = [
+ # Due to a misconfiguration of solidity-bytes-utils, an outdated version
+ # of forge-std is being dragged in
+ #
+ # To remedy this, we'll remap the ds-test and forge-std imports to ou own versions
+ 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test',
+ 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std',
+ '@layerzerolabs/=node_modules/@layerzerolabs/',
+ '@openzeppelin/=node_modules/@openzeppelin/',
+]
diff --git a/examples/onft721/hardhat.config.ts b/examples/onft721/hardhat.config.ts
new file mode 100644
index 000000000..be20edcdf
--- /dev/null
+++ b/examples/onft721/hardhat.config.ts
@@ -0,0 +1,75 @@
+// Get the environment configuration from .env file
+//
+// To make use of automatic environment setup:
+// - Duplicate .env.example file and name it .env
+// - Fill in the environment variables
+import 'dotenv/config'
+
+import 'hardhat-deploy'
+import 'hardhat-contract-sizer'
+import '@nomiclabs/hardhat-ethers'
+import '@layerzerolabs/toolbox-hardhat'
+import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types'
+
+import { EndpointId } from '@layerzerolabs/lz-definitions'
+
+// Set your preferred authentication method
+//
+// If you prefer using a mnemonic, set a MNEMONIC environment variable
+// to a valid mnemonic
+const MNEMONIC = process.env.MNEMONIC
+
+// If you prefer to be authenticated using a private key, set a PRIVATE_KEY environment variable
+const PRIVATE_KEY = process.env.PRIVATE_KEY
+
+const accounts: HttpNetworkAccountsUserConfig | undefined = MNEMONIC
+ ? { mnemonic: MNEMONIC }
+ : PRIVATE_KEY
+ ? [PRIVATE_KEY]
+ : undefined
+
+if (accounts == null) {
+ console.warn(
+ 'Could not find MNEMONIC or PRIVATE_KEY environment variables. It will not be possible to execute transactions in your example.'
+ )
+}
+
+const config: HardhatUserConfig = {
+ solidity: {
+ compilers: [
+ {
+ version: '0.8.22',
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ ],
+ },
+ networks: {
+ sepolia: {
+ eid: EndpointId.SEPOLIA_V2_TESTNET,
+ url: process.env.RPC_URL_SEPOLIA || 'https://rpc.sepolia.org/',
+ accounts,
+ },
+ fuji: {
+ eid: EndpointId.AVALANCHE_V2_TESTNET,
+ url: process.env.RPC_URL_FUJI || 'https://rpc.ankr.com/avalanche_fuji',
+ accounts,
+ },
+ amoy: {
+ eid: EndpointId.AMOY_V2_TESTNET,
+ url: process.env.RPC_URL_AMOY || 'https://polygon-amoy-bor-rpc.publicnode.com',
+ accounts,
+ },
+ },
+ namedAccounts: {
+ deployer: {
+ default: 0, // wallet address of index[0], of the mnemonic in .env
+ },
+ },
+}
+
+export default config
diff --git a/examples/onft721/layerzero.config.ts b/examples/onft721/layerzero.config.ts
new file mode 100644
index 000000000..0212a343a
--- /dev/null
+++ b/examples/onft721/layerzero.config.ts
@@ -0,0 +1,85 @@
+import { EndpointId } from '@layerzerolabs/lz-definitions'
+import { ExecutorOptionType } from '@layerzerolabs/lz-v2-utilities'
+
+import type { OAppEdgeConfig, OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat'
+
+const sepoliaContract: OmniPointHardhat = {
+ eid: EndpointId.SEPOLIA_V2_TESTNET,
+ contractName: 'MyONFT721',
+}
+
+const fujiContract: OmniPointHardhat = {
+ eid: EndpointId.AVALANCHE_V2_TESTNET,
+ contractName: 'MyONFT721',
+}
+
+const amoyContract: OmniPointHardhat = {
+ eid: EndpointId.AMOY_V2_TESTNET,
+ contractName: 'MyONFT721',
+}
+
+const DEFAULT_EDGE_CONFIG: OAppEdgeConfig = {
+ enforcedOptions: [
+ {
+ msgType: 1,
+ optionType: ExecutorOptionType.LZ_RECEIVE,
+ gas: 100_000,
+ value: 0,
+ },
+ {
+ msgType: 2,
+ optionType: ExecutorOptionType.COMPOSE,
+ index: 0,
+ gas: 100_000,
+ value: 0,
+ },
+ ],
+}
+
+const config: OAppOmniGraphHardhat = {
+ contracts: [
+ {
+ contract: fujiContract,
+ },
+ {
+ contract: sepoliaContract,
+ },
+ {
+ contract: amoyContract,
+ },
+ ],
+ connections: [
+ {
+ from: fujiContract,
+ to: sepoliaContract,
+ config: DEFAULT_EDGE_CONFIG,
+ },
+ {
+ from: fujiContract,
+ to: amoyContract,
+ config: DEFAULT_EDGE_CONFIG,
+ },
+ {
+ from: sepoliaContract,
+ to: fujiContract,
+ config: DEFAULT_EDGE_CONFIG,
+ },
+ {
+ from: sepoliaContract,
+ to: amoyContract,
+ config: DEFAULT_EDGE_CONFIG,
+ },
+ {
+ from: amoyContract,
+ to: sepoliaContract,
+ config: DEFAULT_EDGE_CONFIG,
+ },
+ {
+ from: amoyContract,
+ to: fujiContract,
+ config: DEFAULT_EDGE_CONFIG,
+ },
+ ],
+}
+
+export default config
diff --git a/examples/onft721/package.json b/examples/onft721/package.json
new file mode 100644
index 000000000..30885bbbc
--- /dev/null
+++ b/examples/onft721/package.json
@@ -0,0 +1,75 @@
+{
+ "name": "@layerzerolabs/onft721-example",
+ "version": "0.0.1",
+ "license": "MIT",
+ "scripts": {
+ "clean": "rm -rf artifacts cache out",
+ "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat",
+ "compile:forge": "forge build",
+ "compile:hardhat": "hardhat compile",
+ "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol",
+ "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt",
+ "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .",
+ "lint:sol": "solhint 'contracts/**/*.sol'",
+ "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat",
+ "test:forge": "forge test",
+ "test:hardhat": "hardhat test"
+ },
+ "resolutions": {
+ "@nomicfoundation/edr": "0.3.5",
+ "ethers": "^5.7.2",
+ "hardhat-deploy": "^0.12.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.23.9",
+ "@layerzerolabs/eslint-config-next": "~2.3.3",
+ "@layerzerolabs/lz-definitions": "^2.3.25",
+ "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.25",
+ "@layerzerolabs/lz-evm-oapp-v2": "^2.3.25",
+ "@layerzerolabs/lz-evm-protocol-v2": "^2.3.25",
+ "@layerzerolabs/lz-evm-v1-0.7": "^2.3.25",
+ "@layerzerolabs/lz-v2-utilities": "^2.3.25",
+ "@layerzerolabs/onft-evm": "0.0.1",
+ "@layerzerolabs/prettier-config-next": "^2.3.25",
+ "@layerzerolabs/solhint-config": "^2.3.3",
+ "@layerzerolabs/test-devtools-evm-foundry": "~0.2.7",
+ "@layerzerolabs/toolbox-foundry": "~0.1.7",
+ "@layerzerolabs/toolbox-hardhat": "~0.2.35",
+ "@nomicfoundation/hardhat-ethers": "^3.0.5",
+ "@nomiclabs/hardhat-ethers": "^2.2.3",
+ "@openzeppelin/contracts": "^5.0.1",
+ "@openzeppelin/contracts-upgradeable": "^5.0.1",
+ "@rushstack/eslint-patch": "^1.7.0",
+ "@types/chai": "^4.3.11",
+ "@types/mocha": "^10.0.6",
+ "@types/node": "~18.18.14",
+ "chai": "^4.4.1",
+ "dotenv": "^16.4.1",
+ "eslint-plugin-jest-extended": "~2.0.0",
+ "ethers": "^5.7.2",
+ "hardhat": "^2.22.3",
+ "hardhat-contract-sizer": "^2.10.0",
+ "hardhat-deploy": "^0.12.1",
+ "mocha": "^10.2.0",
+ "prettier": "^3.2.5",
+ "solhint": "^4.1.1",
+ "solidity-bytes-utils": "^0.8.2",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.3.3"
+ },
+ "engines": {
+ "node": ">=18.16.0"
+ },
+ "pnpm": {
+ "overrides": {
+ "@nomicfoundation/edr": "0.3.5",
+ "ethers": "^5.7.2",
+ "hardhat-deploy": "^0.12.1"
+ }
+ },
+ "overrides": {
+ "@nomicfoundation/edr": "0.3.5",
+ "ethers": "^5.7.2",
+ "hardhat-deploy": "^0.12.1"
+ }
+}
diff --git a/examples/onft721/solhint.config.js b/examples/onft721/solhint.config.js
new file mode 100644
index 000000000..52efe629c
--- /dev/null
+++ b/examples/onft721/solhint.config.js
@@ -0,0 +1 @@
+module.exports = require('@layerzerolabs/solhint-config');
diff --git a/examples/onft721/tsconfig.json b/examples/onft721/tsconfig.json
new file mode 100644
index 000000000..027ad0f3f
--- /dev/null
+++ b/examples/onft721/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "exclude": ["node_modules"],
+ "include": ["deploy", "tasks", "test", "hardhat.config.ts"],
+ "compilerOptions": {
+ "target": "es2020",
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ }
+}
diff --git a/examples/onft721/turbo.json b/examples/onft721/turbo.json
new file mode 100644
index 000000000..d3c81288a
--- /dev/null
+++ b/examples/onft721/turbo.json
@@ -0,0 +1,9 @@
+{
+ "extends": ["//"],
+ "pipeline": {
+ "compile": {
+ "dependsOn": ["@layerzerolabs/oft-example#compile"],
+ "description": "We include this dependency to make sure that the compilation is executed in series to avoid race conditions with solc compilers in foundry"
+ }
+ }
+}
diff --git a/packages/create-lz-oapp/src/config.ts b/packages/create-lz-oapp/src/config.ts
index 3aff23588..dd67e439f 100644
--- a/packages/create-lz-oapp/src/config.ts
+++ b/packages/create-lz-oapp/src/config.ts
@@ -31,6 +31,13 @@ export const EXAMPLES: Example[] = [
directory: 'examples/oapp',
ref,
},
+ {
+ id: 'onft721',
+ label: 'ONFT721',
+ repository,
+ directory: 'examples/onft721',
+ ref,
+ },
]
export const PACKAGE_MANAGERS: PackageManager[] = [
diff --git a/packages/onft-evm/.gitignore b/packages/onft-evm/.gitignore
new file mode 100644
index 000000000..62efafcc3
--- /dev/null
+++ b/packages/onft-evm/.gitignore
@@ -0,0 +1 @@
+out/**/*
diff --git a/packages/onft-evm/README.md b/packages/onft-evm/README.md
new file mode 100644
index 000000000..3bcdfbcea
--- /dev/null
+++ b/packages/onft-evm/README.md
@@ -0,0 +1,3 @@
+# ONFT721
+
+:warning: ** This code is currently under audit and should not yet be used in production. **
diff --git a/packages/onft-evm/contracts/libs/ONFTComposeMsgCodec.sol b/packages/onft-evm/contracts/libs/ONFTComposeMsgCodec.sol
new file mode 100644
index 000000000..924594c2e
--- /dev/null
+++ b/packages/onft-evm/contracts/libs/ONFTComposeMsgCodec.sol
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.22;
+
+/**
+ * @title ONFT Composed Message Codec
+ * @notice Library for encoding and decoding ONFT composed messages.
+ */
+library ONFTComposeMsgCodec {
+ // Offset constants for decoding composed messages
+ uint8 private constant NONCE_OFFSET = 8;
+ uint8 private constant SRC_EID_OFFSET = 12;
+ uint8 private constant COMPOSE_FROM_OFFSET = 44;
+
+ /**
+ * @dev Encodes a ONFT721 composed message.
+ * @param _nonce The nonce value.
+ * @param _srcEid The source LayerZero endpoint ID.
+ * @param _composeMsg The composed message.
+ * @return The encoded payload, including the composed message.
+ */
+ function encode(
+ uint64 _nonce,
+ uint32 _srcEid,
+ bytes memory _composeMsg // 0x[composeFrom][composeMsg]
+ ) internal pure returns (bytes memory) {
+ return abi.encodePacked(_nonce, _srcEid, _composeMsg);
+ }
+
+ /**
+ * @dev Retrieves the nonce from the composed message.
+ * @param _msg The message.
+ * @return The nonce value.
+ */
+ function nonce(bytes calldata _msg) internal pure returns (uint64) {
+ return uint64(bytes8(_msg[:NONCE_OFFSET]));
+ }
+
+ /**
+ * @dev Retrieves the source LayerZero endpoint ID from the composed message.
+ * @param _msg The message.
+ * @return The source LayerZero endpoint ID.
+ */
+ function srcEid(bytes calldata _msg) internal pure returns (uint32) {
+ return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));
+ }
+
+ /**
+ * @dev Retrieves the composeFrom value from the composed message.
+ * @param _msg The message.
+ * @return The composeFrom value as bytes32.
+ */
+ function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {
+ return bytes32(_msg[SRC_EID_OFFSET:COMPOSE_FROM_OFFSET]);
+ }
+
+ /**
+ * @dev Retrieves the composed message.
+ * @param _msg The message.
+ * @return The composed message.
+ */
+ function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
+ return _msg[COMPOSE_FROM_OFFSET:];
+ }
+
+ /**
+ * @dev Converts an address to bytes32.
+ * @param _addr The address to convert.
+ * @return The bytes32 representation of the address.
+ */
+ function addressToBytes32(address _addr) internal pure returns (bytes32) {
+ return bytes32(uint256(uint160(_addr)));
+ }
+
+ /**
+ * @dev Converts bytes32 to an address.
+ * @param _b The bytes32 value to convert.
+ * @return The address representation of bytes32.
+ */
+ function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
+ return address(uint160(uint256(_b)));
+ }
+}
diff --git a/packages/onft-evm/contracts/onft721/ONFT721.sol b/packages/onft-evm/contracts/onft721/ONFT721.sol
new file mode 100644
index 000000000..93307f257
--- /dev/null
+++ b/packages/onft-evm/contracts/onft721/ONFT721.sol
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.22;
+
+import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+
+import { ONFT721Core } from "./ONFT721Core.sol";
+
+/**
+ * @title ONFT721 Contract
+ * @dev ONFT721 is an ERC-721 token that extends the functionality of the ONFT721Core contract.
+ */
+abstract contract ONFT721 is ONFT721Core, ERC721 {
+ string internal baseTokenURI;
+
+ /**
+ * @dev Constructor for the ONFT721 contract.
+ * @param _name The name of the ONFT.
+ * @param _symbol The symbol of the ONFT.
+ * @param _lzEndpoint The LayerZero endpoint address.
+ * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
+ */
+ constructor(
+ string memory _name,
+ string memory _symbol,
+ address _lzEndpoint,
+ address _delegate
+ ) ERC721(_name, _symbol) ONFT721Core(_lzEndpoint, _delegate) {}
+
+ /**
+ * @notice Retrieves the address of the underlying ERC721 implementation (ie. this contract).
+ */
+ function token() external view returns (address) {
+ return address(this);
+ }
+
+ function setBaseURI(string memory _baseTokenURI) external onlyOwner {
+ baseTokenURI = _baseTokenURI;
+ }
+
+ function _baseURI() internal view override returns (string memory) {
+ return baseTokenURI;
+ }
+
+ /**
+ * @notice Indicates whether the ONFT721 contract requires approval of the 'token()' to send.
+ * @dev In the case of ONFT where the contract IS the token, approval is NOT required.
+ * @return requiresApproval Needs approval of the underlying token implementation.
+ */
+ function approvalRequired() external pure virtual returns (bool) {
+ return false;
+ }
+
+ function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override {
+ if (_from != ERC721.ownerOf(_tokenId)) revert OnlyNFTOwner(_from, ERC721.ownerOf(_tokenId));
+ _burn(_tokenId);
+ }
+
+ function _credit(address _to, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override {
+ _mint(_to, _tokenId);
+ }
+}
diff --git a/packages/onft-evm/contracts/onft721/ONFT721Adapter.sol b/packages/onft-evm/contracts/onft721/ONFT721Adapter.sol
new file mode 100644
index 000000000..b030d9f60
--- /dev/null
+++ b/packages/onft-evm/contracts/onft721/ONFT721Adapter.sol
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.22;
+
+import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
+
+import { ONFT721Core } from "./ONFT721Core.sol";
+
+/**
+ * @title ONFT721Adapter Contract
+ * @dev ONFT721Adapter is a wrapper used to enable cross-chain transferring of an existing ERC721 token.
+ */
+abstract contract ONFT721Adapter is ONFT721Core {
+ IERC721 internal immutable innerToken;
+
+ /**
+ * @dev Constructor for the ONFT721 contract.
+ * @param _token The underlying ERC721 token address this adapts
+ * @param _lzEndpoint The LayerZero endpoint address.
+ * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
+ */
+ constructor(address _token, address _lzEndpoint, address _delegate) ONFT721Core(_lzEndpoint, _delegate) {
+ innerToken = IERC721(_token);
+ }
+
+ /**
+ * @notice Retrieves the address of the underlying ERC721 implementation (ie. external contract).
+ */
+ function token() external view returns (address) {
+ return address(innerToken);
+ }
+
+ /**
+ * @notice Indicates whether the ONFT721 contract requires approval of the 'token()' to send.
+ * @dev In the case of ONFT where the contract IS the token, approval is NOT required.
+ * @return requiresApproval Needs approval of the underlying token implementation.
+ */
+ function approvalRequired() external pure virtual returns (bool) {
+ return true;
+ }
+
+ function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override {
+ // @dev Dont need to check onERC721Received() when moving into this contract, ie. no 'safeTransferFrom' required
+ innerToken.transferFrom(_from, address(this), _tokenId);
+ }
+
+ function _credit(address _toAddress, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override {
+ // @dev Do not need to check onERC721Received() when moving out of this contract, ie. no 'safeTransferFrom'
+ // required
+ // @dev The default implementation does not implement IERC721Receiver as 'safeTransferFrom' is not used.
+ // @dev If IERC721Receiver is required, ensure proper re-entrancy protection is implemented.
+ innerToken.transferFrom(address(this), _toAddress, _tokenId);
+ }
+}
diff --git a/packages/onft-evm/contracts/onft721/ONFT721Core.sol b/packages/onft-evm/contracts/onft721/ONFT721Core.sol
new file mode 100644
index 000000000..3dcceb81d
--- /dev/null
+++ b/packages/onft-evm/contracts/onft721/ONFT721Core.sol
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.22;
+
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+
+import { OApp, Origin } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
+import { OAppOptionsType3 } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol";
+import { IOAppMsgInspector } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppMsgInspector.sol";
+import { OAppPreCrimeSimulator } from "@layerzerolabs/lz-evm-oapp-v2/contracts/precrime/OAppPreCrimeSimulator.sol";
+
+import { IONFT721, MessagingFee, MessagingReceipt, SendParam } from "./interfaces/IONFT721.sol";
+import { ONFT721MsgCodec } from "./libs/ONFT721MsgCodec.sol";
+import { ONFTComposeMsgCodec } from "../libs/ONFTComposeMsgCodec.sol";
+
+/**
+ * @title ONFT721Core
+ * @dev Abstract contract for an ONFT721 token.
+ */
+abstract contract ONFT721Core is IONFT721, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {
+ using ONFT721MsgCodec for bytes;
+ using ONFT721MsgCodec for bytes32;
+
+ // @notice Msg types that are used to identify the various OFT operations.
+ // @dev This can be extended in child contracts for non-default oft operations
+ // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.
+ uint16 public constant SEND = 1;
+ uint16 public constant SEND_AND_COMPOSE = 2;
+
+ // Address of an optional contract to inspect both 'message' and 'options'
+ address public msgInspector;
+
+ event MsgInspectorSet(address inspector);
+
+ /**
+ * @dev Constructor.
+ * @param _lzEndpoint The address of the LayerZero endpoint.
+ * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
+ */
+ constructor(address _lzEndpoint, address _delegate) Ownable(_delegate) OApp(_lzEndpoint, _delegate) {}
+
+ /**
+ * @notice Retrieves interfaceID and the version of the ONFT.
+ * @return interfaceId The interface ID (0x23e18da6).
+ * @return version The version.
+ * @dev version: Indicates a cross-chain compatible msg encoding with other ONFTs.
+ * @dev If a new feature is added to the ONFT cross-chain msg encoding, the version will be incremented.
+ * @dev ie. localONFT version(x,1) CAN send messages to remoteONFT version(x,1)
+ */
+ function onftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {
+ return (type(IONFT721).interfaceId, 1);
+ }
+
+ /**
+ * @notice Sets the message inspector address for the OFT.
+ * @param _msgInspector The address of the message inspector.
+ * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.
+ * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.
+ */
+ function setMsgInspector(address _msgInspector) public virtual onlyOwner {
+ msgInspector = _msgInspector;
+ emit MsgInspectorSet(_msgInspector);
+ }
+
+ function quoteSend(
+ SendParam calldata _sendParam,
+ bool _payInLzToken
+ ) external view virtual returns (MessagingFee memory msgFee) {
+ (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam);
+ return _quote(_sendParam.dstEid, message, options, _payInLzToken);
+ }
+
+ function send(
+ SendParam calldata _sendParam,
+ MessagingFee calldata _fee,
+ address _refundAddress
+ ) external payable virtual returns (MessagingReceipt memory msgReceipt) {
+ _debit(msg.sender, _sendParam.tokenId, _sendParam.dstEid);
+
+ (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam);
+
+ // @dev Sends the message to the LayerZero Endpoint, returning the MessagingReceipt.
+ msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);
+ emit ONFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, _sendParam.tokenId);
+ }
+
+ /**
+ * @dev Internal function to build the message and options.
+ * @param _sendParam The parameters for the send() operation.
+ * @return message The encoded message.
+ * @return options The encoded options.
+ */
+ function _buildMsgAndOptions(
+ SendParam calldata _sendParam
+ ) internal view virtual returns (bytes memory message, bytes memory options) {
+ if (_sendParam.to == bytes32(0)) revert InvalidReceiver();
+ bool hasCompose;
+ (message, hasCompose) = ONFT721MsgCodec.encode(_sendParam.to, _sendParam.tokenId, _sendParam.composeMsg);
+ uint16 msgType = hasCompose ? SEND_AND_COMPOSE : SEND;
+
+ options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);
+
+ // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.
+ // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean
+ if (msgInspector != address(0)) IOAppMsgInspector(msgInspector).inspect(message, options);
+ }
+
+ /**
+ * @dev Internal function to handle the receive on the LayerZero endpoint.
+ * @param _origin The origin information.
+ * - srcEid: The source chain endpoint ID.
+ * - sender: The sender address from the src chain.
+ * - nonce: The nonce of the LayerZero message.
+ * @param _guid The unique identifier for the received LayerZero message.
+ * @param _message The encoded message.
+ * @dev _executor The address of the executor.
+ * @dev _extraData Additional data.
+ */
+ function _lzReceive(
+ Origin calldata _origin,
+ bytes32 _guid,
+ bytes calldata _message,
+ address /*_executor*/, // @dev unused in the default implementation.
+ bytes calldata /*_extraData*/ // @dev unused in the default implementation.
+ ) internal virtual override {
+ address toAddress = _message.sendTo().bytes32ToAddress();
+ uint256 tokenId = _message.tokenId();
+
+ _credit(toAddress, tokenId, _origin.srcEid);
+
+ if (_message.isComposed()) {
+ bytes memory composeMsg = ONFTComposeMsgCodec.encode(_origin.nonce, _origin.srcEid, _message.composeMsg());
+ // @dev As batching is not implemented, the compose index is always 0.
+ // @dev If batching is added, the index will need to be tracked.
+ endpoint.sendCompose(toAddress, _guid, 0 /* the index of composed message*/, composeMsg);
+ }
+
+ emit ONFTReceived(_guid, _origin.srcEid, toAddress, tokenId);
+ }
+
+ /*
+ * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.
+ * @param _origin The origin information.
+ * - srcEid: The source chain endpoint ID.
+ * - sender: The sender address from the src chain.
+ * - nonce: The nonce of the LayerZero message.
+ * @param _guid The unique identifier for the received LayerZero message.
+ * @param _message The LayerZero message.
+ * @param _executor The address of the off-chain executor.
+ * @param _extraData Arbitrary data passed by the msg executor.
+ * @dev Enables the preCrime simulator to mock sending lzReceive() messages,
+ * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.
+ */
+ function _lzReceiveSimulate(
+ Origin calldata _origin,
+ bytes32 _guid,
+ bytes calldata _message,
+ address _executor,
+ bytes calldata _extraData
+ ) internal virtual override {
+ _lzReceive(_origin, _guid, _message, _executor, _extraData);
+ }
+
+ /**
+ * @dev Check if the peer is considered 'trusted' by the OApp.
+ * @param _eid The endpoint ID to check.
+ * @param _peer The peer to check.
+ * @return Whether the peer passed is considered 'trusted' by the OApp.
+ * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.
+ */
+ function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {
+ return peers[_eid] == _peer;
+ }
+
+ function _debit(address /*_from*/, uint256 /*_tokenId*/, uint32 /*_dstEid*/) internal virtual;
+
+ function _credit(address /*_to*/, uint256 /*_tokenId*/, uint32 /*_srcEid*/) internal virtual;
+}
diff --git a/packages/onft-evm/contracts/onft721/interfaces/IONFT721.sol b/packages/onft-evm/contracts/onft721/interfaces/IONFT721.sol
new file mode 100644
index 000000000..50130e551
--- /dev/null
+++ b/packages/onft-evm/contracts/onft721/interfaces/IONFT721.sol
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.22;
+
+import { MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OAppSender.sol";
+
+/**
+ * @dev Struct representing token parameters for the ONFT send() operation.
+ */
+struct SendParam {
+ uint32 dstEid; // Destination LayerZero EndpointV2 ID.
+ bytes32 to; // Recipient address.
+ uint256 tokenId;
+ bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
+ bytes composeMsg; // The composed message for the send() operation.
+ bytes onftCmd; // The ONFT command to be executed, unused in default ONFT implementations.
+}
+
+/**
+ * @title IONFT
+ * @dev Interface for the ONFT721 token.
+ * @dev Does not inherit ERC721 to accommodate usage by OFT721Adapter.
+ */
+interface IONFT721 {
+ // Custom error messages
+ error InvalidReceiver();
+ error OnlyNFTOwner(address caller, address owner);
+
+ // Events
+ event ONFTSent(
+ bytes32 indexed guid, // GUID of the ONFT message.
+ uint32 dstEid, // Destination Endpoint ID.
+ address indexed fromAddress, // Address of the sender on the src chain.
+ uint256 tokenId // ONFT ID sent.
+ );
+
+ event ONFTReceived(
+ bytes32 indexed guid, // GUID of the ONFT message.
+ uint32 srcEid, // Source Endpoint ID.
+ address indexed toAddress, // Address of the recipient on the dst chain.
+ uint256 tokenId // ONFT ID received.
+ );
+
+ /**
+ * @notice Retrieves interfaceID and the version of the ONFT.
+ * @return interfaceId The interface ID.
+ * @return version The version.
+ * @dev interfaceId: This specific interface ID is '0x94642228'.
+ * @dev version: Indicates a cross-chain compatible msg encoding with other ONFTs.
+ * @dev If a new feature is added to the ONFT cross-chain msg encoding, the version will be incremented.
+ * ie. localONFT version(x,1) CAN send messages to remoteONFT version(x,1)
+ */
+ function onftVersion() external view returns (bytes4 interfaceId, uint64 version);
+
+ /**
+ * @notice Retrieves the address of the token associated with the ONFT.
+ * @return token The address of the ERC721 token implementation.
+ */
+ function token() external view returns (address);
+
+ /**
+ * @notice Indicates whether the ONFT contract requires approval of the 'token()' to send.
+ * @return requiresApproval Needs approval of the underlying token implementation.
+ * @dev Allows things like wallet implementers to determine integration requirements,
+ * without understanding the underlying token implementation.
+ */
+ function approvalRequired() external view returns (bool);
+
+ /**
+ * @notice Provides a quote for the send() operation.
+ * @param _sendParam The parameters for the send() operation.
+ * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
+ * @return fee The calculated LayerZero messaging fee from the send() operation.
+ * @dev MessagingFee: LayerZero msg fee
+ * - nativeFee: The native fee.
+ * - lzTokenFee: The lzToken fee.
+ */
+ function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);
+
+ /**
+ * @notice Executes the send() operation.
+ * @param _sendParam The parameters for the send operation.
+ * @param _fee The fee information supplied by the caller.
+ * - nativeFee: The native fee.
+ * - lzTokenFee: The lzToken fee.
+ * @param _refundAddress The address to receive any excess funds from fees etc. on the src.
+ * @return receipt The LayerZero messaging receipt from the send() operation.
+ * @dev MessagingReceipt: LayerZero msg receipt
+ * - guid: The unique identifier for the sent message.
+ * - nonce: The nonce of the sent message.
+ * - fee: The LayerZero fee incurred for the message.
+ */
+ function send(
+ SendParam calldata _sendParam,
+ MessagingFee calldata _fee,
+ address _refundAddress
+ ) external payable returns (MessagingReceipt memory);
+}
diff --git a/packages/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol b/packages/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol
new file mode 100644
index 000000000..db69fd4a4
--- /dev/null
+++ b/packages/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.22;
+
+/**
+ * @title ONFT721MsgCodec
+ * @notice Library for encoding and decoding ONFT721 LayerZero messages.
+ */
+library ONFT721MsgCodec {
+ uint8 private constant SEND_TO_OFFSET = 32;
+ uint8 private constant TOKEN_ID_OFFSET = 64;
+
+ /**
+ * @dev Encodes an ONFT721 LayerZero message payload.
+ * @param _sendTo The recipient address.
+ * @param _tokenId The ID of the token to transfer.
+ * @param _composeMsg The composed payload.
+ * @return payload The encoded message payload.
+ * @return hasCompose A boolean indicating whether the message payload contains a composed payload.
+ */
+ function encode(
+ bytes32 _sendTo,
+ uint256 _tokenId,
+ bytes memory _composeMsg
+ ) internal view returns (bytes memory payload, bool hasCompose) {
+ hasCompose = _composeMsg.length > 0;
+ payload = hasCompose
+ ? abi.encodePacked(_sendTo, _tokenId, addressToBytes32(msg.sender), _composeMsg)
+ : abi.encodePacked(_sendTo, _tokenId);
+ }
+
+ /**
+ * @dev Decodes sendTo from the ONFT LayerZero message.
+ * @param _msg The message.
+ * @return The recipient address in bytes32 format.
+ */
+ function sendTo(bytes calldata _msg) internal pure returns (bytes32) {
+ return bytes32(_msg[:SEND_TO_OFFSET]);
+ }
+
+ /**
+ * @dev Decodes tokenId from the ONFT LayerZero message.
+ * @param _msg The message.
+ * @return The ID of the tokens to transfer.
+ */
+ function tokenId(bytes calldata _msg) internal pure returns (uint256) {
+ return abi.decode(_msg[SEND_TO_OFFSET:TOKEN_ID_OFFSET], (uint256));
+ }
+
+ /**
+ * @dev Decodes whether there is a composed payload.
+ * @param _msg The message.
+ * @return A boolean indicating whether the message has a composed payload.
+ */
+ function isComposed(bytes calldata _msg) internal pure returns (bool) {
+ return _msg.length > TOKEN_ID_OFFSET;
+ }
+
+ /**
+ * @dev Decodes the composed message.
+ * @param _msg The message.
+ * @return The composed message.
+ */
+ function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
+ return _msg[TOKEN_ID_OFFSET:];
+ }
+
+ /**
+ * @dev Converts an address to bytes32.
+ * @param _addr The address to convert.
+ * @return The bytes32 representation of the address.
+ */
+ function addressToBytes32(address _addr) internal pure returns (bytes32) {
+ return bytes32(uint256(uint160(_addr)));
+ }
+
+ /**
+ * @dev Converts bytes32 to an address.
+ * @param _b The bytes32 value to convert.
+ * @return The address representation of bytes32.
+ */
+ function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
+ return address(uint160(uint256(_b)));
+ }
+}
diff --git a/packages/onft-evm/foundry.toml b/packages/onft-evm/foundry.toml
new file mode 100644
index 000000000..23238e574
--- /dev/null
+++ b/packages/onft-evm/foundry.toml
@@ -0,0 +1,27 @@
+[profile.default]
+solc-version = '0.8.22'
+src = 'contracts'
+out = 'out'
+test = 'test'
+cache_path = 'cache'
+libs = [
+ # We provide a set of useful contract utilities
+ # in the lib directory of @layerzerolabs/toolbox-foundry:
+ #
+ # - forge-std
+ # - ds-test
+ # - solidity-bytes-utils
+ 'node_modules/@layerzerolabs/toolbox-foundry/lib',
+ 'node_modules',
+]
+
+remappings = [
+ # Due to a misconfiguration of solidity-bytes-utils, an outdated version
+ # of forge-std is being dragged in
+ #
+ # To remedy this, we'll remap the ds-test and forge-std imports to ou own versions
+ 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/src/ds-test/src/',
+ 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/src/forge-std/src/',
+ '@layerzerolabs/=node_modules/@layerzerolabs/',
+ '@openzeppelin/=node_modules/@openzeppelin/',
+]
diff --git a/packages/onft-evm/package.json b/packages/onft-evm/package.json
new file mode 100644
index 000000000..74b84c163
--- /dev/null
+++ b/packages/onft-evm/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@layerzerolabs/onft-evm",
+ "version": "0.0.1",
+ "description": "",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/LayerZero-Labs/devtools.git"
+ },
+ "exports": {
+ "./package.json": "./package.json"
+ },
+ "main": "./dist/index.cjs",
+ "files": [
+ "artifacts/contracts/**/!(*.dbg).json",
+ "contracts/**/*"
+ ],
+ "scripts": {
+ "compile": "$npm_execpath compile:forge",
+ "compile:forge": "forge build",
+ "test": "$npm_execpath test:forge",
+ "test:forge": "forge test"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.23.9",
+ "@layerzerolabs/eslint-config-next": "~2.3.3",
+ "@layerzerolabs/lz-definitions": "^2.3.25",
+ "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.25",
+ "@layerzerolabs/lz-evm-oapp-v2": "^2.3.25",
+ "@layerzerolabs/lz-evm-protocol-v2": "^2.3.25",
+ "@layerzerolabs/lz-evm-v1-0.7": "^2.3.25",
+ "@layerzerolabs/lz-v2-utilities": "^2.3.25",
+ "@layerzerolabs/prettier-config-next": "^2.3.25",
+ "@layerzerolabs/solhint-config": "^2.3.3",
+ "@layerzerolabs/test-devtools-evm-foundry": "~0.2.7",
+ "@layerzerolabs/toolbox-foundry": "~0.1.7",
+ "@layerzerolabs/toolbox-hardhat": "~0.2.35",
+ "@nomicfoundation/hardhat-ethers": "^3.0.5",
+ "@nomiclabs/hardhat-ethers": "^2.2.3",
+ "@openzeppelin/contracts": "^5.0.2",
+ "@openzeppelin/contracts-upgradeable": "^5.0.2",
+ "@rushstack/eslint-patch": "^1.7.0",
+ "@types/chai": "^4.3.11",
+ "@types/mocha": "^10.0.6",
+ "@types/node": "~18.18.14",
+ "chai": "^4.4.1",
+ "dotenv": "^16.4.1",
+ "erc721a": "^4.3.0",
+ "eslint-plugin-jest-extended": "~2.0.0",
+ "ethers": "^5.7.2",
+ "hardhat": "^2.22.3",
+ "hardhat-contract-sizer": "^2.10.0",
+ "hardhat-deploy": "^0.12.1",
+ "mocha": "^10.2.0",
+ "prettier": "^3.2.5",
+ "solhint": "^4.1.1",
+ "solidity-bytes-utils": "^0.8.2",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.3.3"
+ },
+ "engines": {
+ "node": ">=18.16.0"
+ }
+}
diff --git a/packages/onft-evm/solhint.config.js b/packages/onft-evm/solhint.config.js
new file mode 100644
index 000000000..52efe629c
--- /dev/null
+++ b/packages/onft-evm/solhint.config.js
@@ -0,0 +1 @@
+module.exports = require('@layerzerolabs/solhint-config');
diff --git a/packages/onft-evm/test/mocks/ComposerMock.sol b/packages/onft-evm/test/mocks/ComposerMock.sol
new file mode 100644
index 000000000..eff6e5907
--- /dev/null
+++ b/packages/onft-evm/test/mocks/ComposerMock.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { IOAppComposer } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppComposer.sol";
+
+contract ComposerMock is IOAppComposer {
+ // default empty values for testing a lzCompose received message
+ address public from;
+ bytes32 public guid;
+ bytes public message;
+ address public executor;
+ bytes public extraData;
+
+ function lzCompose(
+ address _from,
+ bytes32 _guid,
+ bytes calldata _message,
+ address _executor,
+ bytes calldata /*_extraData*/
+ ) external payable {
+ from = _from;
+ guid = _guid;
+ message = _message;
+ executor = _executor;
+ extraData = _message;
+ }
+}
diff --git a/packages/onft-evm/test/mocks/InspectorMock.sol b/packages/onft-evm/test/mocks/InspectorMock.sol
new file mode 100644
index 000000000..da0b0a932
--- /dev/null
+++ b/packages/onft-evm/test/mocks/InspectorMock.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { IOAppMsgInspector } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppMsgInspector.sol";
+
+contract InspectorMock is IOAppMsgInspector {
+ function inspect(bytes calldata _message, bytes calldata _options) external pure returns (bool) {
+ revert InspectionFailed(_message, _options);
+ }
+}
diff --git a/packages/onft-evm/test/onft/ONFTBaseTestHelper.sol b/packages/onft-evm/test/onft/ONFTBaseTestHelper.sol
new file mode 100644
index 000000000..af2fea258
--- /dev/null
+++ b/packages/onft-evm/test/onft/ONFTBaseTestHelper.sol
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol";
+
+abstract contract ONFTBaseTestHelper is TestHelperOz5 {
+ uint256 internal constant INITIAL_NATIVE_BALANCE = 1000 ether;
+
+ uint32 internal constant A_EID = 1;
+ uint32 internal constant B_EID = 2;
+ uint32 internal constant C_EID = 3;
+ uint8 internal constant NUM_ENDPOINTS = 3;
+
+ address internal alice = makeAddr("alice");
+ address internal bob = makeAddr("bob");
+ address internal charlie = makeAddr("charlie");
+
+ function setUp() public virtual override {
+ super.setUp();
+ setUpEndpoints(NUM_ENDPOINTS, LibraryType.UltraLightNode);
+ _deal();
+ }
+
+ /// @dev deal initial native balance to alice, bob, charlie
+ function _deal() internal virtual {
+ vm.deal(alice, INITIAL_NATIVE_BALANCE);
+ vm.deal(bob, INITIAL_NATIVE_BALANCE);
+ vm.deal(charlie, INITIAL_NATIVE_BALANCE);
+ }
+
+ function sliceUintArray(uint[] memory array, uint start, uint end) public pure returns (uint[] memory) {
+ if (start == end) return new uint[](0);
+ require(end <= array.length, "end index out of bounds");
+
+ uint length = end - start;
+ uint[] memory slicedArray = new uint[](length);
+
+ for (uint i = 0; i < length; i++) {
+ slicedArray[i] = array[start + i];
+ }
+
+ return slicedArray;
+ }
+}
diff --git a/packages/onft-evm/test/onft/onft721/ONFT721.t.sol b/packages/onft-evm/test/onft/onft721/ONFT721.t.sol
new file mode 100644
index 000000000..2f2578799
--- /dev/null
+++ b/packages/onft-evm/test/onft/onft721/ONFT721.t.sol
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+
+import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";
+import { ONFTComposeMsgCodec } from "../../../contracts/libs/ONFTComposeMsgCodec.sol";
+import { ONFT721Adapter } from "../../../contracts/onft721/ONFT721Adapter.sol";
+
+import { IONFT721 } from "../../../contracts/onft721/interfaces/IONFT721.sol";
+import { ERC721Mock } from "./mocks/ERC721Mock.sol";
+import { ONFT721MsgCodec } from "../../../contracts/onft721/libs/ONFT721MsgCodec.sol";
+import { ComposerMock } from "../../mocks/ComposerMock.sol";
+import { InspectorMock, IOAppMsgInspector } from "../../mocks/InspectorMock.sol";
+import { MessagingFee, MessagingReceipt } from "../../../contracts/onft721/ONFT721Core.sol";
+import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol";
+
+import { SendParam } from "../../../contracts/onft721/interfaces/IONFT721.sol";
+
+import { ONFT721Mock } from "./mocks/ONFT721Mock.sol";
+import { ONFT721AdapterMock } from "./mocks/ONFT721AdapterMock.sol";
+import { ONFT721Base } from "./ONFT721Base.sol";
+
+contract ONFT721Test is ONFT721Base {
+ using OptionsBuilder for bytes;
+
+ bytes4 internal constant EXPECTED_ONFT721_ID = 0x23e18da6;
+ uint8 internal constant EXPECTED_ONFT721_VERSION = 1;
+
+ // also tests token() function
+ function test_constructor() public {
+ assertEq(aONFT.owner(), address(this));
+ assertEq(bONFT.owner(), address(this));
+ assertEq(cONFTAdapter.owner(), address(this));
+
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
+
+ assertEq(aONFT.token(), address(aONFT));
+ assertEq(bONFT.token(), address(bONFT));
+ assertEq(cONFTAdapter.token(), address(cERC721Mock));
+ }
+
+ function test_approvalRequired() public {
+ assertFalse(aONFT.approvalRequired());
+ assertFalse(bONFT.approvalRequired());
+ assertTrue(cONFTAdapter.approvalRequired());
+ }
+
+ function test_onftVersion() public {
+ (bytes4 interfaceId, uint64 version) = aONFT.onftVersion();
+ bytes4 expectedId = EXPECTED_ONFT721_ID;
+ assertEq(interfaceId, expectedId);
+ assertEq(version, EXPECTED_ONFT721_VERSION);
+ }
+
+ function test_send(uint16 _tokenToSend) public {
+ // 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter
+ vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3);
+
+ // 2. Set enforced options for SEND
+ _setMeshDefaultEnforcedSendOption();
+
+ // 3. Sanity check token balances and _tokenToSend ownership
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(IERC721(cONFTAdapter.token()).ownerOf(_tokenToSend), charlie);
+
+ // 4. Send the same ONFT in a circle 10 times.
+ // a) C->A
+ // b) A->B
+ // c) B->C
+ for (uint8 i = 0; i < 10; i++) {
+ vm.startPrank(charlie);
+ IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), _tokenToSend);
+ vm.stopPrank();
+ _sendAndCheck(
+ _tokenToSend,
+ C_EID,
+ A_EID,
+ charlie,
+ alice,
+ DEFAULT_INITIAL_ONFTS_PER_EID,
+ DEFAULT_INITIAL_ONFTS_PER_EID,
+ true,
+ false
+ );
+ _sendAndCheck(
+ _tokenToSend,
+ A_EID,
+ B_EID,
+ alice,
+ bob,
+ DEFAULT_INITIAL_ONFTS_PER_EID + 1,
+ DEFAULT_INITIAL_ONFTS_PER_EID,
+ false,
+ false
+ );
+ _sendAndCheck(
+ _tokenToSend,
+ B_EID,
+ C_EID,
+ bob,
+ charlie,
+ DEFAULT_INITIAL_ONFTS_PER_EID + 1,
+ DEFAULT_INITIAL_ONFTS_PER_EID - 1,
+ false,
+ true
+ );
+ }
+
+ // 5. Check the final balances
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
+ }
+
+ /// @dev Test to ensure that the quoteSend function reverts when the receiver is invalid.
+ function test_quoteSend_InvalidReceiver(uint16 _tokenToSend) public {
+ // 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter
+ vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3);
+
+ // 2. Set enforced options for SEND
+ _setMeshDefaultEnforcedSendOption();
+
+ SendParam memory sendParam = SendParam(B_EID, addressToBytes32(address(0)), _tokenToSend, "", "", "");
+ vm.expectRevert(IONFT721.InvalidReceiver.selector);
+ IONFT721(onfts[2]).quoteSend(sendParam, false);
+ }
+
+ /// @dev Test to ensure that the send function reverts when the receiver is invalid.
+ function test_send_InvalidReceiver(uint16 _tokenToSend) public {
+ // 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter
+ vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3);
+
+ // 2. Set enforced options for SEND
+ _setMeshDefaultEnforcedSendOption();
+
+ SendParam memory sendParam = SendParam(B_EID, addressToBytes32(address(0)), _tokenToSend, "", "", "");
+ MessagingFee memory fee = MessagingFee(200_000, 0);
+
+ vm.startPrank(charlie);
+ IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), _tokenToSend);
+ vm.expectRevert(IONFT721.InvalidReceiver.selector);
+ IONFT721(onfts[2]).send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));
+ vm.stopPrank();
+ }
+
+ function test_sendAndCompose(uint8 _tokenToSend, bytes memory _composeMsg) public {
+ vm.assume(_composeMsg.length > 0);
+
+ assertEq(aONFT.ownerOf(_tokenToSend), alice);
+
+ ComposerMock composer = new ComposerMock();
+ bytes memory options = OptionsBuilder
+ .newOptions()
+ .addExecutorLzReceiveOption(500000, 0)
+ .addExecutorLzComposeOption(0, 500000, 0);
+ SendParam memory sendParam = SendParam(
+ B_EID,
+ addressToBytes32(address(composer)),
+ _tokenToSend,
+ options,
+ _composeMsg,
+ ""
+ );
+ MessagingFee memory fee = aONFT.quoteSend(sendParam, false);
+
+ assertEq(bONFT.balanceOf(address(composer)), 0);
+
+ vm.prank(alice);
+ MessagingReceipt memory msgReceipt = aONFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));
+ verifyPackets(B_EID, addressToBytes32(address(bONFT)));
+
+ // lzCompose params
+ address from_ = address(bONFT);
+ bytes memory options_ = options;
+ bytes32 guid_ = msgReceipt.guid;
+ address to_ = address(composer);
+ bytes memory composerMsg_ = ONFTComposeMsgCodec.encode(
+ msgReceipt.nonce,
+ A_EID,
+ abi.encodePacked(addressToBytes32(alice), _composeMsg)
+ );
+ this.lzCompose(B_EID, from_, options_, guid_, to_, composerMsg_);
+
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID - 1);
+ assertEq(bONFT.balanceOf(address(composer)), 1);
+
+ assertEq(composer.from(), from_);
+ assertEq(composer.guid(), guid_);
+ assertEq(composer.message(), composerMsg_);
+ assertEq(composer.executor(), address(this));
+ assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test
+ }
+
+ function test_ONFTComposeMsgCodec(uint64 _nonce, uint32 _srcEid, bytes memory _composeMsg) public {
+ vm.assume(_composeMsg.length > 0);
+
+ bytes memory message = ONFTComposeMsgCodec.encode(
+ _nonce,
+ _srcEid,
+ abi.encodePacked(addressToBytes32(msg.sender), _composeMsg)
+ );
+ (uint64 nonce, uint32 srcEid, bytes32 composeFrom, bytes memory composeMsg) = this._decodeONFTComposeMsgCodec(
+ message
+ );
+
+ assertEq(nonce, _nonce);
+ assertEq(srcEid, _srcEid);
+ assertEq(composeFrom, addressToBytes32(msg.sender));
+ assertEq(composeMsg, _composeMsg);
+ }
+
+ function _decodeONFTComposeMsgCodec(
+ bytes calldata _message
+ ) public pure returns (uint64 nonce, uint32 srcEid, bytes32 composeFrom, bytes memory composeMsg) {
+ nonce = ONFTComposeMsgCodec.nonce(_message);
+ srcEid = ONFTComposeMsgCodec.srcEid(_message);
+ composeFrom = ONFTComposeMsgCodec.composeFrom(_message);
+ composeMsg = ONFTComposeMsgCodec.composeMsg(_message);
+ }
+
+ function test_debit(uint256 _tokenId) public {
+ vm.assume(_tokenId < DEFAULT_INITIAL_ONFTS_PER_EID);
+ vm.assume(aONFT.ownerOf(_tokenId) == alice);
+
+ uint32 dstEid = A_EID;
+
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(aONFT.balanceOf(address(this)), 0);
+
+ vm.prank(alice);
+ aONFT.debit(_tokenId, dstEid);
+
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID - 1);
+ assertFalse(aONFT.exists(_tokenId));
+ assertEq(aONFT.balanceOf(address(this)), 0);
+ }
+
+ function test_credit(uint256 _tokenId) public {
+ vm.assume(_tokenId >= DEFAULT_INITIAL_ONFTS_PER_EID);
+ uint32 srcEid = A_EID;
+
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(aONFT.balanceOf(address(this)), 0);
+
+ vm.prank(alice);
+ aONFT.credit(alice, _tokenId, srcEid);
+
+ assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID + 1);
+ assertEq(aONFT.ownerOf(_tokenId), alice);
+ assertEq(aONFT.balanceOf(address(this)), 0);
+ }
+
+ function test_ONFTAdapter_debitAndCredit(uint16 _tokenId) public {
+ // Ensure that the tokenId is owned by userC
+ vm.assume(_tokenId >= DEFAULT_INITIAL_ONFTS_PER_EID * 2 && _tokenId < DEFAULT_INITIAL_ONFTS_PER_EID * 3);
+ vm.assume(cERC721Mock.ownerOf(_tokenId) == charlie);
+
+ uint32 dstEid = C_EID;
+ uint32 srcEid = C_EID;
+
+ assertEq(cERC721Mock.balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
+ assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2);
+
+ vm.prank(charlie);
+ cERC721Mock.approve(address(cONFTAdapter), _tokenId);
+ vm.prank(charlie);
+ cONFTAdapter.debit(_tokenId, dstEid);
+
+ // Ensure that
+ // 1. userC balance is decremented by 1.
+ // 2. The Adapter balance is incremented by 1.
+ // 3. The Adapter is the owner of the token
+ assertEq(cERC721Mock.balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID - 1);
+ assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2 + 1);
+ assertEq(cERC721Mock.ownerOf(_tokenId), address(cONFTAdapter));
+
+ vm.prank(charlie);
+ cONFTAdapter.credit(bob, _tokenId, srcEid);
+
+ // Ensure that:
+ // 1. userB balance is incremented by 1.
+ // 2. The Adapter balance is decremented by 1.
+ // 3. userB owns the token
+ assertEq(cERC721Mock.balanceOf(address(bob)), 1);
+ assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2);
+ assertEq(cERC721Mock.ownerOf(_tokenId), bob);
+ }
+
+ function _decodeONFTMsgCodec(
+ bytes calldata _message
+ ) public pure returns (bool isComposed, bytes32 sendTo, uint256 tokenId, bytes memory composeMsg) {
+ isComposed = ONFT721MsgCodec.isComposed(_message);
+ sendTo = ONFT721MsgCodec.sendTo(_message);
+ tokenId = ONFT721MsgCodec.tokenId(_message);
+ composeMsg = ONFT721MsgCodec.composeMsg(_message);
+ }
+
+ function test_buildMsgAndOptions(
+ uint256 _tokenId,
+ bytes memory _composeMsg,
+ uint128 _baseGas,
+ uint128 _value,
+ uint128 _composeGas
+ ) public {
+ vm.assume(_baseGas > 0);
+ vm.assume(_composeGas > 0);
+
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(_baseGas, _value);
+ if (_composeMsg.length > 0) extraOptions = extraOptions.addExecutorLzComposeOption(0, _composeGas, _value);
+ SendParam memory sendParam = SendParam(B_EID, addressToBytes32(alice), _tokenId, extraOptions, _composeMsg, "");
+
+ (bytes memory message, bytes memory options) = aONFT.buildMsgAndOptions(sendParam);
+
+ assertEq(options, extraOptions);
+ (bool isComposed, bytes32 sendTo, uint256 tokenId, bytes memory composeMsg) = this._decodeONFTMsgCodec(message);
+ assertEq(isComposed, _composeMsg.length > 0);
+ assertEq(sendTo, addressToBytes32(alice));
+ assertEq(tokenId, _tokenId);
+ bytes memory expectedComposeMsg = abi.encodePacked(addressToBytes32(address(this)), _composeMsg);
+ assertEq(composeMsg, _composeMsg.length > 0 ? expectedComposeMsg : bytes(""));
+ }
+
+ function test_buildMsgAndOptions_noComposition(
+ uint256 _tokenId,
+ bool _useEnforcedOptions,
+ bool _useExtraOptions,
+ uint128 _lzReceiveGas,
+ uint128 _lzReceiveValue
+ ) public {
+ if (_useEnforcedOptions) _setMeshDefaultEnforcedSendOption();
+ bytes memory extraOptions = _useExtraOptions
+ ? OptionsBuilder.newOptions().addExecutorLzReceiveOption(_lzReceiveGas, _lzReceiveValue)
+ : bytes("");
+ SendParam memory sendParam = SendParam(B_EID, addressToBytes32(alice), _tokenId, extraOptions, "", "");
+
+ (bytes memory message, bytes memory options) = aONFT.buildMsgAndOptions(sendParam);
+ assertEq(options, aONFT.combineOptions(B_EID, 1, extraOptions));
+ (bool isComposed_, bytes32 sendTo_, uint256 tokenId_, bytes memory composeMsg_) = this._decodeONFTMsgCodec(
+ message
+ );
+ assertEq(isComposed_, false);
+ assertEq(sendTo_, addressToBytes32(alice));
+ assertEq(tokenId_, _tokenId);
+ assertEq(composeMsg_.length, 0);
+ assertEq(composeMsg_, "");
+ }
+
+ function test_setEnforcedOptions(
+ uint32 _eid,
+ uint128 _optionTypeOneGas,
+ uint128 _optionTypeOneValue,
+ uint128 _optionTypeTwoGas,
+ uint128 _optionTypeTwoValue
+ ) public {
+ vm.assume(
+ _optionTypeOneGas > 0 &&
+ _optionTypeOneGas < type(uint128).max &&
+ _optionTypeTwoGas > 0 &&
+ _optionTypeTwoGas < type(uint128).max
+ );
+
+ bytes memory optionsTypeOne = OptionsBuilder.newOptions().addExecutorLzReceiveOption(
+ _optionTypeOneGas,
+ _optionTypeOneValue
+ );
+ bytes memory optionsTypeTwo = OptionsBuilder.newOptions().addExecutorLzReceiveOption(
+ _optionTypeTwoGas,
+ _optionTypeTwoValue
+ );
+
+ EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](2);
+ enforcedOptions[0] = EnforcedOptionParam(_eid, 1, optionsTypeOne);
+ enforcedOptions[1] = EnforcedOptionParam(_eid, 2, optionsTypeTwo);
+
+ aONFT.setEnforcedOptions(enforcedOptions);
+
+ assertEq(aONFT.enforcedOptions(_eid, 1), optionsTypeOne);
+ assertEq(aONFT.enforcedOptions(_eid, 2), optionsTypeTwo);
+ }
+
+ function test_assertOptionsType3(uint32 _eid, bytes2 _prefix) public {
+ vm.assume(_prefix != bytes2(0x0003));
+
+ EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](1);
+
+ bytes memory options = new bytes(2);
+ assembly {
+ mstore(add(options, 32), _prefix)
+ }
+
+ enforcedOptions[0] = EnforcedOptionParam(_eid, 1, options); // not type 3
+ vm.expectRevert(abi.encodeWithSelector(IOAppOptionsType3.InvalidOptions.selector, options));
+ aONFT.setEnforcedOptions(enforcedOptions);
+ }
+
+ function test_combineOptions(
+ uint32 _eid,
+ uint16 _msgType,
+ uint128 _enforcedOptionGas,
+ uint128 _enforcedOptionNativeDrop,
+ uint128 _combinedOptionNativeDrop
+ ) public {
+ vm.assume(uint256(_enforcedOptionNativeDrop) + _combinedOptionNativeDrop < type(uint128).max);
+
+ bytes memory enforcedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(
+ _enforcedOptionGas,
+ _enforcedOptionNativeDrop
+ );
+ EnforcedOptionParam[] memory enforcedOptionsArray = new EnforcedOptionParam[](1);
+ enforcedOptionsArray[0] = EnforcedOptionParam(_eid, _msgType, enforcedOptions);
+ aONFT.setEnforcedOptions(enforcedOptionsArray);
+
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption(
+ _combinedOptionNativeDrop,
+ addressToBytes32(alice)
+ );
+
+ bytes memory expectedOptions = OptionsBuilder
+ .newOptions()
+ .addExecutorLzReceiveOption(_enforcedOptionGas, _enforcedOptionNativeDrop)
+ .addExecutorNativeDropOption(_combinedOptionNativeDrop, addressToBytes32(alice));
+
+ bytes memory combinedOptions = aONFT.combineOptions(_eid, _msgType, extraOptions);
+ assertEq(combinedOptions, expectedOptions);
+ }
+
+ function test_combineOptions_noExtraOptions(
+ uint32 _eid,
+ uint16 _msgType,
+ uint128 _enforcedOptionGas,
+ uint128 _enforcedOptionNativeGas
+ ) public {
+ bytes memory enforcedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(
+ _enforcedOptionGas,
+ _enforcedOptionNativeGas
+ );
+ EnforcedOptionParam[] memory enforcedOptionsArray = new EnforcedOptionParam[](1);
+ enforcedOptionsArray[0] = EnforcedOptionParam(_eid, _msgType, enforcedOptions);
+ aONFT.setEnforcedOptions(enforcedOptionsArray);
+
+ bytes memory expectedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(
+ _enforcedOptionGas,
+ _enforcedOptionNativeGas
+ );
+
+ bytes memory combinedOptions = aONFT.combineOptions(_eid, _msgType, "");
+ assertEq(combinedOptions, expectedOptions);
+ }
+
+ function test_combineOptions_noEnforcedOptions(
+ uint32 _eid,
+ uint16 _msgType,
+ uint128 _combinedOptionNativeDrop
+ ) public {
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption(
+ _combinedOptionNativeDrop,
+ addressToBytes32(alice)
+ );
+
+ bytes memory expectedOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption(
+ _combinedOptionNativeDrop,
+ addressToBytes32(alice)
+ );
+
+ bytes memory combinedOptions = aONFT.combineOptions(_eid, _msgType, extraOptions);
+ assertEq(combinedOptions, expectedOptions);
+ }
+
+ function test_OAppInspector_inspect(uint256 _tokenId, bytes32 _to) public {
+ uint32 dstEid = B_EID;
+ _setMeshDefaultEnforcedSendOption();
+
+ SendParam memory sendParam = SendParam(dstEid, _to, _tokenId, "", "", "");
+
+ // doesnt revert
+ (bytes memory message, ) = aONFT.buildMsgAndOptions(sendParam);
+
+ // deploy a universal inspector, it automatically reverts
+ oAppInspector = new InspectorMock();
+ aONFT.setMsgInspector(address(oAppInspector));
+
+ // does revert because inspector is set
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ IOAppMsgInspector.InspectionFailed.selector,
+ message,
+ aONFT.enforcedOptions(B_EID, 1)
+ )
+ );
+ (message, ) = aONFT.buildMsgAndOptions(sendParam);
+ }
+
+ function test_setBaseURI(address _user, string memory _baseTokenURI, uint256 _id) public {
+ vm.assume(_user != address(this));
+
+ // 1. Test non privileged user
+ vm.prank(_user);
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _user));
+ aONFT.setBaseURI(_baseTokenURI);
+
+ // 2. Test setting with owner doesn't throw
+ aONFT.setBaseURI(_baseTokenURI);
+ }
+}
diff --git a/packages/onft-evm/test/onft/onft721/ONFT721Base.sol b/packages/onft-evm/test/onft/onft721/ONFT721Base.sol
new file mode 100644
index 000000000..07579cd31
--- /dev/null
+++ b/packages/onft-evm/test/onft/onft721/ONFT721Base.sol
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
+
+import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";
+import { EnforcedOptionParam, OAppOptionsType3 } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol";
+import { MessagingFee } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
+
+import { IONFT721, SendParam } from "../../../contracts/onft721/interfaces/IONFT721.sol";
+import { ONFT721Adapter } from "../../../contracts/onft721/ONFT721Adapter.sol";
+
+import { ERC721Mock } from "./mocks/ERC721Mock.sol";
+import { ONFT721Mock } from "./mocks/ONFT721Mock.sol";
+import { ONFT721AdapterMock } from "./mocks/ONFT721AdapterMock.sol";
+import { InspectorMock, IOAppMsgInspector } from "../../mocks/InspectorMock.sol";
+
+import { ONFTBaseTestHelper } from "../ONFTBaseTestHelper.sol";
+
+abstract contract ONFT721Base is ONFTBaseTestHelper {
+ using OptionsBuilder for bytes;
+
+ string internal constant A_ONFT_NAME = "aONFT";
+ string internal constant A_ONFT_SYMBOL = "aONFT";
+ string internal constant B_ONFT_NAME = "bONFT";
+ string internal constant B_ONFT_SYMBOL = "bONFT";
+ string internal constant C_TOKEN_NAME = "cONFT";
+ string internal constant C_TOKEN_SYMBOL = "cONFT";
+
+ uint256 internal constant DEFAULT_INITIAL_ONFTS_PER_EID = 256;
+
+ address[] public onfts;
+ ONFT721Mock internal aONFT;
+ ONFT721Mock internal bONFT;
+ ONFT721AdapterMock internal cONFTAdapter;
+ ERC721Mock internal cERC721Mock;
+
+ InspectorMock internal oAppInspector;
+
+ function setUp() public virtual override {
+ super.setUp();
+
+ _deployONFTs();
+ _wireAndMintInitial();
+ }
+
+ /// @dev deploy ONFTs
+ /// @notice this function should deploy aONFT, bONFT, cONFTAdapter, and cERC721Mock
+ /// @dev Implementations may override this function to deploy TestableONFT721Mock, which contains additional testing functionality.
+ function _deployONFTs() internal virtual {
+ aONFT = ONFT721Mock(
+ _deployOApp(
+ type(ONFT721Mock).creationCode,
+ abi.encode(A_ONFT_NAME, A_ONFT_SYMBOL, address(endpoints[A_EID]), address(this))
+ )
+ );
+
+ bONFT = ONFT721Mock(
+ _deployOApp(
+ type(ONFT721Mock).creationCode,
+ abi.encode(B_ONFT_NAME, B_ONFT_SYMBOL, address(endpoints[B_EID]), address(this))
+ )
+ );
+
+ cERC721Mock = new ERC721Mock(C_TOKEN_NAME, C_TOKEN_SYMBOL);
+ cONFTAdapter = ONFT721AdapterMock(
+ _deployOApp(
+ type(ONFT721AdapterMock).creationCode,
+ abi.encode(address(cERC721Mock), address(endpoints[C_EID]), address(this))
+ )
+ );
+ }
+
+ /// @dev the initial number of ONFTS to mint per EID
+ /// @dev Implementations may override this function to change the number of ONFTs minted per EID.
+ function _initialNumONFTsPerEID() internal pure virtual returns (uint256) {
+ return DEFAULT_INITIAL_ONFTS_PER_EID;
+ }
+
+ /// @dev wire the ONFTs and mint the initial ONFTs
+ function _wireAndMintInitial() internal {
+ // wire the onfts
+ onfts = new address[](3);
+ uint256 onftIndex = 0;
+ onfts[onftIndex++] = address(aONFT);
+ onfts[onftIndex++] = address(bONFT);
+ onfts[onftIndex++] = address(cONFTAdapter);
+ wireOApps(onfts);
+
+ _mintOnAdapter();
+ _distributeAcrossMesh();
+ }
+
+ /// @dev mint ONFTs on the adapter
+ function _mintOnAdapter() internal {
+ uint256 numONFTsPerEID = _initialNumONFTsPerEID();
+ for (uint256 i = 0; i < numONFTsPerEID * 3; i++) {
+ cERC721Mock.mint(charlie, i);
+ }
+ }
+
+ /// @dev distribute ONFTs across the mesh, giving alice 256 on A_EID and bob 256 on B_EID.
+ function _distributeAcrossMesh() internal {
+ bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200_000, 0);
+ uint256 numONFTsPerEID = _initialNumONFTsPerEID();
+ for (uint256 i = 0; i < numONFTsPerEID; i++) {
+ vm.startPrank(charlie);
+ IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), i);
+ vm.stopPrank();
+ _sendAndCheck(i, C_EID, A_EID, charlie, alice, options, numONFTsPerEID * 3 - i, i, true, false);
+ }
+ for (uint256 i = numONFTsPerEID; i < numONFTsPerEID * 2; i++) {
+ vm.startPrank(charlie);
+ IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), i);
+ vm.stopPrank();
+ _sendAndCheck(
+ i,
+ C_EID,
+ B_EID,
+ charlie,
+ bob,
+ options,
+ numONFTsPerEID * 3 - i,
+ i - numONFTsPerEID,
+ true,
+ false
+ );
+ }
+ }
+
+ function _sendAndCheck(
+ uint256 _tokenToSend,
+ uint32 _srcEid,
+ uint32 _dstEid,
+ address _from,
+ address _to,
+ uint256 _srcCount,
+ uint256 _dstCount,
+ bool _srcIsAdapter,
+ bool _dstIsAdapter
+ ) internal {
+ _sendAndCheck(
+ _tokenToSend,
+ _srcEid,
+ _dstEid,
+ _from,
+ _to,
+ "",
+ _srcCount,
+ _dstCount,
+ _srcIsAdapter,
+ _dstIsAdapter
+ );
+ }
+
+ function _sendAndCheck(
+ uint256 _tokenToSend,
+ uint32 _srcEid,
+ uint32 _dstEid,
+ address _from,
+ address _to,
+ bytes memory _options,
+ uint256 _srcCount,
+ uint256 _dstCount,
+ bool _srcIsAdapter,
+ bool _dstIsAdapter
+ ) internal {
+ SendParam memory sendParam = SendParam(_dstEid, addressToBytes32(_to), _tokenToSend, _options, "", "");
+ MessagingFee memory fee = IONFT721(onfts[_srcEid - 1]).quoteSend(sendParam, false);
+
+ vm.prank(_from);
+ IONFT721(onfts[_srcEid - 1]).send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));
+ verifyPackets(_dstEid, addressToBytes32(address(onfts[_dstEid - 1])));
+
+ assertEq(
+ IERC721(!_srcIsAdapter ? onfts[_srcEid - 1] : ONFT721Adapter(onfts[_srcEid - 1]).token()).balanceOf(_from),
+ _srcCount - 1
+ );
+ assertEq(
+ IERC721(!_dstIsAdapter ? onfts[_dstEid - 1] : ONFT721Adapter(onfts[_dstEid - 1]).token()).balanceOf(_to),
+ _dstCount + 1
+ );
+ assertEq(
+ IERC721(!_dstIsAdapter ? onfts[_dstEid - 1] : ONFT721Adapter(onfts[_dstEid - 1]).token()).ownerOf(
+ _tokenToSend
+ ),
+ _to
+ );
+ }
+
+ function _setMeshDefaultEnforcedSendOption() internal {
+ for (uint32 i = 0; i <= 3; i++) {
+ _setDefaultEnforcedSendOption(address(aONFT), i);
+ _setDefaultEnforcedSendOption(address(bONFT), i);
+ _setDefaultEnforcedSendOption(address(cONFTAdapter), i);
+ }
+ }
+
+ function _setDefaultEnforcedSendOption(address _onft, uint32 _eid) internal {
+ _setEnforcedSendOption(_onft, _eid, 200_000);
+ }
+
+ function _setEnforcedSendOption(address _onft, uint32 _eid, uint128 _gas) internal {
+ _setEnforcedOption(_onft, _eid, 1, OptionsBuilder.newOptions().addExecutorLzReceiveOption(_gas, 0));
+ }
+
+ function _setEnforcedOption(address _onft, uint32 _eid, uint16 _optionId, bytes memory _options) internal {
+ EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](1);
+ enforcedOptions[0] = EnforcedOptionParam(_eid, _optionId, _options);
+ OAppOptionsType3(_onft).setEnforcedOptions(enforcedOptions);
+ }
+}
diff --git a/packages/onft-evm/test/onft/onft721/mocks/ERC721Mock.sol b/packages/onft-evm/test/onft/onft721/mocks/ERC721Mock.sol
new file mode 100644
index 000000000..6854bc934
--- /dev/null
+++ b/packages/onft-evm/test/onft/onft721/mocks/ERC721Mock.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+
+contract ERC721Mock is ERC721 {
+ constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}
+
+ function mint(address _to, uint256 _tokenId) public {
+ _mint(_to, _tokenId);
+ }
+}
diff --git a/packages/onft-evm/test/onft/onft721/mocks/ONFT721AdapterMock.sol b/packages/onft-evm/test/onft/onft721/mocks/ONFT721AdapterMock.sol
new file mode 100644
index 000000000..e04efddff
--- /dev/null
+++ b/packages/onft-evm/test/onft/onft721/mocks/ONFT721AdapterMock.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { ONFT721Adapter } from "../../../../contracts/onft721/ONFT721Adapter.sol";
+
+contract ONFT721AdapterMock is ONFT721Adapter {
+ constructor(
+ address _token,
+ address _lzEndpoint,
+ address _delegate
+ ) ONFT721Adapter(_token, _lzEndpoint, _delegate) {}
+
+ function debit(uint256 _tokenId, uint32 _dstEid) public {
+ _debit(msg.sender, _tokenId, _dstEid);
+ }
+
+ function credit(address _to, uint256 _tokenId, uint32 _srcEid) public {
+ _credit(_to, _tokenId, _srcEid);
+ }
+}
diff --git a/packages/onft-evm/test/onft/onft721/mocks/ONFT721Mock.sol b/packages/onft-evm/test/onft/onft721/mocks/ONFT721Mock.sol
new file mode 100644
index 000000000..7f8624489
--- /dev/null
+++ b/packages/onft-evm/test/onft/onft721/mocks/ONFT721Mock.sol
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { ONFT721 } from "../../../../contracts/onft721/ONFT721.sol";
+import { SendParam } from "../../../../contracts/onft721/ONFT721Core.sol";
+
+contract ONFT721Mock is ONFT721 {
+ constructor(
+ string memory _name,
+ string memory _symbol,
+ address _lzEndpoint,
+ address _delegate
+ ) ONFT721(_name, _symbol, _lzEndpoint, _delegate) {}
+
+ function mint(address _to, uint256 _tokenId) public {
+ _mint(_to, _tokenId);
+ }
+
+ function debit(uint256 _tokenId, uint32 _dstEid) public {
+ _debit(msg.sender, _tokenId, _dstEid);
+ }
+
+ function exists(uint256 _tokenId) public view returns (bool) {
+ return _ownerOf(_tokenId) != address(0);
+ }
+
+ function credit(address _toAddress, uint256 _tokenId, uint32 _srcEid) public {
+ _credit(_toAddress, _tokenId, _srcEid);
+ }
+
+ function buildMsgAndOptions(SendParam calldata _sendParam) public view returns (bytes memory, bytes memory) {
+ return _buildMsgAndOptions(_sendParam);
+ }
+}
diff --git a/packages/onft-evm/tsconfig.json b/packages/onft-evm/tsconfig.json
new file mode 100644
index 000000000..027ad0f3f
--- /dev/null
+++ b/packages/onft-evm/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "exclude": ["node_modules"],
+ "include": ["deploy", "tasks", "test", "hardhat.config.ts"],
+ "compilerOptions": {
+ "target": "es2020",
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ }
+}
diff --git a/packages/onft-evm/turbo.json b/packages/onft-evm/turbo.json
new file mode 100644
index 000000000..d663f5d46
--- /dev/null
+++ b/packages/onft-evm/turbo.json
@@ -0,0 +1,15 @@
+{
+ "extends": ["//"],
+ "pipeline": {
+ "build": {
+ "outputs": [
+ "dist/**",
+ "artifacts*/**",
+ "hh-cache*/**",
+ "cache*/**",
+ "src/typechain-types/**",
+ "out/**"
+ ]
+ }
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 720223c1d..5eeae8340 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -286,6 +286,114 @@ importers:
specifier: ^5.3.3
version: 5.3.3
+ examples/onft721:
+ devDependencies:
+ '@babel/core':
+ specifier: ^7.23.9
+ version: 7.23.9
+ '@layerzerolabs/eslint-config-next':
+ specifier: ~2.3.3
+ version: 2.3.3(typescript@5.4.5)
+ '@layerzerolabs/lz-definitions':
+ specifier: ^2.3.25
+ version: 2.3.25
+ '@layerzerolabs/lz-evm-messagelib-v2':
+ specifier: ^2.3.25
+ version: 2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2)
+ '@layerzerolabs/lz-evm-oapp-v2':
+ specifier: ^2.3.25
+ version: 2.3.25(@layerzerolabs/lz-evm-messagelib-v2@2.3.25)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2)
+ '@layerzerolabs/lz-evm-protocol-v2':
+ specifier: ^2.3.25
+ version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2)
+ '@layerzerolabs/lz-evm-v1-0.7':
+ specifier: ^2.3.25
+ version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)
+ '@layerzerolabs/lz-v2-utilities':
+ specifier: ^2.3.25
+ version: 2.3.25
+ '@layerzerolabs/onft-evm':
+ specifier: 0.0.1
+ version: link:../../packages/onft-evm
+ '@layerzerolabs/prettier-config-next':
+ specifier: ^2.3.25
+ version: 2.3.25
+ '@layerzerolabs/solhint-config':
+ specifier: ^2.3.3
+ version: 2.3.3(typescript@5.4.5)
+ '@layerzerolabs/test-devtools-evm-foundry':
+ specifier: ~0.2.7
+ version: link:../../packages/test-devtools-evm-foundry
+ '@layerzerolabs/toolbox-foundry':
+ specifier: ~0.1.7
+ version: link:../../packages/toolbox-foundry
+ '@layerzerolabs/toolbox-hardhat':
+ specifier: ~0.2.35
+ version: link:../../packages/toolbox-hardhat
+ '@nomicfoundation/hardhat-ethers':
+ specifier: ^3.0.5
+ version: 3.0.5(ethers@5.7.2)(hardhat@2.22.3)
+ '@nomiclabs/hardhat-ethers':
+ specifier: ^2.2.3
+ version: 2.2.3(ethers@5.7.2)(hardhat@2.22.3)
+ '@openzeppelin/contracts':
+ specifier: ^5.0.1
+ version: 5.0.2
+ '@openzeppelin/contracts-upgradeable':
+ specifier: ^5.0.1
+ version: 5.0.2(@openzeppelin/contracts@5.0.2)
+ '@rushstack/eslint-patch':
+ specifier: ^1.7.0
+ version: 1.7.0
+ '@types/chai':
+ specifier: ^4.3.11
+ version: 4.3.11
+ '@types/mocha':
+ specifier: ^10.0.6
+ version: 10.0.6
+ '@types/node':
+ specifier: ~18.18.14
+ version: 18.18.14
+ chai:
+ specifier: ^4.4.1
+ version: 4.4.1
+ dotenv:
+ specifier: ^16.4.1
+ version: 16.4.5
+ eslint-plugin-jest-extended:
+ specifier: ~2.0.0
+ version: 2.0.0(eslint@8.57.0)(typescript@5.4.5)
+ ethers:
+ specifier: ^5.7.2
+ version: 5.7.2
+ hardhat:
+ specifier: ^2.22.3
+ version: 2.22.3(ts-node@10.9.2)(typescript@5.4.5)
+ hardhat-contract-sizer:
+ specifier: ^2.10.0
+ version: 2.10.0(hardhat@2.22.3)
+ hardhat-deploy:
+ specifier: ^0.12.1
+ version: 0.12.4
+ mocha:
+ specifier: ^10.2.0
+ version: 10.2.0
+ prettier:
+ specifier: ^3.2.5
+ version: 3.2.5
+ solhint:
+ specifier: ^4.1.1
+ version: 4.1.1(typescript@5.4.5)
+ solidity-bytes-utils:
+ specifier: ^0.8.2
+ version: 0.8.2
+ ts-node:
+ specifier: ^10.9.2
+ version: 10.9.2(@types/node@18.18.14)(typescript@5.4.5)
+ typescript:
+ specifier: ^5.3.3
+ version: 5.4.5
+
packages/build-lz-options:
dependencies:
yoga-layout-prebuilt:
@@ -937,6 +1045,114 @@ importers:
specifier: ^3.22.4
version: 3.22.4
+ packages/onft-evm:
+ devDependencies:
+ '@babel/core':
+ specifier: ^7.23.9
+ version: 7.23.9
+ '@layerzerolabs/eslint-config-next':
+ specifier: ~2.3.3
+ version: 2.3.3(typescript@5.4.5)
+ '@layerzerolabs/lz-definitions':
+ specifier: ^2.3.25
+ version: 2.3.25
+ '@layerzerolabs/lz-evm-messagelib-v2':
+ specifier: ^2.3.25
+ version: 2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2)
+ '@layerzerolabs/lz-evm-oapp-v2':
+ specifier: ^2.3.25
+ version: 2.3.25(@layerzerolabs/lz-evm-messagelib-v2@2.3.25)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2)
+ '@layerzerolabs/lz-evm-protocol-v2':
+ specifier: ^2.3.25
+ version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2)
+ '@layerzerolabs/lz-evm-v1-0.7':
+ specifier: ^2.3.25
+ version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)
+ '@layerzerolabs/lz-v2-utilities':
+ specifier: ^2.3.25
+ version: 2.3.25
+ '@layerzerolabs/prettier-config-next':
+ specifier: ^2.3.25
+ version: 2.3.25
+ '@layerzerolabs/solhint-config':
+ specifier: ^2.3.3
+ version: 2.3.3(typescript@5.4.5)
+ '@layerzerolabs/test-devtools-evm-foundry':
+ specifier: ~0.2.7
+ version: link:../test-devtools-evm-foundry
+ '@layerzerolabs/toolbox-foundry':
+ specifier: ~0.1.7
+ version: link:../toolbox-foundry
+ '@layerzerolabs/toolbox-hardhat':
+ specifier: ~0.2.35
+ version: link:../toolbox-hardhat
+ '@nomicfoundation/hardhat-ethers':
+ specifier: ^3.0.5
+ version: 3.0.5(ethers@5.7.2)(hardhat@2.22.3)
+ '@nomiclabs/hardhat-ethers':
+ specifier: ^2.2.3
+ version: 2.2.3(ethers@5.7.2)(hardhat@2.22.3)
+ '@openzeppelin/contracts':
+ specifier: ^5.0.2
+ version: 5.0.2
+ '@openzeppelin/contracts-upgradeable':
+ specifier: ^5.0.2
+ version: 5.0.2(@openzeppelin/contracts@5.0.2)
+ '@rushstack/eslint-patch':
+ specifier: ^1.7.0
+ version: 1.7.0
+ '@types/chai':
+ specifier: ^4.3.11
+ version: 4.3.11
+ '@types/mocha':
+ specifier: ^10.0.6
+ version: 10.0.6
+ '@types/node':
+ specifier: ~18.18.14
+ version: 18.18.14
+ chai:
+ specifier: ^4.4.1
+ version: 4.4.1
+ dotenv:
+ specifier: ^16.4.1
+ version: 16.4.5
+ erc721a:
+ specifier: ^4.3.0
+ version: 4.3.0
+ eslint-plugin-jest-extended:
+ specifier: ~2.0.0
+ version: 2.0.0(eslint@8.57.0)(typescript@5.4.5)
+ ethers:
+ specifier: ^5.7.2
+ version: 5.7.2
+ hardhat:
+ specifier: ^2.22.3
+ version: 2.22.3(ts-node@10.9.2)(typescript@5.4.5)
+ hardhat-contract-sizer:
+ specifier: ^2.10.0
+ version: 2.10.0(hardhat@2.22.3)
+ hardhat-deploy:
+ specifier: ^0.12.1
+ version: 0.12.4
+ mocha:
+ specifier: ^10.2.0
+ version: 10.2.0
+ prettier:
+ specifier: ^3.2.5
+ version: 3.2.5
+ solhint:
+ specifier: ^4.1.1
+ version: 4.1.1(typescript@5.4.5)
+ solidity-bytes-utils:
+ specifier: ^0.8.2
+ version: 0.8.2
+ ts-node:
+ specifier: ^10.9.2
+ version: 10.9.2(@types/node@18.18.14)(typescript@5.4.5)
+ typescript:
+ specifier: ^5.3.3
+ version: 5.4.5
+
packages/protocol-devtools:
devDependencies:
'@layerzerolabs/devtools':
@@ -3907,6 +4123,26 @@ packages:
- typescript
dev: true
+ /@layerzerolabs/eslint-config-next@2.3.3(typescript@5.4.5):
+ resolution: {integrity: sha512-zeq0LZkSPI2tm6M1fRmmPA6gzTAUg3rux074FV+8N+PyC53/DhPUHT/DYvTHl0/+F04nFp8MD+yYSHReVIQLNQ==}
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5)
+ '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
+ eslint: 8.57.0
+ eslint-config-prettier: 9.1.0(eslint@8.57.0)
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.1)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-prettier: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5)
+ eslint-plugin-unused-imports: 3.1.0(@typescript-eslint/eslint-plugin@7.7.1)(eslint@8.57.0)
+ prettier: 3.2.5
+ transitivePeerDependencies:
+ - '@types/eslint'
+ - eslint-import-resolver-node
+ - eslint-import-resolver-webpack
+ - supports-color
+ - typescript
+ dev: true
+
/@layerzerolabs/evm-sdks-core@2.3.25:
resolution: {integrity: sha512-2qUeEVMnYFnj3MrEWGmrXKn249ohfvjSyDsjivJJbwu9rlxsk/zclb+May1SCO0UcbPfgILVkldZoL4LyqrwnA==}
dependencies:
@@ -4339,6 +4575,14 @@ packages:
- typescript
dev: true
+ /@layerzerolabs/solhint-config@2.3.3(typescript@5.4.5):
+ resolution: {integrity: sha512-sL2W+kbZRPFKnLDn8A5hQX796gFi8sEVJFRkQbbpFrtB/KGCPpWK/w8JjU7OoOzOvJF9pQnJGS1ayUjDJKCb0Q==}
+ dependencies:
+ solhint: 4.1.1(typescript@5.4.5)
+ transitivePeerDependencies:
+ - typescript
+ dev: true
+
/@manypkg/find-root@1.1.0:
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
dependencies:
@@ -5439,6 +5683,35 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5):
+ resolution: {integrity: sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==}
+ engines: {node: ^18.18.0 || >=20.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^7.0.0
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@eslint-community/regexpp': 4.10.0
+ '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
+ '@typescript-eslint/scope-manager': 7.7.1
+ '@typescript-eslint/type-utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
+ '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
+ '@typescript-eslint/visitor-keys': 7.7.1
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ natural-compare: 1.4.0
+ semver: 7.6.0
+ ts-api-utils: 1.3.0(typescript@5.4.5)
+ typescript: 5.4.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3):
resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -5481,6 +5754,27 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5):
+ resolution: {integrity: sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==}
+ engines: {node: ^18.18.0 || >=20.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/scope-manager': 7.7.1
+ '@typescript-eslint/types': 7.7.1
+ '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
+ '@typescript-eslint/visitor-keys': 7.7.1
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ typescript: 5.4.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/scope-manager@5.62.0:
resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -5525,7 +5819,27 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.3.3):
+ /@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==}
+ engines: {node: ^18.18.0 || >=20.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.3.3)
+ '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.3.3)
+ debug: 4.3.4(supports-color@8.1.1)
+ eslint: 8.57.0
+ ts-api-utils: 1.3.0(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -5535,12 +5849,12 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.3.3)
- '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.3.3)
+ '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
+ '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.57.0
- ts-api-utils: 1.3.0(typescript@5.3.3)
- typescript: 5.3.3
+ ts-api-utils: 1.3.0(typescript@5.4.5)
+ typescript: 5.4.5
transitivePeerDependencies:
- supports-color
dev: true
@@ -5581,6 +5895,27 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5):
+ resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/visitor-keys': 5.62.0
+ debug: 4.3.4(supports-color@8.1.1)
+ globby: 11.1.0
+ is-glob: 4.0.3
+ semver: 7.6.0
+ tsutils: 3.21.0(typescript@5.4.5)
+ typescript: 5.4.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.3):
resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -5646,6 +5981,28 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/typescript-estree@7.7.1(typescript@5.4.5):
+ resolution: {integrity: sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==}
+ engines: {node: ^18.18.0 || >=20.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 7.7.1
+ '@typescript-eslint/visitor-keys': 7.7.1
+ debug: 4.3.4(supports-color@8.1.1)
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.4
+ semver: 7.6.0
+ ts-api-utils: 1.3.0(typescript@5.4.5)
+ typescript: 5.4.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/utils@5.62.0(eslint@8.56.0)(typescript@5.5.3):
resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -5686,6 +6043,26 @@ packages:
- typescript
dev: true
+ /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.4.5):
+ resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.6
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5)
+ eslint: 8.57.0
+ eslint-scope: 5.1.1
+ semver: 7.5.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
/@typescript-eslint/utils@6.21.0(eslint@8.56.0)(typescript@5.5.3):
resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -5724,6 +6101,25 @@ packages:
- typescript
dev: true
+ /@typescript-eslint/utils@7.7.1(eslint@8.57.0)(typescript@5.4.5):
+ resolution: {integrity: sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==}
+ engines: {node: ^18.18.0 || >=20.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.8
+ '@typescript-eslint/scope-manager': 7.7.1
+ '@typescript-eslint/types': 7.7.1
+ '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
+ eslint: 8.57.0
+ semver: 7.6.0
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
/@typescript-eslint/visitor-keys@5.62.0:
resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -6848,6 +7244,22 @@ packages:
typescript: 5.3.3
dev: true
+ /cosmiconfig@8.3.6(typescript@5.4.5):
+ resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ typescript: 5.4.5
+ dev: true
+
/crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
@@ -7285,6 +7697,10 @@ packages:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
+ /erc721a@4.3.0:
+ resolution: {integrity: sha512-JneTLVEH5I/rJMArAwwJ26CX4HvL3ql9Q3egpQ4QB7sz7vIGZbMdMmqHLBIIKhBlfToZKewtC7OZtdr3qU6jsA==}
+ dev: true
+
/error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
dependencies:
@@ -7562,7 +7978,7 @@ packages:
debug: 4.3.4(supports-color@8.1.1)
enhanced-resolve: 5.16.0
eslint: 8.57.0
- eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.3
@@ -7604,37 +8020,7 @@ packages:
- supports-color
dev: true
- /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
- resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
- engines: {node: '>=4'}
- peerDependencies:
- '@typescript-eslint/parser': '*'
- eslint: '*'
- eslint-import-resolver-node: '*'
- eslint-import-resolver-typescript: '*'
- eslint-import-resolver-webpack: '*'
- peerDependenciesMeta:
- '@typescript-eslint/parser':
- optional: true
- eslint:
- optional: true
- eslint-import-resolver-node:
- optional: true
- eslint-import-resolver-typescript:
- optional: true
- eslint-import-resolver-webpack:
- optional: true
- dependencies:
- '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.3.3)
- debug: 3.2.7
- eslint: 8.57.0
- eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.1)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+ /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
engines: {node: '>=4'}
peerDependencies:
@@ -7658,6 +8044,7 @@ packages:
'@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.3.3)
debug: 3.2.7
eslint: 8.57.0
+ eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.1)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
@@ -7740,8 +8127,8 @@ packages:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
- hasown: 2.0.0
+ eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ hasown: 2.0.1
is-core-module: 2.13.1
is-glob: 4.0.3
minimatch: 3.1.2
@@ -7769,6 +8156,19 @@ packages:
- typescript
dev: true
+ /eslint-plugin-jest-extended@2.0.0(eslint@8.57.0)(typescript@5.4.5):
+ resolution: {integrity: sha512-nMhVVsVcG/+Q6FMshql35WBxwx8xlBhxKgAG08WP3BYWfXrp28oxLpJVu9JSbMpfmfKGVrHwMYJGfPLRKlGB8w==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ dependencies:
+ '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
+ eslint: 8.57.0
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
/eslint-plugin-jest@27.6.3(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0)(typescript@5.5.3):
resolution: {integrity: sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -8263,6 +8663,7 @@ packages:
dependencies:
is-hex-prefixed: 1.0.0
strip-hex-prefix: 1.0.0
+ bundledDependencies: false
/eventemitter3@4.0.4:
resolution: {integrity: sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==}
@@ -9246,6 +9647,70 @@ packages:
- utf-8-validate
dev: true
+ /hardhat@2.22.3(ts-node@10.9.2)(typescript@5.4.5):
+ resolution: {integrity: sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA==}
+ hasBin: true
+ peerDependencies:
+ ts-node: '*'
+ typescript: '*'
+ peerDependenciesMeta:
+ ts-node:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ '@ethersproject/abi': 5.7.0
+ '@metamask/eth-sig-util': 4.0.1
+ '@nomicfoundation/edr': 0.3.5
+ '@nomicfoundation/ethereumjs-common': 4.0.4
+ '@nomicfoundation/ethereumjs-tx': 5.0.4
+ '@nomicfoundation/ethereumjs-util': 9.0.4
+ '@nomicfoundation/solidity-analyzer': 0.1.1
+ '@sentry/node': 5.30.0
+ '@types/bn.js': 5.1.5
+ '@types/lru-cache': 5.1.1
+ adm-zip: 0.4.16
+ aggregate-error: 3.1.0
+ ansi-escapes: 4.3.2
+ boxen: 5.1.2
+ chalk: 2.4.2
+ chokidar: 3.6.0
+ ci-info: 2.0.0
+ debug: 4.3.4(supports-color@8.1.1)
+ enquirer: 2.4.1
+ env-paths: 2.2.1
+ ethereum-cryptography: 1.2.0
+ ethereumjs-abi: 0.6.8
+ find-up: 2.1.0
+ fp-ts: 1.19.3
+ fs-extra: 7.0.1
+ glob: 7.2.0
+ immutable: 4.3.4
+ io-ts: 1.10.4
+ keccak: 3.0.4
+ lodash: 4.17.21
+ mnemonist: 0.38.5
+ mocha: 10.2.0
+ p-map: 4.0.0
+ raw-body: 2.5.2
+ resolve: 1.17.0
+ semver: 6.3.1
+ solc: 0.7.3(debug@4.3.4)
+ source-map-support: 0.5.21
+ stacktrace-parser: 0.1.10
+ ts-node: 10.9.2(@types/node@18.18.14)(typescript@5.4.5)
+ tsort: 0.0.1
+ typescript: 5.4.5
+ undici: 5.28.2
+ uuid: 8.3.2
+ ws: 7.5.9
+ transitivePeerDependencies:
+ - bufferutil
+ - c-kzg
+ - supports-color
+ - utf-8-validate
+ dev: true
+
/has-bigints@1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
dev: true
@@ -12509,7 +12974,35 @@ packages:
pluralize: 8.0.0
semver: 7.6.0
strip-ansi: 6.0.1
- table: 6.8.1
+ table: 6.8.2
+ text-table: 0.2.0
+ optionalDependencies:
+ prettier: 2.8.8
+ transitivePeerDependencies:
+ - typescript
+ dev: true
+
+ /solhint@4.1.1(typescript@5.4.5):
+ resolution: {integrity: sha512-7G4iF8H5hKHc0tR+/uyZesSKtfppFIMvPSW+Ku6MSL25oVRuyFeqNhOsXHfkex64wYJyXs4fe+pvhB069I19Tw==}
+ hasBin: true
+ dependencies:
+ '@solidity-parser/parser': 0.16.2
+ ajv: 6.12.6
+ antlr4: 4.13.1
+ ast-parents: 0.0.1
+ chalk: 4.1.2
+ commander: 10.0.1
+ cosmiconfig: 8.3.6(typescript@5.4.5)
+ fast-diff: 1.3.0
+ glob: 8.1.0
+ ignore: 5.3.1
+ js-yaml: 4.1.0
+ latest-version: 7.0.0
+ lodash: 4.17.21
+ pluralize: 8.0.0
+ semver: 7.6.0
+ strip-ansi: 6.0.1
+ table: 6.8.2
text-table: 0.2.0
optionalDependencies:
prettier: 2.8.8
@@ -12861,17 +13354,6 @@ packages:
tslib: 2.6.3
dev: true
- /table@6.8.1:
- resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==}
- engines: {node: '>=10.0.0'}
- dependencies:
- ajv: 8.12.0
- lodash.truncate: 4.4.2
- slice-ansi: 4.0.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
- dev: true
-
/table@6.8.2:
resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==}
engines: {node: '>=10.0.0'}
@@ -12881,7 +13363,6 @@ packages:
slice-ansi: 4.0.0
string-width: 4.2.3
strip-ansi: 6.0.1
- dev: false
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
@@ -13074,6 +13555,15 @@ packages:
typescript: 5.3.3
dev: true
+ /ts-api-utils@1.3.0(typescript@5.4.5):
+ resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ typescript: '>=4.2.0'
+ dependencies:
+ typescript: 5.4.5
+ dev: true
+
/ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
@@ -13361,6 +13851,16 @@ packages:
typescript: 5.3.3
dev: true
+ /tsutils@3.21.0(typescript@5.4.5):
+ resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+ engines: {node: '>= 6'}
+ peerDependencies:
+ typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+ dependencies:
+ tslib: 1.14.1
+ typescript: 5.4.5
+ dev: true
+
/tsutils@3.21.0(typescript@5.5.3):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
diff --git a/tests-user/tests/create-lz-oapp.bats b/tests-user/tests/create-lz-oapp.bats
index c2a93ca92..df447a1e4 100644
--- a/tests-user/tests/create-lz-oapp.bats
+++ b/tests-user/tests/create-lz-oapp.bats
@@ -136,6 +136,17 @@ teardown() {
pnpm lint:fix
}
+@test "should work with pnpm & onft721 example in CI mode" {
+ local DESTINATION="$PROJECTS_DIRECTORY/pnpm-onft721"
+
+ npx --yes create-lz-oapp --ci --example onft721 --destination $DESTINATION --package-manager pnpm
+ cd "$DESTINATION"
+ pnpm compile
+ pnpm test
+ pnpm lint
+ pnpm lint:fix
+}
+
@test "should work with yarn & oapp example in CI mode" {
local DESTINATION="$PROJECTS_DIRECTORY/yarn-oapp"
@@ -158,6 +169,17 @@ teardown() {
yarn lint:fix
}
+@test "should work with yarn & onft721 example in CI mode" {
+ local DESTINATION="$PROJECTS_DIRECTORY/yarn-oapp"
+
+ npx --yes create-lz-oapp --ci --example onft721 --destination $DESTINATION --package-manager yarn
+ cd "$DESTINATION"
+ yarn compile
+ yarn test
+ yarn lint
+ yarn lint:fix
+}
+
@test "should work with npm & oapp example in CI mode" {
local DESTINATION="$PROJECTS_DIRECTORY/npm-oapp"
@@ -178,4 +200,15 @@ teardown() {
npm run test
npm run lint
npm run lint:fix
-}
\ No newline at end of file
+}
+
+@test "should work with npm & onft721 example in CI mode" {
+ local DESTINATION="$PROJECTS_DIRECTORY/npm-oft"
+
+ npx --yes create-lz-oapp --ci --example onft721 --destination $DESTINATION --package-manager npm
+ cd "$DESTINATION"
+ npm run compile
+ npm run test
+ npm run lint
+ npm run lint:fix
+}