// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /** * @title VerifyVaultCityRegistry * @dev CityNFT registry with Ordinals proof anchor, USD pricing settled in native ETH or an ERC20. * Uses Chainlink ETH/USD (+ optional BTC/USD) price feeds to compute mint cost. * Owner can pause, set feeds, set baseURI, and withdraw. Includes ERC-2981 royalties. */ import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/token/common/ERC2981.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; interface AggregatorV3Interface { function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function decimals() external view returns (uint8); } contract VerifyVaultCityRegistry is ERC721, ERC721Enumerable, ERC2981, Ownable, Pausable, ReentrancyGuard { using Strings for uint256; // ===== Ordinals Proof Anchor ===== string public inscriptionId; // full Ordinals inscription string bytes32 public inscriptionHash; // keccak256(inscriptionId) event ProofAnchored(string inscriptionId, bytes32 inscriptionHash); // ===== Price Feeds ===== AggregatorV3Interface public ethUsdFeed; // required AggregatorV3Interface public btcUsdFeed; // optional (for display/math) // ===== Accepted ERC20 (optional stable like USDC) ===== IERC20 public quoteErc20; // if set, users can mint with this token (priced 1:1 in USD decimals) // ===== Metadata ===== string private _baseTokenURI; // ===== City Model ===== struct City { string name; // e.g., "Los Angeles" string geoHash; // optional geohash / place id uint256 usdPrice; // mint price in USD with 2 decimals (e.g., $1,234.56 => 123456) uint32 points; // arbitrary score string tokenURI; // full tokenURI (if empty, baseURI+tokenId used) bool useBtcLogic; // informational flag (pricing still resolves to ETH via feeds) bool exists; } // tokenId => City mapping(uint256 => City) public cities; // Supply tracker uint256 private _nextId = 1; // ===== Events ===== event CityCreated(uint256 indexed tokenId, string name, uint256 usdPriceCents, bool useBtcLogic); event CityUpdated(uint256 indexed tokenId); event FeedsUpdated(address ethUsd, address btcUsd); event QuoteErc20Updated(address token); event BaseURISet(string baseURI); constructor( string memory _name, string memory _symbol, string memory _inscriptionId, address _owner, address _ethUsdFeed, address _btcUsdFeed, address _quoteErc20, address _royaltyReceiver, uint96 _royaltyFeeNumerator ) ERC721(_name, _symbol) { require(_owner != address(0), "Owner address cannot be zero"); transferOwnership(_owner); // Ordinals proof anchor inscriptionId = _inscriptionId; inscriptionHash = keccak256(bytes(_inscriptionId)); emit ProofAnchored(_inscriptionId, inscriptionHash); // Price feeds require(_ethUsdFeed != address(0), "ETH/USD feed required"); ethUsdFeed = AggregatorV3Interface(_ethUsdFeed); if (_btcUsdFeed != address(0)) { btcUsdFeed = AggregatorV3Interface(_btcUsdFeed); } // ERC20 quote token if (_quoteErc20 != address(0)) { quoteErc20 = IERC20(_quoteErc20); emit QuoteErc20Updated(_quoteErc20); } // Royalties if (_royaltyReceiver != address(0) && _royaltyFeeNumerator > 0) { _setDefaultRoyalty(_royaltyReceiver, _royaltyFeeNumerator); } } // Admin Functions (Feeds, ERC20, Metadata)... }import { ethers } from "hardhat"; async function main() { const NAME = "VerifyVault City"; // NFT Collection Name const SYMBOL = "VCITY"; // NFT Symbol const INSCRIPTION_ID = "7f62e2694bfb8f7d318bc69e798b017b05c992c326c0527968deeca709ceb313i0"; const [deployer] = await ethers.getSigners(); console.log("Deploying contract with address:", deployer.address); const Factory = await ethers.getContractFactory("VerifyVaultCityRegistry"); const contract = await Factory.deploy( NAME, SYMBOL, INSCRIPTION_ID, deployer.address, // Contract owner "0xETH_USD_FEED_ADDRESS", // Replace with Chainlink ETH/USD feed "0xBTC_USD_FEED_ADDRESS", // Replace with optional Chainlink BTC/USD feed "0xUSDC_CONTRACT_ADDRESS", // Replace with USDC/ERC20 contract deployer.address, // Royalties receiver 500 // 5% royalties ); await contract.deployed(); console.log("Contract deployed to:", contract.address); } main().catch((error) => { console.error(error); process.exitCode = 1; }); PRIVATE_KEY=0xYOUR_PRIVATE_KEY RPC_ETHEREUM=https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY npx hardhat compile npx hardhat run scripts/deploy.ts --network ethereum npx hardhat verify --network ethereum "VerifyVault City" "VCITY" "" ""