├── .gitignore ├── README.md ├── contracts ├── ERC7007Enumerable.sol ├── ERC7007Opml.sol ├── ERC7007Zkml.sol ├── IERC7007.sol ├── IERC7007Enumerable.sol ├── IERC7007Updatable.sol ├── IOpmlLib.sol ├── IVerifier.sol ├── MockOpmlLib.sol └── MockVerifier.sol ├── hardhat.config.js ├── package-lock.json ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ERC-7007 Reference Implementation 2 | 3 | This is a WIP implementation of ERC-7007 based on the discussions in the [EIP-7007 issue thread](https://github.com/ethereum/EIPs/issues/7007). 4 | 5 | ## Setup 6 | Run `npm install` in the root directory. 7 | 8 | ## Testing 9 | Try running some of the following tasks: 10 | 11 | ```shell 12 | npx hardhat help 13 | npx hardhat test 14 | REPORT_GAS=true npx hardhat test 15 | ``` 16 | 17 | ## Metadata Standard 18 | 19 | ```json 20 | { 21 | "title": "AIGC Metadata", 22 | "type": "object", 23 | "properties": { 24 | "name": { 25 | "type": "string", 26 | "description": "Identifies the asset to which this NFT represents" 27 | }, 28 | "description": { 29 | "type": "string", 30 | "description": "Describes the asset to which this NFT represents" 31 | }, 32 | "image": { 33 | "type": "string", 34 | "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." 35 | }, 36 | "prompt": { 37 | "type": "string", 38 | "description": "Identifies the prompt from which this AIGC NFT generated" 39 | }, 40 | "seed": { 41 | "type": "uint256", 42 | "description": "Identifies the seed from which this AIGC NFT generated" 43 | }, 44 | "aigc_type": { 45 | "type": "string", 46 | "description": "image/video/audio..." 47 | }, 48 | "aigc_data": { 49 | "type": "string", 50 | "description": "A URI pointing to a resource with mime type image/* representing the asset to which this AIGC NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." 51 | }, 52 | "proof_type": { 53 | "type": "string", 54 | "description": "validity (zkML) or fraud (opML)" 55 | } 56 | } 57 | } 58 | ``` -------------------------------------------------------------------------------- /contracts/ERC7007Enumerable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "./ERC7007Zkml.sol"; 5 | import "./IERC7007Enumerable.sol"; 6 | 7 | /** 8 | * @dev Implementation of the {IERC7007Enumerable} interface. 9 | */ 10 | abstract contract ERC7007Enumerable is ERC7007Zkml, IERC7007Enumerable { 11 | /** 12 | * @dev See {IERC7007Enumerable-tokenId}. 13 | */ 14 | mapping(uint256 => string) public prompt; 15 | 16 | 17 | /** 18 | * @dev See {IERC7007Enumerable-prompt}. 19 | */ 20 | mapping(bytes => uint256) public tokenId; 21 | 22 | /** 23 | * @dev See {IERC165-supportsInterface}. 24 | */ 25 | function supportsInterface( 26 | bytes4 interfaceId 27 | ) public view virtual override(IERC165, ERC7007Zkml) returns (bool) { 28 | return 29 | interfaceId == type(IERC7007Enumerable).interfaceId || 30 | super.supportsInterface(interfaceId); 31 | } 32 | 33 | function mint( 34 | address to, 35 | bytes calldata prompt_, 36 | bytes calldata aigcData, 37 | string calldata uri, 38 | bytes calldata proof 39 | ) public virtual override returns (uint256 tokenId_) { 40 | tokenId_ = ERC7007Zkml.mint(to, prompt_, aigcData, uri, proof); 41 | prompt[tokenId_] = string(prompt_); 42 | tokenId[prompt_] = tokenId_; 43 | } 44 | } 45 | 46 | contract MockERC7007Enumerable is ERC7007Enumerable { 47 | constructor( 48 | string memory name_, 49 | string memory symbol_, 50 | address verifier_ 51 | ) ERC7007Zkml(name_, symbol_, verifier_) {} 52 | } -------------------------------------------------------------------------------- /contracts/ERC7007Opml.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 6 | import "./IERC7007Updatable.sol"; 7 | import "./IOpmlLib.sol"; 8 | 9 | /** 10 | * @dev Implementation of the {IERC7007} interface. 11 | */ 12 | contract ERC7007Opml is ERC165, IERC7007Updatable, ERC721URIStorage { 13 | address public immutable opmlLib; 14 | mapping (uint256 => uint256) public tokenIdToRequestId; 15 | 16 | /** 17 | * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. 18 | */ 19 | constructor( 20 | string memory name_, 21 | string memory symbol_, 22 | address opmlLib_ 23 | ) ERC721(name_, symbol_) { 24 | opmlLib = opmlLib_; 25 | } 26 | 27 | function mint( 28 | address to, 29 | bytes calldata prompt, 30 | bytes calldata aigcData, 31 | string calldata uri, 32 | bytes calldata proof 33 | ) public returns (uint256 tokenId) { 34 | tokenId = uint256(keccak256(prompt)); 35 | _safeMint(to, tokenId); 36 | string memory tokenUri = string( 37 | abi.encodePacked( 38 | "{", 39 | uri, 40 | ', "prompt": "', 41 | string(prompt), 42 | '", "aigc_data": "', 43 | string(aigcData), 44 | '"}' 45 | ) 46 | ); 47 | _setTokenURI(tokenId, tokenUri); 48 | addAigcData(tokenId, prompt, aigcData, proof); 49 | } 50 | 51 | /** 52 | * @dev See {IERC7007-addAigcData}. 53 | */ 54 | function addAigcData( 55 | uint256 tokenId, 56 | bytes calldata prompt, 57 | bytes calldata aigcData, 58 | bytes calldata proof 59 | ) public virtual override { 60 | require(ownerOf(tokenId) != address(0), "ERC7007: nonexistent token"); 61 | require(tokenIdToRequestId[tokenId] == 0, "ERC7007: requestId already exists"); 62 | tokenIdToRequestId[tokenId] = IOpmlLib(opmlLib).initOpmlRequest(prompt); 63 | IOpmlLib(opmlLib).uploadResult(tokenIdToRequestId[tokenId], aigcData); 64 | emit AigcData(tokenId, prompt, aigcData, proof); 65 | } 66 | 67 | /** 68 | * @dev See {IERC7007-verify}. 69 | */ 70 | function verify( 71 | bytes calldata prompt, 72 | bytes calldata aigcData, 73 | bytes calldata proof 74 | ) public view virtual override returns (bool success) { 75 | uint256 tokenId = uint256(keccak256(prompt)); 76 | bytes memory output = IOpmlLib(opmlLib).getOutput(tokenIdToRequestId[tokenId]); 77 | 78 | return IOpmlLib(opmlLib).isFinalized(tokenIdToRequestId[tokenId]) && (keccak256(output) == keccak256(aigcData)); 79 | } 80 | 81 | /** 82 | * @dev See {IERC7007Updatable-update}. 83 | */ 84 | function update( 85 | bytes calldata prompt, 86 | bytes calldata aigcData 87 | ) public virtual override { 88 | require(verify(prompt, aigcData, prompt), "ERC7007: invalid aigcData"); // proof argument is not used in verify() function for opML, so we can pass prompt as proof 89 | uint256 tokenId = uint256(keccak256(prompt)); 90 | require(ownerOf(tokenId) != address(0), "ERC7007: nonexistent token"); 91 | // TODO: should update tokenURI with new aigcData 92 | emit Update(tokenId, prompt, aigcData); 93 | } 94 | 95 | /** 96 | * @dev See {IERC165-supportsInterface}. 97 | */ 98 | function supportsInterface( 99 | bytes4 interfaceId 100 | ) public view virtual override(ERC165, ERC721, IERC165) returns (bool) { 101 | return 102 | interfaceId == type(IERC721).interfaceId || 103 | interfaceId == type(IERC721Metadata).interfaceId || 104 | super.supportsInterface(interfaceId); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /contracts/ERC7007Zkml.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 6 | import "./IERC7007.sol"; 7 | import "./IVerifier.sol"; 8 | 9 | /** 10 | * @dev Implementation of the {IERC7007} interface. 11 | */ 12 | contract ERC7007Zkml is ERC165, IERC7007, ERC721URIStorage { 13 | address public immutable verifier; 14 | 15 | /** 16 | * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. 17 | */ 18 | constructor( 19 | string memory name_, 20 | string memory symbol_, 21 | address verifier_ 22 | ) ERC721(name_, symbol_) { 23 | verifier = verifier_; 24 | } 25 | 26 | function mint( 27 | address to, 28 | bytes calldata prompt, 29 | bytes calldata aigcData, 30 | string calldata uri, 31 | bytes calldata proof 32 | ) public virtual returns (uint256 tokenId) { 33 | tokenId = uint256(keccak256(prompt)); 34 | _safeMint(to, tokenId); 35 | addAigcData(tokenId, prompt, aigcData, proof); 36 | 37 | string memory tokenUri = string( 38 | abi.encodePacked( 39 | "{", 40 | uri, 41 | ', "prompt": "', 42 | string(prompt), 43 | '", "aigc_data": "', 44 | string(aigcData), 45 | '"}' 46 | ) 47 | ); 48 | _setTokenURI(tokenId, tokenUri); 49 | } 50 | 51 | /** 52 | * @dev See {IERC7007-addAigcData}. 53 | */ 54 | function addAigcData( 55 | uint256 tokenId, 56 | bytes calldata prompt, 57 | bytes calldata aigcData, 58 | bytes calldata proof 59 | ) public virtual override { 60 | require(ownerOf(tokenId) != address(0), "ERC7007: nonexistent token"); 61 | require(verify(prompt, aigcData, proof), "ERC7007: invalid proof"); 62 | emit AigcData(tokenId, prompt, aigcData, proof); 63 | } 64 | 65 | /** 66 | * @dev See {IERC7007-verify}. 67 | */ 68 | function verify( 69 | bytes calldata prompt, 70 | bytes calldata aigcData, 71 | bytes calldata proof 72 | ) public view virtual override returns (bool success) { 73 | return 74 | IVerifier(verifier).verifyProof( 75 | proof, 76 | abi.encodePacked(prompt, aigcData) 77 | ); 78 | } 79 | 80 | /** 81 | * @dev See {IERC165-supportsInterface}. 82 | */ 83 | function supportsInterface( 84 | bytes4 interfaceId 85 | ) public view virtual override(ERC165, ERC721, IERC165) returns (bool) { 86 | return 87 | interfaceId == type(IERC721).interfaceId || 88 | interfaceId == type(IERC721Metadata).interfaceId || 89 | super.supportsInterface(interfaceId); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /contracts/IERC7007.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | /** 8 | * @dev Required interface of an ERC7007 compliant contract. 9 | */ 10 | interface IERC7007 is IERC165, IERC721 { 11 | /** 12 | * @dev Emitted when AI Generated Content (AIGC) data is added to token at `tokenId`. 13 | */ 14 | event AigcData( 15 | uint256 indexed tokenId, 16 | bytes indexed prompt, 17 | bytes aigcData, 18 | bytes proof 19 | ); 20 | 21 | /** 22 | * @dev Add AIGC data to token at `tokenId` given `prompt`, `aigcData` and `proof`. 23 | * 24 | * Optional: 25 | * - `proof` should not include `aigcData` to save gas. 26 | * - verify(`prompt`, `aigcData`, `proof`) should return true for zkML scenario. 27 | */ 28 | function addAigcData( 29 | uint256 tokenId, 30 | bytes calldata prompt, 31 | bytes calldata aigcData, 32 | bytes calldata proof 33 | ) external; 34 | 35 | /** 36 | * @dev Verify the `prompt`, `aigcData` and `proof`. 37 | */ 38 | function verify( 39 | bytes calldata prompt, 40 | bytes calldata aigcData, 41 | bytes calldata proof 42 | ) external view returns (bool success); 43 | } 44 | -------------------------------------------------------------------------------- /contracts/IERC7007Enumerable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "./IERC7007.sol"; 5 | 6 | /** 7 | * @title ERC7007 Token Standard, optional enumeration extension 8 | */ 9 | interface IERC7007Enumerable is IERC7007 { 10 | /** 11 | * @dev Returns the token ID given `prompt`. 12 | */ 13 | function tokenId(bytes calldata prompt) external view returns (uint256); 14 | 15 | /** 16 | * @dev Returns the prompt given `tokenId`. 17 | */ 18 | function prompt(uint256 tokenId) external view returns (string calldata); 19 | } 20 | -------------------------------------------------------------------------------- /contracts/IERC7007Updatable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "./IERC7007.sol"; 5 | 6 | /** 7 | * @title ERC7007 Token Standard, optional updatable extension 8 | */ 9 | interface IERC7007Updatable is IERC7007 { 10 | /** 11 | * @dev Update the `aigcData` of `prompt`. 12 | */ 13 | function update( 14 | bytes calldata prompt, 15 | bytes calldata aigcData 16 | ) external; 17 | 18 | /** 19 | * @dev Emitted when `tokenId` token is updated. 20 | */ 21 | event Update( 22 | uint256 indexed tokenId, 23 | bytes indexed prompt, 24 | bytes indexed aigcData 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/IOpmlLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | interface IOpmlLib { 5 | function initOpmlRequest(bytes calldata input) external returns (uint256 requestId); // we can construct the initialState using the input, can replace input with prompt (string) 6 | function uploadResult(uint256 requestId, bytes calldata output) external; 7 | function startChallenge(uint256 requestId, bytes32 finalState) external returns (uint256 challengeId); 8 | function respondState(uint256 challengeId, bytes32 stateHash) external; 9 | function proposeState(uint256 challengeId, bytes32 stateHash) external; 10 | function assertStateTransition(uint256 challengeId) external; 11 | function isFinalized(uint256 requestId) external view returns (bool); 12 | function getOutput(uint256 requestId) external view returns (bytes memory output); 13 | } -------------------------------------------------------------------------------- /contracts/IVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | interface IVerifier { 5 | function verifyProof( 6 | bytes calldata proof, 7 | bytes calldata public_inputs 8 | ) external view returns (bool valid); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/MockOpmlLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "./IOpmlLib.sol"; 5 | contract MockOpmlLib is IOpmlLib { 6 | uint256 public currentRequestId; 7 | mapping (uint256 => bytes) public requestIdToOutput; 8 | 9 | function initOpmlRequest(bytes calldata input) 10 | external 11 | override 12 | returns (uint256 requestId) 13 | { 14 | return currentRequestId++; 15 | } 16 | 17 | function uploadResult(uint256 requestId, bytes calldata output) 18 | external 19 | override 20 | { 21 | requestIdToOutput[requestId] = output; 22 | } 23 | 24 | function startChallenge(uint256 requestId, bytes32 finalState) 25 | external 26 | override 27 | returns (uint256 challengeId) 28 | { 29 | return 0; 30 | } 31 | 32 | function respondState(uint256 challengeId, bytes32 stateHash) 33 | external 34 | override 35 | {} 36 | 37 | function proposeState(uint256 challengeId, bytes32 stateHash) 38 | external 39 | override 40 | {} 41 | 42 | function assertStateTransition(uint256 challengeId) external override {} 43 | 44 | function isFinalized(uint256 requestId) 45 | external 46 | view 47 | override 48 | returns (bool) 49 | { 50 | return true; 51 | } 52 | 53 | function getOutput(uint256 requestId) 54 | external 55 | view 56 | override 57 | returns (bytes memory output) 58 | { 59 | return requestIdToOutput[requestId]; 60 | } 61 | } -------------------------------------------------------------------------------- /contracts/MockVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "./IVerifier.sol"; 5 | 6 | contract MockVerifier is IVerifier { 7 | function verifyProof( 8 | bytes calldata proof, 9 | bytes calldata public_inputs 10 | ) external pure override returns (bool valid) { 11 | return 12 | public_inputs.length > 0 && keccak256(proof) == keccak256("valid"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | 3 | /** @type import('hardhat/config').HardhatUserConfig */ 4 | module.exports = { 5 | solidity: "0.8.18", 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "erc7007", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@nomicfoundation/hardhat-toolbox": "^2.0.2", 14 | "@openzeppelin/contracts": "^4.8.3", 15 | "hardhat": "^2.14.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { BigNumber } = require("ethers"); 3 | 4 | async function deployVerifierFixture() { 5 | const Verifier = await ethers.getContractFactory("MockVerifier"); 6 | const verifier = await Verifier.deploy(); 7 | await verifier.deployed(); 8 | return verifier; 9 | } 10 | const prompt = ethers.utils.toUtf8Bytes("test"); 11 | const aigcData = ethers.utils.toUtf8Bytes("test"); 12 | const uri = '"name": "test", "description": "test", "image": "test", "aigc_type": "test", "proof_type": "test"'; 13 | const validProof = ethers.utils.toUtf8Bytes("valid"); 14 | const invalidProof = ethers.utils.toUtf8Bytes("invalid"); 15 | const tokenId = BigNumber.from("70622639689279718371527342103894932928233838121221666359043189029713682937432"); 16 | 17 | describe("ERC7007Zkml.sol", function () { 18 | 19 | async function deployERC7007Fixture() { 20 | const verifier = await deployVerifierFixture(); 21 | 22 | const ERC7007 = await ethers.getContractFactory("ERC7007Zkml"); 23 | const erc7007 = await ERC7007.deploy("testing", "TEST", verifier.address); 24 | await erc7007.deployed(); 25 | return erc7007; 26 | } 27 | 28 | describe("mint", function () { 29 | it("should mint a token", async function () { 30 | const erc7007 = await deployERC7007Fixture(); 31 | const [owner] = await ethers.getSigners(); 32 | await erc7007.mint(owner.address, prompt, aigcData, uri, validProof); 33 | expect(await erc7007.balanceOf(owner.address)).to.equal(1); 34 | }); 35 | 36 | it("should not mint a token with invalid proof", async function () { 37 | const erc7007 = await deployERC7007Fixture(); 38 | const [owner] = await ethers.getSigners(); 39 | await expect(erc7007.mint(owner.address, prompt, aigcData, uri, invalidProof)).to.be.revertedWith("ERC7007: invalid proof"); 40 | }); 41 | 42 | it("should not mint a token with same data twice", async function () { 43 | const erc7007 = await deployERC7007Fixture(); 44 | const [owner] = await ethers.getSigners(); 45 | await erc7007.mint(owner.address, prompt, aigcData, uri, validProof); 46 | await expect(erc7007.mint(owner.address, prompt, aigcData, uri, validProof)).to.be.revertedWith("ERC721: token already minted"); 47 | }); 48 | 49 | it("should emit a AigcData event", async function () { 50 | const erc7007 = await deployERC7007Fixture(); 51 | const [owner] = await ethers.getSigners(); 52 | await expect(erc7007.mint(owner.address, prompt, aigcData, uri, validProof)) 53 | .to.emit(erc7007, "AigcData") 54 | }); 55 | }); 56 | 57 | describe("metadata", function () { 58 | it("should return token metadata", async function () { 59 | const erc7007 = await deployERC7007Fixture(); 60 | const [owner] = await ethers.getSigners(); 61 | await erc7007.mint(owner.address, prompt, aigcData, uri, validProof); 62 | expect(await erc7007.tokenURI(tokenId)).to.equal('{"name": "test", "description": "test", "image": "test", "aigc_type": "test", "proof_type": "test", "prompt": "test", "aigc_data": "test"}'); 63 | }); 64 | }); 65 | }); 66 | 67 | describe("ERC7007Enumerable.sol", function () { 68 | 69 | async function deployERC7007EnumerableFixture() { 70 | const verifier = await deployVerifierFixture(); 71 | const [owner] = await ethers.getSigners(); 72 | 73 | const ERC7007Enumerable = await ethers.getContractFactory("MockERC7007Enumerable"); 74 | const erc7007Enumerable = await ERC7007Enumerable.deploy("testing", "TEST", verifier.address); 75 | await erc7007Enumerable.deployed(); 76 | 77 | await erc7007Enumerable.mint(owner.address, prompt, aigcData, uri, validProof); 78 | return erc7007Enumerable; 79 | } 80 | 81 | it("should return token id by prompt", async function () { 82 | const erc7007Enumerable = await deployERC7007EnumerableFixture(); 83 | expect(await erc7007Enumerable.tokenId(prompt)).to.equal(tokenId); 84 | }); 85 | 86 | it("should return token prompt by id", async function () { 87 | const erc7007Enumerable = await deployERC7007EnumerableFixture(); 88 | expect(await erc7007Enumerable.prompt(tokenId)).to.equal("test"); 89 | }); 90 | 91 | }); 92 | 93 | async function deployOpmlLibFixture() { 94 | const OpmlLib = await ethers.getContractFactory("MockOpmlLib"); 95 | const opmlLib = await OpmlLib.deploy(); 96 | await opmlLib.deployed(); 97 | return opmlLib; 98 | } 99 | 100 | describe("ERC7007Opml.sol", function () { 101 | 102 | async function deployERC7007Fixture() { 103 | const opmlLib = await deployOpmlLibFixture(); 104 | 105 | const ERC7007 = await ethers.getContractFactory("ERC7007Opml"); 106 | const erc7007 = await ERC7007.deploy("testing", "TEST", opmlLib.address); 107 | await erc7007.deployed(); 108 | return erc7007; 109 | } 110 | 111 | describe("mint", function () { 112 | it("should mint a token", async function () { 113 | const erc7007 = await deployERC7007Fixture(); 114 | const [owner] = await ethers.getSigners(); 115 | await erc7007.mint(owner.address, prompt, aigcData, uri, 0x00); 116 | expect(await erc7007.balanceOf(owner.address)).to.equal(1); 117 | }); 118 | 119 | it("should verify a fianlized request", async function () { 120 | const erc7007 = await deployERC7007Fixture(); 121 | const [owner] = await ethers.getSigners(); 122 | await erc7007.mint(owner.address, prompt, aigcData, uri, 0x00); 123 | expect(await erc7007.verify(prompt, aigcData, 0x00)).to.equal(true); 124 | }); 125 | 126 | it("should not mint a token with same data twice", async function () { 127 | const erc7007 = await deployERC7007Fixture(); 128 | const [owner] = await ethers.getSigners(); 129 | await erc7007.mint(owner.address, prompt, aigcData, uri, 0x00); 130 | await expect(erc7007.mint(owner.address, prompt, aigcData, uri, 0x00)).to.be.revertedWith("ERC721: token already minted"); 131 | }); 132 | 133 | it("should emit a AigcData event", async function () { 134 | const erc7007 = await deployERC7007Fixture(); 135 | const [owner] = await ethers.getSigners(); 136 | await expect(erc7007.mint(owner.address, prompt, aigcData, uri, validProof)) 137 | .to.emit(erc7007, "AigcData") 138 | }); 139 | }); 140 | 141 | describe("metadata", function () { 142 | it("should return token metadata", async function () { 143 | const erc7007 = await deployERC7007Fixture(); 144 | const [owner] = await ethers.getSigners(); 145 | await erc7007.mint(owner.address, prompt, aigcData, uri, validProof); 146 | expect(await erc7007.tokenURI(tokenId)).to.equal('{"name": "test", "description": "test", "image": "test", "aigc_type": "test", "proof_type": "test", "prompt": "test", "aigc_data": "test"}'); 147 | }); 148 | }); 149 | }); --------------------------------------------------------------------------------