Linea'da NFT Pazaryeri Oluşturun

Linea üzerinde merkezi olmayan bir NFT pazaryeri oluşturmayı ve dağıtmayı öğrenin.

10 dk okuma
Linea'da NFT Pazaryeri Oluşturun

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:

  1. 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.

  2. 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.

  3. Toplu NFT satın alma: Gas maliyetlerini azaltmak için tek bir işlemde birden fazla NFT satın alınabilir.

  4. 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 market

Market 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-commit

NFT 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.

// SPDX-License-Identifier: MIT
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;
    }
    // NFT Contract => Token ID => Listing
    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");
        _;
    }
    /**
     * @dev List an NFT for sale.
     * @param nftContract The address of the NFT contract.
     * @param tokenId The ID of the token to list.
     * @param price The price of the NFT in wei.
     */
    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);
    }
    /**
     * @dev Buy a listed NFT.
     * @param nftContract The address of the NFT contract.
     * @param tokenId The ID of the token to buy.
     */
    function buyNFT(address nftContract, uint256 tokenId)
        public
        payable
        isListed(nftContract, tokenId)
    {
        Listing memory listing = listings[nftContract][tokenId];
        require(msg.value >= listing.price, "Insufficient funds");
        // Remove listing
        delete listings[nftContract][tokenId];
        // Transfer payment to the seller
        (bool sent, ) = listing.seller.call{value: listing.price}("");
        require(sent, "Failed to send Ether");
        // Transfer NFT to the buyer
        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]);   
        }
    }
    /**
     * @dev Delist an NFT from the marketplace.
     * @param nftContract The address of the NFT contract.
     * @param tokenId The ID of the token to delist.
     */
    function delistNFT(address nftContract, uint256 tokenId)
        external
        isListed(nftContract, tokenId)
        isSeller(nftContract, tokenId, msg.sender)
    {
        Listing memory listing = listings[nftContract][tokenId];

        // Remove listing
        delete listings[nftContract][tokenId];
        // Return NFT to the seller
        IERC721(nftContract).transferFrom(address(this), listing.seller, tokenId);
        emit Delisted(nftContract, tokenId, listing.seller);
    }
}
Kopyala

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; // NFT seller
        uint256 price;  // NFT price
    }
Kopyala

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.

 // NFT Contract => Token ID => Listing
    mapping(address => mapping(uint256 => Listing)) public listings;
Kopyala

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);
    }
Kopyala

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");
        // Remove listing
        delete listings[nftContract][tokenId];
        // Transfer payment to the seller
        (bool sent, ) = listing.seller.call{value: listing.price}("");
        require(sent, "Failed to send Ether");
        // Transfer NFT to the buyer
        IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
        emit Purchase(nftContract, tokenId, msg.sender, listing.price);
    }
Kopyala

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]);   
        }
    }
Kopyala

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];

        // Remove listing
        delete listings[nftContract][tokenId];
        // Return NFT to the seller
        IERC721(nftContract).transferFrom(address(this), listing.seller, tokenId);
        emit Delisted(nftContract, tokenId, listing.seller);
    }
Kopyala

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
Kopyala

Test paketine genel bakış

src klasöründe bir NFT.sol dosyası oluştur ve aşağıdaki kodu dosyaya ekle.

// SPDX-License-Identifier: MIT
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;
    }
    /// @dev Function to mint a new NFT
    function mint(address to) external onlyOwner {
        uint256 tokenId = _tokenIdCounter;
        _tokenIdCounter += 1;
        _safeMint(to, tokenId);
        emit Minted(to, tokenId);
    }
    /// @dev Override base token URI for metadata
    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }
    /// @dev Function to update the base token URI
    function setBaseTokenURI(string memory newBaseTokenURI) external onlyOwner {
        _baseTokenURI = newBaseTokenURI;
    }
    /// @dev Function to retrieve the current token ID count
    function getTokenCount() external view returns (uint256) {
        return _tokenIdCounter;
    }
}
Kopyala

test klasöründe bir market.t.sol dosyası oluştur ve aşağıdaki kodu dosyaya ekle.

// SPDX-License-Identifier: UNLICENSED
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));

    }
}
Kopyala

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 test

Linea ü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/ // Infura RPC endpoint for connecting to Linea 
PRIVATE_KEY=************************************************* // Wallet private key for signing transactions LINEA_API_KEY=************************* // API key for accessing Linea services via Infura
Kopyala

Aşağıdaki ayrıntıları foundry.toml'a ekle:

[etherscan]
linea = { key = "${LINEA_API_KEY}", url = "https://api-sepolia.lineascan.build/api" }
Kopyala

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
Kopyala

Dağıtım

make deploy

Doğrulama

make verify

Sonuç

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.

Yapay zeka tarafından çevrildi. Hatalar içerebilir. Lütfen her zaman bilgileri doğrulayın.

Çeviriyi değerlendir
  • Kingsley Okonkwo
    Kingsley Okonkwo

    Kingsley, a certified Ethereum Blockchain developer, serves as a technical writer at Consensys, with a special focus on developer tools. Before joining Consensys, Kingsley worked as a freelance backend developer on sevral platforms including Gigster and Braintrust. Boasting 5 years of software development experience, he's proficient with tools such as JavaScript (Node.js & React), GoLang, SQL, and Docker. Kingsley excels in producing step-by-step tutorials and how-to guides, utilizing the latest in web3 technology to help fellow developers build better dapps. Currently based in Dubai, Kingsley enjoys playing basketball and soccer in his free time

    Tüm makaleleri oku