Skip to content

Instantly share code, notes, and snippets.

@korrio
Created July 12, 2025 06:07
Show Gist options
  • Save korrio/a6b53ecf2652f754c53b19684fbaccaf to your computer and use it in GitHub Desktop.
Save korrio/a6b53ecf2652f754c53b19684fbaccaf to your computer and use it in GitHub Desktop.
SelfSellingNFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SelfSellingNFT is ERC721, Ownable, ReentrancyGuard {
uint256 private _tokenIdCounter;
// Mapping จาก tokenId ไปยังราคาขาย
mapping(uint256 => uint256) public tokenPrices;
// Mapping จาก tokenId ไปยังสถานะการขาย
mapping(uint256 => bool) public tokenForSale;
// Events
event TokenMinted(uint256 indexed tokenId, address indexed owner, uint256 price);
event TokenPriceSet(uint256 indexed tokenId, uint256 price);
event TokenSold(uint256 indexed tokenId, address indexed from, address indexed to, uint256 price);
event TokenListedForSale(uint256 indexed tokenId, uint256 price);
event TokenRemovedFromSale(uint256 indexed tokenId);
constructor() ERC721("SelfSellingNFT", "SSNFT") {
_tokenIdCounter = 1;
}
/**
* @dev Mint NFT ใหม่พร้อมกำหนดราคาขาย
* @param to ที่อยู่ผู้รับ NFT
* @param price ราคาขายใน Wei
*/
function mintWithPrice(address to, uint256 price) external onlyOwner {
require(to != address(0), "Cannot mint to zero address");
require(price > 0, "Price must be greater than 0");
uint256 tokenId = _tokenIdCounter;
_tokenIdCounter++;
_safeMint(to, tokenId);
tokenPrices[tokenId] = price;
tokenForSale[tokenId] = true;
emit TokenMinted(tokenId, to, price);
emit TokenListedForSale(tokenId, price);
}
/**
* @dev เจ้าของ NFT สามารถกำหนดราคาขายใหม่ได้
* @param tokenId ID ของ NFT
* @param price ราคาใหม่ใน Wei
*/
function setPrice(uint256 tokenId, uint256 price) external {
require(_exists(tokenId), "Token does not exist");
require(ownerOf(tokenId) == msg.sender, "Only owner can set price");
require(price > 0, "Price must be greater than 0");
tokenPrices[tokenId] = price;
tokenForSale[tokenId] = true;
emit TokenPriceSet(tokenId, price);
emit TokenListedForSale(tokenId, price);
}
/**
* @dev เจ้าของ NFT สามารถยกเลิกการขายได้
* @param tokenId ID ของ NFT
*/
function removeFromSale(uint256 tokenId) external {
require(_exists(tokenId), "Token does not exist");
require(ownerOf(tokenId) == msg.sender, "Only owner can remove from sale");
tokenForSale[tokenId] = false;
emit TokenRemovedFromSale(tokenId);
}
/**
* @dev ฟังก์ชันหลักสำหรับซื้อ NFT - รับเงินแล้วโอนให้เจ้าของเดิมและโอน NFT ให้ผู้ซื้อ
* @param tokenId ID ของ NFT ที่ต้องการซื้อ
*/
function buyNFT(uint256 tokenId) external payable nonReentrant {
require(_exists(tokenId), "Token does not exist");
require(tokenForSale[tokenId], "Token is not for sale");
require(msg.value >= tokenPrices[tokenId], "Insufficient payment");
require(msg.sender != ownerOf(tokenId), "Owner cannot buy own token");
address currentOwner = ownerOf(tokenId);
uint256 price = tokenPrices[tokenId];
// ยกเลิกการขาย
tokenForSale[tokenId] = false;
// โอนเงินให้เจ้าของเดิม
(bool success, ) = payable(currentOwner).call{value: price}("");
require(success, "Payment transfer failed");
// คืนเงินส่วนเกิน (ถ้ามี)
if (msg.value > price) {
(bool refundSuccess, ) = payable(msg.sender).call{value: msg.value - price}("");
require(refundSuccess, "Refund failed");
}
// โอน NFT ให้ผู้ซื้อ
_transfer(currentOwner, msg.sender, tokenId);
emit TokenSold(tokenId, currentOwner, msg.sender, price);
}
/**
* @dev ดูข้อมูล NFT ที่กำลังขาย
* @param tokenId ID ของ NFT
*/
function getTokenInfo(uint256 tokenId) external view returns (
address owner,
uint256 price,
bool forSale,
string memory tokenURI
) {
require(_exists(tokenId), "Token does not exist");
return (
ownerOf(tokenId),
tokenPrices[tokenId],
tokenForSale[tokenId],
tokenURI(tokenId)
);
}
/**
* @dev ดูรายการ NFT ทั้งหมดที่กำลังขาย
*/
function getTokensForSale() external view returns (uint256[] memory) {
uint256 totalTokens = _tokenIdCounter - 1;
uint256 forSaleCount = 0;
// นับจำนวน token ที่กำลังขาย
for (uint256 i = 1; i <= totalTokens; i++) {
if (_exists(i) && tokenForSale[i]) {
forSaleCount++;
}
}
// สร้าง array และเก็บ tokenId ที่กำลังขาย
uint256[] memory tokensForSale = new uint256[](forSaleCount);
uint256 currentIndex = 0;
for (uint256 i = 1; i <= totalTokens; i++) {
if (_exists(i) && tokenForSale[i]) {
tokensForSale[currentIndex] = i;
currentIndex++;
}
}
return tokensForSale;
}
/**
* @dev ตรวจสอบว่า token มีอยู่จริงหรือไม่
*/
function _exists(uint256 tokenId) internal view returns (bool) {
return tokenId > 0 && tokenId < _tokenIdCounter;
}
/**
* @dev Override tokenURI เพื่อใส่ metadata
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "Token does not exist");
// คุณสามารถ customize metadata ได้ตามต้องการ
return string(abi.encodePacked("https://api.example.com/metadata/", Strings.toString(tokenId)));
}
/**
* @dev Emergency withdraw (เฉพาะ owner)
*/
function emergencyWithdraw() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No funds to withdraw");
(bool success, ) = payable(owner()).call{value: balance}("");
require(success, "Withdraw failed");
}
/**
* @dev รับ Ether โดยตรง (สำหรับกรณีฉุกเฉิน)
*/
receive() external payable {
// สามารถรับ Ether ได้
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment