NFT'ler, 2021 patlamasından bu yana web3 ekosisteminin temel bir parçası haline geldi; koleksiyon ötesine geçerek üyelik erişimi, varlık tokenizasyonu, oyun öğeleri ve dijital kimlik gibi gerçek dünya kullanım alanlarına evrildi.
Phosphor gibi kodsuz platformlar, sezgisel bir Creator Studio ve API paketi aracılığıyla içerik üreticilerinin ve geliştiricilerin NFT basmasını, özelleştirmesini ve dağıtmasını her zamankinden daha kolay hale getirdi; böylece sıfırdan özel NFT sözleşmeleri oluşturma ihtiyacını ortadan kaldırdı. Ancak Solidity ve web3'e özgü deneyimler konusundaki anlayışını derinleştirmek isteyen geliştiriciler için NFT pazaryeri oluşturmayı öğrenmek temel bir beceri olmaya devam etmektedir.
Bu kılavuzda, Linea üzerinde temel ama tam işlevsel bir NFT pazaryeri geliştirme ve dağıtma sürecini adım adım ele alacağız. Sonunda kendi pazaryerini başlatacak bilgiye sahip olacak ve akıllı sözleşme geliştirme, merkeziyetsiz uygulamalar ile web3 etkileşimleri konularında daha sağlam bir kavrayış kazanacaksın.
Ön Koşullar
Solidity hakkında temel bir anlayışa sahip olmalısın.
Bilgisayarında Nodejs ve Foundry kurulu olmalıdır.
Sözleşmeye genel bakış
Bu NFT pazaryeri aşağıdaki işlevleri destekleyecektir:
NFT'leri satışa listeleme: Kullanıcılar bir fiyat belirleyip NFT'lerini listeleyebilir; ayrıntılar akıllı sözleşmede güvenli biçimde saklanır.
NFT satın alma: Alıcılar, gerekli Ether'i ödeyerek listelenen NFT'leri satın alabilir ve sorunsuz bir işlem gerçekleştirir.
Toplu NFT satın alma: Gas maliyetlerini azaltmak için tek bir işlemde birden fazla NFT satın alınabilir.
NFT listesini kaldırma: Sahipler, NFT'lerini satıştan çekerek cüzdanlarına geri alabilir.
Proje kurulumu
Bu proje için Foundry framework'ünü kullanacağız. Foundry kurulu durumdayken, yeni bir proje başlatmak için aşağıdaki komutu çalıştır:
forge init marketMarket klasörünü Vscode'da veya favori kod editöründe aç; scripts/counter.s.sol, src/counter.sol ve test/counter.t.sol dosyalarını sil.
Tüm bağımlılıkları yükle
forge install foundry-rs/forge-std --no-commit && forge install OpenZeppelin/openzeppelin-contracts --no-commitNFT pazaryeri akıllı sözleşmesinin tam kodu
src klasöründe bir market.sol dosyası oluştur ve aşağıdaki kodu dosyaya ekle.
pragma solidity ^0.8.18;
import "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {Test, console} from "forge-std/Test.sol";
contract NFTMarketplace {
struct Listing {
address seller;
uint256 price;
}
mapping(address => mapping(uint256 => Listing)) public listings;
event Listed(address indexed nftContract, uint256 indexed tokenId, address seller, uint256 price);
event Purchase(address indexed nftContract, uint256 indexed tokenId, address buyer, uint256 price);
event Delisted(address indexed nftContract, uint256 indexed tokenId, address seller);
modifier isSeller(address nftContract, uint256 tokenId, address spender) {
require(listings[address(nftContract)][tokenId].seller == spender, "Not the seller");
_;
}
modifier isListed(address nftContract, uint256 tokenId) {
require(listings[nftContract][tokenId].price > 0, "Not listed");
_;
}
modifier notListed(address nftContract, uint256 tokenId) {
require(listings[nftContract][tokenId].price == 0, "Already listed");
_;
}
function listNFT(address nftContract, uint256 tokenId, uint256 price)
external
notListed(nftContract, tokenId)
{
require(price > 0, "Price must be greater than zero");
require(IERC721(nftContract).ownerOf(tokenId) == msg.sender, "not owner");
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
listings[nftContract][tokenId] = Listing(msg.sender, price);
emit Listed(nftContract, tokenId, msg.sender, price);
}
function buyNFT(address nftContract, uint256 tokenId)
public
payable
isListed(nftContract, tokenId)
{
Listing memory listing = listings[nftContract][tokenId];
require(msg.value >= listing.price, "Insufficient funds");
delete listings[nftContract][tokenId];
(bool sent, ) = listing.seller.call{value: listing.price}("");
require(sent, "Failed to send Ether");
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
emit Purchase(nftContract, tokenId, msg.sender, listing.price);
}
function buyMultipleNFT(address nftContract, uint256 [] memory tokenIds)
external
payable
{
uint256 totalAmount;
for (uint256 i; i < tokenIds.length; i++){
Listing memory listing = listings[nftContract][tokenIds[i]];
totalAmount += listing.price;
}
require(msg.value >= totalAmount, "Insufficient funds");
for (uint256 i; i < tokenIds.length; i++){
buyNFT(nftContract, tokenIds[i]);
}
}
function delistNFT(address nftContract, uint256 tokenId)
external
isListed(nftContract, tokenId)
isSeller(nftContract, tokenId, msg.sender)
{
Listing memory listing = listings[nftContract][tokenId];
delete listings[nftContract][tokenId];
IERC721(nftContract).transferFrom(address(this), listing.seller, tokenId);
emit Delisted(nftContract, tokenId, listing.seller);
}
}
Kodun temel özellikleri
Listing struct
Bu yapı, merkeziyetsiz bir NFT pazaryerinde listeleme, satın alma ve listeden kaldırma gibi özelliklerin uygulanması için gereklidir. Her öğenin satıcısını ve fiyatını kayıt altında tutarken listeleri yönetmenin açık ve verimli bir yolunu sunar; her ikisi de pazaryerinin işleyişi açısından kritik öneme sahiptir.
struct Listing {
address seller;
uint256 price;
}
Listing mapping
Bu kod satırı, Solidity'de NFT pazaryerinin listelerini düzenleyen ve depolayan iç içe bir mapping tanımlar. Dış mapping, NFT sözleşme adresini anahtar olarak kullanarak akıllı sözleşmenin listeleri ilgili NFT koleksiyonlarına göre kategorize etmesini sağlar. Bu sayede pazaryeri birden fazla NFT koleksiyonunu destekleyebilir.
İç mapping ise her NFT için benzersiz bir tanımlayıcı olan token ID'yi anahtar olarak kullanır; böylece tek bir NFT sözleşmesi altındaki birden fazla listenin verimli biçimde yönetilmesi mümkün olur.
mapping(address => mapping(uint256 => Listing)) public listings;
NFT listeleme
listNFT fonksiyonu, kullanıcıların NFT'lerini pazaryerinde satışa sunmasına olanak tanır. Üç parametre alır: NFT sözleşmesi, token ID ve fiyat. NFT sözleşmesi, kullanıcının NFT listelemek istediği koleksiyonun adresini; token ID, o koleksiyon içindeki NFT'nin benzersiz tanımlayıcısını; fiyat ise kullanıcının NFT'yi satmak istediği miktarı ifade eder.
Fonksiyon önce kullanıcının NFT'nin sahibi olduğunu doğrular ve fiyatın sıfırdan büyük olmasını sağlar. Bu kontroller geçildikten sonra NFT satıcıdan pazaryerine aktarılır, listings mapping güncellenir ve Listed olayı doğru parametrelerle yayılır.
function listNFT(address nftContract, uint256 tokenId, uint256 price)
external
notListed(nftContract, tokenId)
{
require(price > 0, "Price must be greater than zero");
require(IERC721(nftContract).ownerOf(tokenId) == msg.sender, "not owner");
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
listings[nftContract][tokenId] = Listing(msg.sender, price);
emit Listed(nftContract, tokenId, msg.sender, price);
}
NFT satın alma
buyNFT fonksiyonu, kullanıcıların pazaryerinde listelenen bir NFT'yi satın almasına olanak tanır. İki parametre alır: NFT'nin akıllı sözleşme adresi olan nftContract ve satın alınan NFT'nin benzersiz tanımlayıcısı olan tokenId.
Fonksiyon önce alıcının NFT fiyatını karşılayacak kadar Ether gönderdiğini doğrular. Doğrulama tamamlandıktan sonra liste, listings mapping'den kaldırılır; ödeme güvenli biçimde satıcıya aktarılır ve NFT, pazaryeri sözleşmesinden alıcının adresine transfer edilerek satın alma işlemi tamamlanır.
function buyNFT(address nftContract, uint256 tokenId)
public
payable
isListed(nftContract, tokenId)
{
Listing memory listing = listings[nftContract][tokenId];
require(msg.value >= listing.price, "Insufficient funds");
delete listings[nftContract][tokenId];
(bool sent, ) = listing.seller.call{value: listing.price}("");
require(sent, "Failed to send Ether");
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
emit Purchase(nftContract, tokenId, msg.sender, listing.price);
}
Birden fazla NFT satın alma
buyMultipleNFT fonksiyonu, kullanıcının tek bir işlemde birden fazla NFT satın almasına olanak tanır. Önce tokenIds dizisinde listelenen tüm NFT'lerin fiyatlarını toplayarak toplam fiyatı hesaplar. Ardından alıcının toplam tutarı karşılayacak kadar fon gönderdiğini doğrular.
Fonlar doğrulandıktan sonra fonksiyon, tokenIds dizisini iterasyonla gezerek her NFT için buyNFT fonksiyonunu çağırır. Bu sayede her NFT ayrı ayrı satın alınır, ödeme satıcıya aktarılır ve NFT'ler alıcıya gönderilir; buyNFT fonksiyonuyla aynı süreç izlenir.
function buyMultipleNFT(address nftContract, uint256 [] memory tokenIds)
external
payable
{
uint256 totalAmount;
for (uint256 i; i < tokenIds.length; i++){
Listing memory listing = listings[nftContract][tokenIds[i]];
totalAmount += listing.price;
}
require(msg.value >= totalAmount, "Insufficient funds");
for (uint256 i; i < tokenIds.length; i++){
buyNFT(nftContract, tokenIds[i]);
}
}
NFT listesini kaldırma
delistNFT fonksiyonu, bir satıcının NFT'sini pazaryerinden kaldırmasına olanak tanır. Önce NFT'nin listelendiğini ve çağrıyı yapanın gerçek sahibi olduğunu doğrular. Doğrulama tamamlandıktan sonra liste pazaryerinden silinerek NFT satıştan kaldırılır.
Fonksiyon ardından NFT'yi satıcıya geri transfer ederek varlığın sahipliğini yeniden kazanmasını sağlar. Son olarak, NFT'nin pazaryerinden başarıyla kaldırıldığını teyit etmek için Delisted olayı yayılır.
function delistNFT(address nftContract, uint256 tokenId)
external
isListed(nftContract, tokenId)
isSeller(nftContract, tokenId, msg.sender)
{
Listing memory listing = listings[nftContract][tokenId];
delete listings[nftContract][tokenId];
IERC721(nftContract).transferFrom(address(this), listing.seller, tokenId);
emit Delisted(nftContract, tokenId, listing.seller);
}
Temel olaylar
Sözleşme, temel pazaryeri eylemleri için olaylar yayar:
Listed: Bir NFT listelendiğinde tetiklenir; satıcı, fiyat ve NFT bilgileri gibi ayrıntıları kaydeder.
Purchase: Bir alıcı NFT satın aldığında yayılır; işlem izlenebilirliğini sağlar.
Delisted: Bir NFT'nin pazaryerinden başarıyla kaldırıldığını gösterir; şeffaflığı korur.
event Listed(address indexed nftContract, uint256 indexed tokenId, address seller, uint256 price);
event Purchase(address indexed nftContract, uint256 indexed tokenId, address buyer, uint256 price);
event Delisted(address indexed nftContract, uint256 indexed tokenId, address seller
Test paketine genel bakış
src klasöründe bir NFT.sol dosyası oluştur ve aşağıdaki kodu dosyaya ekle.
pragma solidity ^0.8.18;
import "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
contract NFT is ERC721, Ownable {
uint256 private _tokenIdCounter;
string private _baseTokenURI;
event Minted(address indexed to, uint256 tokenId);
constructor(string memory name, string memory symbol, string memory baseTokenURI) ERC721(name, symbol) Ownable(msg.sender) {
_baseTokenURI = baseTokenURI;
}
function mint(address to) external onlyOwner {
uint256 tokenId = _tokenIdCounter;
_tokenIdCounter += 1;
_safeMint(to, tokenId);
emit Minted(to, tokenId);
}
function _baseURI() internal view override returns (string memory) {
return _baseTokenURI;
}
function setBaseTokenURI(string memory newBaseTokenURI) external onlyOwner {
_baseTokenURI = newBaseTokenURI;
}
function getTokenCount() external view returns (uint256) {
return _tokenIdCounter;
}
}
test klasöründe bir market.t.sol dosyası oluştur ve aşağıdaki kodu dosyaya ekle.
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {NFTMarketplace} from "../src/Market.sol";
import {NFT} from "../src/NFT.sol";
contract MarketTest is Test {
NFTMarketplace public market;
NFT public nft;
address public user1 = makeAddr("user1");
address public user2 = makeAddr("user2");
address public user3 = makeAddr("user3");
function setUp() public {
nft = new NFT("TestNFT", "tnft", "");
market = new NFTMarketplace();
nft.mint(user1);
nft.mint(user1);
nft.mint(user1);
}
function test_Listing() public {
vm.startPrank(user1);
nft.approve(address(market), 0);
market.listNFT(address(nft), 0, 1 ether);
}
function test_BuyingNFT() public {
vm.startPrank(user1);
nft.approve(address(market), 0);
market.listNFT(address(nft), 0, 1 ether);
vm.startPrank(user2);
deal(user2, 10 ether);
market.buyNFT{value: 1 ether}(address(nft), 0);
assert(nft.ownerOf(0) == user2);
}
function test_BuyingMultipleNFT() public {
vm.startPrank(user1);
nft.approve(address(market), 0);
nft.approve(address(market), 1);
nft.approve(address(market), 2);
market.listNFT(address(nft), 0, 1 ether);
market.listNFT(address(nft), 1, 1 ether);
market.listNFT(address(nft), 2, 1 ether);
vm.startPrank(user2);
deal(user2, 10 ether);
uint256 [] memory nfts = new uint256[] (3);
nfts[0] = 0;
nfts[1] = 1;
nfts[2] = 2;
market.buyMultipleNFT{value: 3 ether}(address(nft), nfts);
}
function test_DelistingNFT() public {
vm.startPrank(user1);
nft.approve(address(market), 0);
market.listNFT(address(nft), 0, 1 ether);
assert(nft.ownerOf(0) == address(market));
market.delistNFT(address(nft), 0);
assert(nft.ownerOf(0) == address(user1));
}
}
Yukarıdaki test paketi, NFT pazaryeri sözleşmesinin temel işlevleri boyunca beklendiği gibi çalışmasını sağlar. Testlerin dökümü aşağıda verilmiştir:
Test ortamının kurulması
setUp fonksiyonu, hem NFTMarketplace hem de NFT sözleşmelerini dağıtarak ortamı başlatır. Test amacıyla user1'e üç NFT basar. Bir mock NFT sözleşmesi (NFT), NFT üretmek için mint fonksiyonuyla dağıtılırken pazaryeri sözleşmesi (NFTMarketplace) örneklenir. User1, basılan tüm NFT'lere sahiptir; bu da listeleme ve listeden kaldırma işlevlerinin test edilmesine olanak tanır.
NFT listelemenin test edilmesi
test_Listing fonksiyonu, user1'in pazaryerinde bir NFT'yi satışa listeleyebildiğini doğrular.
User1, pazaryerinin tokenId 0'ı transfer etmesine onay verir.
User1, NFT'yi 1 Ether fiyatıyla listeler.
Test, listNFT fonksiyonunun doğru çalıştığını ve NFT'nin pazaryerine transfer edildiğini teyit eder.
Tekil NFT satın almanın test edilmesi
test_BuyingNFT fonksiyonu, user2 tarafından listelenen bir NFT'nin satın alınmasını simüle eder.
User1, tokenId 0'ı 1 Ether karşılığında listeler.
User2, 10 Ether alır ve NFT'yi satın almak için 1 Ether göndererek buyNFT fonksiyonunu çağırır.
Satın alma işleminin ardından test, user2'nin tokenId 0'ın yeni sahibi olduğunu doğrular.
Çoklu NFT satın almanın test edilmesi
test_BuyingMultipleNFT fonksiyonu, tek bir işlemde birden fazla NFT satın alma yeteneğini test eder.
User1, üç NFT'yi (tokenId 0, 1, 2) 1'er Ether karşılığında listeler.
User2, token ID'leri ileterek ve 3 Ether göndererek buyMultipleNFT'yi çağırır.
Test, user2'nin üç NFT'nin de sahibi olduğunu ve pazaryerinin birden fazla satın almayı doğru biçimde işlediğini doğrular.
NFT listesini kaldırmanın test edilmesi
test_DelistingNFT fonksiyonu, user1'in NFT'sini pazaryerinden kaldırabildiğini doğrular.
User1, tokenId 0'ı 1 Ether karşılığında listeler ve delistNFT'yi çağırır.
NFT user1'e iade edilir ve test, sahipliğin user1'e geri döndüğünü teyit eder.
Testi çalıştır
forge testLinea üzerinde dağıtım ve doğrulama
Gerekli değişkenleri .env dosyasına ekle; dosyanın projenin kök dizininde bulunduğundan emin ol:
LINEA_RPC_URL=https://linea-mainnet.infura.io/v3/
PRIVATE_KEY=*************************************************
Aşağıdaki ayrıntıları foundry.toml'a ekle:
[etherscan]
linea = { key = "${LINEA_API_KEY}", url = "https://api-sepolia.lineascan.build/api" }
Bir Makefile oluştur ve aşağıdaki ayrıntıları ekle:
deploy:
forge create src/Market.sol:NFTMarketplace --rpc-url $(LINEA_RPC_URL) --private-key $(PRIVATE_KEY)
verify:; forge verify-contract --rpc-url $(LINEA_RPC_URL) --chain linea <contract address> src/Market.sol:NFTMarketplace
Dağıtım
make deployDoğrulama
make verifySonuç
Linea üzerinde bir NFT pazaryeri oluşturmak, geliştiricilere akıllı sözleşme geliştirme, Solidity programlama ve web3 etkileşimleri konularında uygulamalı deneyim kazandırır. Bu kılavuzda, güvenli ve verimli işlemleri sağlarken NFT listeleme, satın alma, toplu satın alma ve listeden kaldırma dahil olmak üzere merkeziyetsiz bir pazaryerinin temel işlevlerini ele aldık.
Ayrıca Foundry kullanarak akıllı sözleşmenin uygulanmasını ve test edilmesini, Linea üzerindeki dağıtım ve doğrulama sürecini de adım adım inceledik. Bu kılavuzu takip ederek artık kendi NFT pazaryerini oluşturmak, test etmek ve dağıtmak için gereken temel bilgiye sahipsin.
Daha fazla uygulamalı eğitim ve blockchain geliştirmeye derinlemesine dalışlar için MetaMask Developer YouTube kanalına göz at ve Twitter üzerinden bizi takip ederek en güncel içeriklerimizden haberdar ol.