// SPDX-License-Identifier: MIT�pragma solidity ^0.8.20;��import \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";�import \"@openzeppelin/contracts/access/Ownable.sol\";��contract HayolaGold is ERC721, Ownable {� uint256 private _nextTokenId;�� struct GoldItem {� string itemType; // \"ring\", \"necklace\", \"bracelet\"� string material; // \"18K Gold\", \"24K Gold\"� uint256 weightMg; // weight in milligrams (2500 = 2.5g)� string gemstone; // \"Diamond 0.5ct\", \"None\"� string manufacturer; // \"Tehran Gold House\"� uint256 manufactureDate; // unix timestamp� string certificateId; // lab certificate number� string imageURI; // IPFS or URL to product photo� }�� // Storage� mapping(uint256 => GoldItem) public goldItems;� mapping(string => uint256) public nfcToToken;� mapping(uint256 => string) public tokenToNfc;� mapping(uint256 => address) public cardSigner;� mapping(bytes32 => bool) public usedChallenges;�� // Events� event GoldItemMinted(uint256 indexed tokenId, string nfcUid, address owner);� event TransferWithCard(uint256 indexed tokenId, address from, address to);�� constructor() ERC721(\"Hayola Gold\", \"HGOLD\") Ownable(msg.sender) {}�� // ==================== MINT ====================� function mint(� address to,� string memory nfcUid,� address cardPublicKey,� string memory itemType,� string memory material,� uint256 weightMg,� string memory gemstone,� string memory manufacturer,� uint256 manufactureDate,� string memory certificateId,� string memory imageURI� ) public onlyOwner returns (uint256) {� require(nfcToToken[nfcUid] == 0, \"NFC already linked\");�� uint256 tokenId = ++_nextTokenId;� _mint(to, tokenId);�� goldItems[tokenId] = GoldItem({� itemType: itemType,� material: material,� weightMg: weightMg,� gemstone: gemstone,� manufacturer: manufacturer,� manufactureDate: manufactureDate,� certificateId: certificateId,� imageURI: imageURI� });�� nfcToToken[nfcUid] = tokenId;� tokenToNfc[tokenId] = nfcUid;� cardSigner[tokenId] = cardPublicKey;�� emit GoldItemMinted(tokenId, nfcUid, to);� return tokenId;� }�� // ==================== VERIFY 1: Simple lookup ====================� // Free — just checks if NFC is registered and returns all data� function verifyBasic(string memory nfcUid) public view returns (� bool exists,� uint256 tokenId,� address owner,� GoldItem memory item� ) {� tokenId = nfcToToken[nfcUid];� if (tokenId == 0) return (false, 0, address(0), item);� exists = true;� owner = ownerOf(tokenId);� item = goldItems[tokenId];� }�� // ==================== VERIFY 2: Card challenge ====================� // Free — proves physical NFC chip is authentic� function verifyWithCard(� string memory nfcUid,� bytes32 challenge,� bytes memory signature� ) public view returns (� bool authentic,� uint256 tokenId,� address owner,� GoldItem memory item� ) {� tokenId = nfcToToken[nfcUid];� require(tokenId != 0, \"NFC not registered\");�� bytes32 ethMessage = keccak256(abi.encodePacked(� \"\x19Ethereum Signed Message:\n32\", challenge� ));� address signer = _recoverSigner(ethMessage, signature);�� authentic = (signer == cardSigner[tokenId]);� owner = ownerOf(tokenId);� item = goldItems[tokenId];� }�� // ==================== TRANSFER: Maker pays gas ====================� // Requires: owner wallet signature + NFC card signature + unique challenge� function transferByMaker(� uint256 tokenId,� address newOwner,� bytes32 challenge,� bytes memory ownerSignature,� bytes memory cardSignature� ) public onlyOwner {� require(!usedChallenges[challenge], \"Challenge already used\");� usedChallenges[challenge] = true;�� address currentOwner = ownerOf(tokenId);�� // Verify owner approved this transfer� bytes32 message = keccak256(abi.encodePacked(� \"HAYOLA_TRANSFER:\", tokenId, newOwner, challenge� ));� bytes32 ethMessage = keccak256(abi.encodePacked(� \"\x19Ethereum Signed Message:\n32\", message� ));� require(� _recoverSigner(ethMessage, ownerSignature) == currentOwner,� \"Owner did not approve\"� );�� // Verify NFC card� require(� _recoverSigner(� keccak256(abi.encodePacked(� \"\x19Ethereum Signed Message:\n32\", message� )),� cardSignature� ) == cardSigner[tokenId],� \"Invalid card signature\"� );�� _transfer(currentOwner, newOwner, tokenId);� emit TransferWithCard(tokenId, currentOwner, newOwner);� }�� // ==================== TRANSFER: Buyer/seller pays gas ====================� // Requires: caller is owner or approved + NFC card signature + unique challenge� function transferBySelf(� uint256 tokenId,� address newOwner,� bytes32 challenge,� bytes memory cardSignature� ) public {� require(!usedChallenges[challenge], \"Challenge already used\");� usedChallenges[challenge] = true;�� address currentOwner = ownerOf(tokenId);� require(� msg.sender == currentOwner ||� isApprovedForAll(currentOwner, msg.sender) ||� getApproved(tokenId) == msg.sender,� \"Not owner or approved\"� );�� // Verify NFC card� bytes32 message = keccak256(abi.encodePacked(� \"HAYOLA_TRANSFER:\", tokenId, newOwner, challenge� ));� bytes32 ethMessage = keccak256(abi.encodePacked(� \"\x19Ethereum Signed Message:\n32\", message� ));� require(� _recoverSigner(ethMessage, cardSignature) == cardSigner[tokenId],� \"Invalid card signature\"� );�� _transfer(currentOwner, newOwner, tokenId);� emit TransferWithCard(tokenId, currentOwner, newOwner);� }�� // ==================== HELPERS ====================� function _recoverSigner(bytes32 hash, bytes memory sig)� internal pure returns (address)� {� require(sig.length == 65, \"Invalid signature length\");� uint8 v; bytes32 r; bytes32 s;� assembly {� r := mload(add(sig, 32))� s := mload(add(sig, 64))� v := byte(0, mload(add(sig, 96)))� }� return ecrecover(hash, v, r, s);� }�� // Total minted count� function totalMinted() public view returns (uint256) {� return _nextTokenId;� }�}�

Embed NFC NFT Verifier