Live demo (frontend on Vercel)
👉 https://victoria-nft-market.vercel.app/How to access:
- Use a desktop browser with the MetaMask extension
- Switch MetaMask network to Sepolia (ChainId = 11155111)
- Click “Connect MetaMask” in the header
Quick demo flow (for reviewers/teachers):
- Open the Bank tab
- Click “Daily Faucet” to claim 1,000 cHKD
- In “Oracle Swap”, input
0.1ETH and swap to get cHKD at the oracle price- Open Dashboard
- Use the default metadata URL and click “Mint NFT” to create a demo NFT
- See the new NFT card under “My Assets” (image + attributes)
- Open Market
- From Dashboard, list one NFT with a fixed price
- Switch to another wallet, go to Market, click
Buyto purchase that NFT with cHKD- Open Auctions
- With any NFT‑owner wallet, start an auction from Dashboard
- Use another wallet to place a bid, then let the seller settle and observe NFT / cHKD transfers
Current Sepolia contract addresses:
cHKD–0x4ee7805D139c5D0e002a127c5EdC199f624e53e0MyNFT–0xb91696c41e39C063987267BdD788FA83831EBD19Market–0x5540BEf94300f50D787806750764d35ACcfB0FE9Auction–0xe8F9921f69A31fCE64115C97447d43762d9939a7For a full Chinese description, see
README.ch.md.
This project implements a small NFT marketplace and auction system on top of a custom stablecoin:
- cHKD – ERC20 “stablecoin” with 6 decimals, faucet, and oracle‑based ETH→cHKD swap
- MyNFT – ERC721 NFT contract with public minting and
tokenURImetadata - Market – fixed‑price NFT marketplace paid in cHKD
- Auction – English auctions using cHKD bids
- Frontend DApp – React + Vite SPA with four views:
- Dashboard – my NFTs, mint, list/auction actions
- Market – fixed‑price listings
- Auctions – live auctions
- Bank – Faucet + ETH→cHKD swap
Tech stack:
- Solidity 0.8.20 + Hardhat + OpenZeppelin
- React + TypeScript + Vite
- ethers.js v6 + MetaMask
Repo layout:
NFT-Market-master/– Hardhat contracts project (Solidity, scripts, tests)frontend-lumina-nft-market/– frontend DApp
- Inherits:
ERC20,ERC20Burnable,Ownable - 6 decimals (1 cHKD = 10^6 units)
- Faucet:
FAUCET_AMOUNT = 1000 * 10^6FAUCET_COOLDOWN = 1 daysclaimFaucet()enforces cooldown and sends tokens from the contract’s own balance
Oracle swap:
AggregatorV3Interface public ethHkdPriceFeedstores the oracle addresssetEthHkdPriceFeed(address)(only owner) configures the oraclebuyWithETH():- Reads
answeranddecimalsfromethHkdPriceFeed(Chainlink‑style feed) - Treats
answer / 10^feedDecimalsas “1 ETH = X (fiat)” - Computes the number of cHKD to mint:
amountOut = msg.value * price * 10^tokenDecimals / (10^18 * 10^feedDecimals) - Mints
amountOutcHKD to the caller
- Reads
On localhost, the script uses a MockPriceFeed with a fixed price (1 ETH ≈ 10,000);
On Sepolia, it uses the Chainlink ETH/USD feed at0x694AA1769357215DE4FAC081bf1f309aDC325306.
- Inherits:
ERC721,ERC721Enumerable,ERC721URIStorage,Ownable - State:
_nextTokenId– incremental token IDpublicMintEnabled– toggle for public mintingmarket– Market contract address
Key functions:
safeMint(address to, string uri)(only owner) – used in scripts for demo NFTspublicMint(string uri)– mints tomsg.senderand setstokenURI(requirespublicMintEnabled)setPublicMintEnabled(bool)/setMarket(address)
- Uses
IERC20 erc20(cHKD) andIERC721 erc721(MyNFT) - Data:
Order { address seller; uint256 tokenId; uint256 price; }orderOfId[tokenId],orders[],idToOrderIndex[tokenId]
Flow:
list(tokenId, price):- Requires caller is NFT owner, price > 0, token not already listed
- Frontend calls
nft.approve(MARKET, tokenId)first - Market takes the NFT via
safeTransferFromand records the order inonERC721Received
buy(tokenId):- Frontend first calls
chkd.approve(MARKET, price) - Market transfers cHKD buyer→seller and NFT contract→buyer, then removes the order
- Frontend first calls
cancelOrder,changePrice,getAllNFTs
- Uses
IERC20 erc20(cHKD),IERC721 erc721(MyNFT),ReentrancyGuard - Data:
AuctionInfo { seller, tokenId, startPrice, highestBid, highestBidder, endTime }auctionOfId[tokenId],auctions[],idToAuctionIndex[tokenId]
Flow:
startAuction(tokenId, startPrice, duration)– moves NFT to the contract and opens an auctionbid(tokenId, amount):- Frontend calls
chkd.approve(AUCTION, amount) - Contract pulls cHKD from bidder, refunds previous highest bidder, updates state
- Frontend calls
cancelAuction(tokenId)– only seller, only if no bidssettle(tokenId)– only seller, sends NFT to winner and cHKD to seller (or returns NFT if no bids)
The deploy script:
- Uses the first signer as
admin(owner of cHKD and MyNFT). - Deploys
cHKDand configures the oracle:- Localhost: deploys
MockPriceFeedwith a fixed price and setsethHkdPriceFeed - Sepolia: sets
ethHkdPriceFeedto the Chainlink ETH/USD feed address
- Localhost: deploys
- Deploys
MyNFT, enables public minting, and deploysMarketandAuction. - Sets the Market address in
MyNFT. - Logs addresses; no tokens or NFTs are pre‑minted – users rely on Faucet and public minting.
- React + TypeScript + Vite
index.tsx→<App />- Core files:
services/blockchain.ts–BlockchainServicewrapperconstants.ts– contract addresses & ABIscomponents/AssetCard.tsx– NFT card UIpublic/metadata/demo.json– demo metadata JSON
Creates contract instances for cHKD, MyNFT, Market, Auction and exposes:
getAdminAddress()– readscHKD.owner()getBalance(address)– returns cHKD balance (6‑decimals formatted)getMyNFTs(address)– enumerates owner tokens, readstokenURI, fetches metadatagetMarketListings()/getAuctions()- Transactions:
faucet,buyCHKD,mint,listNFT,buyNFT,startAuction,bid,settleAuction,cancelAuction
- Connects MetaMask via
ethers.BrowserProvider(window.ethereum) - Restricts network to localhost (31337) or Sepolia (11155111)
- Tracks:
- Connected
accountand discoveredadminAddress balance,myNFTs,marketOrders,auctionsviewMode(Dashboard / Market / Auctions / Bank)txState(loading / error / success)- Form fields (
mintUri,listPrice,auctionStartPrice,auctionDuration,bidAmount,ethToSwap)
- Connected
Views:
- Dashboard – my NFTs, mint, quick “List Fixed” / “Start Auction” actions
- Market – fixed‑price listings with buy buttons
- Auctions – auctions with bidding and settle/cancel buttons
- Bank – Faucet + Oracle Swap
# Contracts
cd NFT-Market-master
npm install
npx hardhat node
npx hardhat run scripts/deploy.js --network localhost
# Frontend
cd ../frontend-lumina-nft-market
npm install
npm run devOpen the Vite dev URL (usually http://localhost:3000), connect MetaMask to the Hardhat localhost network, and follow the same flow as in the online demo.
If you re‑deploy, update this section and
frontend-lumina-nft-market/constants.ts.
- Network: Sepolia (
chainId = 11155111) - Admin/Owner:
0xbf65460E1EA8269Ab61B7946b2B04D5A334E0642 - Contracts:
cHKD–0x4ee7805D139c5D0e002a127c5EdC199f624e53e0MyNFT–0xb91696c41e39C063987267BdD788FA83831EBD19Market–0x5540BEf94300f50D787806750764d35ACcfB0FE9Auction–0xe8F9921f69A31fCE64115C97447d43762d9939a7
- Oracle:
ethHkdPriceFeed→0x694AA1769357215DE4FAC081bf1f309aDC325306(Chainlink ETH/USD feed)cHKD.buyWithETH()uses this feed’s live price for conversions.
- NFT metadata storage is not yet production‑ready:
- The demo uses local static files under
public/metadata/, and the default mint URI is still tied to a demo JSON; - If you deploy the frontend to a new domain without updating metadata URLs, newly minted NFTs may show as “Unknown Asset” or use fallback images.
- The demo uses local static files under
- Recommended improvements:
- Upload metadata JSON and images to IPFS or a stable cloud bucket (S3/OSS/etc.) and use those URLs (or
ipfs://URIs) astokenURI; - Update the default mint URI and any hard‑coded demo URIs to those public URLs;
- Optionally enforce basic URI validation on‑chain or require a valid URL in the frontend before allowing minting.
- Upload metadata JSON and images to IPFS or a stable cloud bucket (S3/OSS/etc.) and use those URLs (or