如何使用 IPFS 在 Flow 上建立NFT市場

買賣虛擬貨幣

正如我們所期望的那樣,Flow擁有一些有關此概念的出色文件,但是我們將擴充套件這些文件,並使它們適合IPFS託管內容的範例。

配置

希望您一直關注前面的兩個教程。如果是這樣,您將擁有繼續所需的所有入門程式碼,我們將簡單地新增到該程式碼中。如果您還沒有開始其他教程,那麼您將會迷路。因此,一定要回過頭來逐步學習這些教程。

現在,繼續並開啟您的專案。

建立合同

除了我們已經建立的市場之外,市場還需要一些其他的東西。讓我們在這裡列出這些東西:

支付機制(即可替代令牌)

代幣轉移能力

代幣供應設定

由於Flow Emulator是Flow區塊鏈的記憶體表示形式,因此您需要確保在執行此步驟之前執行先前的教程,並確保保持模擬器執行。假設您已完成此操作,讓我們建立一個可替代的代幣合約,該合約可用於購買NFT時進行支付。

明確地說,為我們將要建立的這些可替代令牌建立購買機制不在本教程的討論範圍之內。我們只是準備鑄造令牌並將令牌轉移到將要購買NFT的賬戶中。

在pinata-party本系列的第1部分中建立的目錄中,進入該cadence/contracts資料夾並建立一個名為的新檔案PinnieToken.cdc。這將是我們的可替代代幣合同。我們將從定義空合同開始,如下所示:

pub contract PinnieToken {}

主合同程式碼中的每個程式碼都將是其自己的Github要點,我將在最後提供完整的合同程式碼。我們要新增到該合同的第一部分是與我們的令牌和提供者資源關聯的令牌釋出變數。

pub var totalSupply: UFix64pub var tokenName: Stringpub resource interface Provider {    pub fun withdraw(amount: UFix64): @Vault {        post {            result.balance == UFix64(amount):                "Withdrawal amount must be the same as the balance of the withdrawn Vault"        }    }}

將上述程式碼新增到我們最初建立的空合同中。totalSupply和tokenName變數是不言自明的。我們將在稍後初始化令牌合約時進行設定。

Provider我們建立的資源介面需要更多說明。該資源只是定義了一個將公開的功能,但是有趣的是,它仍然只能由對其執行提款的賬戶所有者呼叫。也就是說,我無法對您的賬戶執行提款請求。

接下來,我們將定義另外兩個公共資源介面:

pub resource interface Receiver {    pub fun deposit(from: @Vault)}pub resource interface Balance {    pub var balance: UFix64}

這些直接位於Provider資源介面下方。該Receiver介面包括任何人都可以執行的功能。這樣可以確保只要收款人初始化了可以處理我們透過此合同建立的令牌的保管庫,就可以執行向賬戶中的存款。您會很快看到Vault即將到來的參考。

該Balance資源將簡單地返回任何給定賬戶的新令牌餘額。

現在讓我們建立Vault上面提到的資源。在Balance資源下面新增以下內容:

pub resource Vault: Provider, Receiver, Balance {    pub var balance: UFix64    init(balance: UFix64) {        self.balance = balance    }    pub fun withdraw(amount: UFix64): @Vault {        self.balance = self.balance - amount        return <-create Vault(balance: amount)    }    pub fun deposit(from: @Vault) {        self.balance = self.balance + from.balance        destroy from    }}

該Vault資源是這裡的主要景點。我之所以這樣說,是因為沒有它,什麼都不會發生。如果對Vault資源的引用未儲存在賬戶的儲存器中,則該賬戶無法接收這些特定令牌。這意味著該賬戶無法傳送這些令牌。這也意味著該賬戶無法購買NFT。而且,我們要購買一些NFT。

現在,讓我們看一下Vault資源實現的內容。您可以看到我們的Vault資源繼承了Provider,Receiver和Balance資源介面,然後定義了兩個函式:withdraw和deposit。如果您還記得的話,該Provider介面可以訪問該withdraw函式,因此我們僅在此處定義該函式。該Receiver介面可以訪問deposit我們在此處定義的功能。

您還將注意到,我們有一個balance使用Vault資源初始化的變數。該餘額代表給定賬戶的餘額。

現在,讓我們弄清楚如何確保賬戶可以訪問該Vault介面。請記住,沒有它,我們要建立的令牌將不會發生任何事情。在Vault介面下方,新增以下功能:

pub fun createEmptyVault(): @Vault {     return <-create Vault(balance: 0.0)}

顧名思義,此功能會Vault為賬戶建立一個空資源。餘額當然是0。

我認為現在是時候建立我們的鑄造能力了。在createEmptyVault函式下面新增它:

pub resource VaultMinter {    pub fun mintTokens(amount: UFix64, recipient: Capability<&AnyResource{Receiver}>) {        let recipientRef = recipient.borrow()            ?? panic("Could not borrow a receiver reference to the vault")        PinnieToken.totalSupply = PinnieToken.totalSupply + UFix64(amount)        recipientRef.deposit(from: <-create Vault(balance: amount))    }}

該VaultMinter資源是公共資源,但預設情況下僅對合同賬戶所有者可用。可以使其他人可以使用該資源,但是在本教程中我們將不會專注於該資源。

該VaultMinter資源只有一個功能:mintTokens。該功能需要一定數量的薄荷糖和接收者。只要收件人Vault儲存了資源,新建立的令牌就可以存入該賬戶。鑄造令牌時,totalSupply必須更新變數,因此我們將鑄造的金額新增到先前的供應中以獲得新的供應。

好的,到目前為止,我們已經做到了。我們的合同只剩下一件事了。我們需要初始化它。在VaultMinter資源之後新增它。

init() {    self.totalSupply = 30.0    self.tokenName = "Pinnie"    let vault <- create Vault(balance: self.totalSupply)    self.account.save(<-vault, to: /storage/MainVault)    self.account.save(<-create VaultMinter(), to: /storage/MainMinter)    self.account.link<&VaultMinter>(/private/Minter, target: /storage/MainMinter)}

當我們初始化合同時,我們需要設定總供應量。這可以是您選擇的任何數字。就我們的示例而言,我們將其初始化為30。我們將tokenName設定為“ Pinnie”,因為畢竟這完全是關於Pinata派對的。我們還建立了一個vault變數,該變數建立Vault具有初始供應量的資源並將其儲存在合同建立者的賬戶中。

部署和鑄造令牌

我們需要flow.json在專案中更新檔案,以便我們可以部署此新合同。您在先前的教程中可能發現的一件事是,flow.json與執行事務時相比,在部署合同時檔案的結構需要稍有不同。確保您flow.json引用了新合同,並具有如下所示的emulator-account關鍵引用:

{  "emulators": {    "default": {      "port": 3569,      "serviceAccount": "emulator-account"    }  },  "contracts": {    "PinataPartyContract": "./cadence/contracts/PinataPartyContract.cdc",     "PinnieToken": "./cadence/contracts/PinnieToken.cdc"  },  "networks": {    "emulator": {      "host": "127.0.0.1:3569",      "chain": "flow-emulator"    }  },  "accounts": {    "emulator-account": {      "address": "f8d6e0586b0a20c7",      "keys": "e5ca2b0946358223f0555206144fe4d74e65cbd58b0933c5232ce195b9058cdd"    }  },  "deployments": {    "emulator": {      "emulator-account": ["PinataPartyContract", "PinnieToken"]    }  }}

在pinata-party專案目錄中的另一個終端視窗中,執行flow project deploy。您將獲得已部署合同的賬戶(與部署NFT合同的賬戶相同)。將其保留在某個地方,因為我們將很快使用它。

現在,讓我們測試一下鑄造功能。我們將建立一個允許我們鑄造Pinnie令牌的交易,但首先,我們需要再次更新flow.json。(執行此操作可能有更好的方法,但這是我在模擬器上執行操作時發現的方法)。在emulator-account更改下,您的json返回如下所示:

本key場再次成為了privateKey現場,然後我們在增加sigAlogrithm,hashAlgorithm和chain效能。無論出於何種原因,此格式都可用於傳送交易,而另一種格式則可用於部署合同。

好的,我們還需要做一件事,以允許我們用來部署合同的賬戶建立一些Pinnies。我們需要建立一個連結。這是一個簡單的事務,僅提供對鑄造功能的訪問。因此,在您的交易資料夾中,新增一個名為的檔案LinkPinnie.cdc。在該檔案中,新增:

import PinnieToken from 0xf8d6e0586b0a20c7transaction {  prepare(acct: AuthAccount) {    acct.link<&PinnieToken.Vault{PinnieToken.Receiver, PinnieToken.Balance}>(/public/MainReceiver, target: /storage/MainVault)    log("Public Receiver reference created!")  }  post {    getAccount(0xf8d6e0586b0a20c7).getCapability<&PinnieToken.Vault{PinnieToken.Receiver}>(/public/MainReceiver)                    .check():                    "Vault Receiver Reference was not created correctly"    }}

此交易將匯入我們的Pinnie合同。然後,它建立一個事務,該事務將Receiver資源連結到最終將進行鑄造的計數。對於需要引用該資源的其他賬戶,我們也會做同樣的事情。

建立事務後,讓我們繼續執行它。在專案根目錄的終端中,執行:

flow transactions send --code transactions/LinkPinnie.cdc

現在,我們準備繼續前進。讓我們鑄造一些小兔子!為此,我們需要編寫一個事務,程式碼如下:

import PinnieToken from 0xf8d6e0586b0a20c7transaction {    let mintingRef: &PinnieToken.VaultMinter    var receiver: Capability<&PinnieToken.Vault{PinnieToken.Receiver}>  prepare(acct: AuthAccount) {        self.mintingRef = acct.borrow<&PinnieToken.VaultMinter>(from: /storage/MainMinter)            ?? panic("Could not borrow a reference to the minter")                let recipient = getAccount(0xf8d6e0586b0a20c7)              self.receiver = recipient.getCapability<&PinnieToken.Vault{PinnieToken.Receiver}>(/public/MainReceiver)  }    execute {        self.mintingRef.mintTokens(amount: 30.0, recipient: self.receiver)        log("30 tokens minted and deposited to account 0xf8d6e0586b0a20c7")    }}

此程式碼應新增到MintPinnie.cdc您的交易資料夾中的檔案中。此事務在頂部匯入我們的PinnieToken合同,然後建立對我們在該合同中定義的兩個資源的引用。我們定義了一種VaultMinter資源和一種Receiver資源,等等。這兩個資源正在這裡使用。的VaultMinter是,如你所期望,所用薄荷令牌。該Receiver資源用於處理將新令牌儲存到賬戶中的情況。

這只是一項測試,以確保我們可以鑄造代幣並將其存入我們自己的賬戶。很快,我們將建立一個新賬戶,即薄荷代幣,並將其存入另一個賬戶。

從命令列執行事務,如下所示:

記住,我們使用模擬器賬戶部署了合同,因此,除非提供連結並允許其他賬戶鑄造,否則模擬器賬戶必須進行鑄造。

現在,讓我們建立一個指令碼來檢查我們的Pinnie餘額,以確保一切正常。在您的專案的scripts資料夾中,建立一個名為的檔案CheckPinnieBalance.cdc並新增以下內容:

import PinnieToken from 0xf8d6e0586b0a20c7pub fun main(): UFix64 {    let acct1 = getAccount(0xf8d6e0586b0a20c7)    let acct1ReceiverRef = acct1.getCapability<&PinnieToken.Vault{PinnieToken.Balance}>(/public/MainReceiver)        .borrow()        ?? panic("Could not borrow a reference to the acct1 receiver")    log("Account 1 Balance")    log(acct1ReceiverRef.balance)    return acct1ReceiverRef.balance}

同樣,我們正在匯入合同,正在對要檢查的賬戶(模擬器賬戶)進行硬編碼,並借用了對Pinnie令牌的Balance資源的引用。我們在指令碼末尾返回餘額,以便將其列印在命令列上。

建立合同時,請記住,我們設定了30個代幣的初始供應量。因此,當我們執行該MintPinnie交易時,我們應該已經鑄造了另外30個令牌並將其存入模擬器賬戶。這意味著,如果一切順利,此餘額指令碼應顯示60個令牌。

我們可以使用以下命令執行指令碼:

flow scripts execute scripts/CheckPinnieBalance.cdc

結果應該是這樣的:

極好的!我們可以鑄造代幣。讓我們確保可以鑄造一些並將它們存入其他人的新賬戶中(真的,它仍然只是您,但我們可以假裝)。

要建立一個新賬戶,您首先需要生成一個新的金鑰對。為此,請執行以下命令:

flow keys generate

這將生成一個私鑰和一個公鑰。我們需要公鑰來生成一個新賬戶,並且不久我們將使用私鑰來更新我們的flow.json。因此,讓我們現在建立一個新賬戶。執行以下命令:

flow accounts create --key YourNewPublicKey

這將建立一個交易,並且交易的結果將包括新的賬戶地址。建立新賬戶後,您應該已經收到了交易ID。複製該事務ID,然後執行以下命令:

flow transactions status YourTransactionId

此命令應導致如下所示:

列出的地址是新的賬戶地址。讓我們用它來更新我們的flow.json檔案。

在該檔案內的accounts物件下,讓我們建立對該賬戶的新引用。還記得以前的私鑰嗎?我們現在需要它。將您的賬戶物件設定為如下所示:

"accounts": {  "emulator-account": {    "address": "f8d6e0586b0a20c7",    "keys": "e5ca2b0946358223f0555206144fe4d74e65cbd58b0933c5232ce195b9058cdd"  },  "second-account": {    "address": "01cf0e2f2f715450",    "keys": "9bde7092cc0695c67f896e4375bffa0b5bf0a63ce562195a36f864ba7c3b09e3"  }},

現在,我們有了第二個賬戶,可用於將Pinnie令牌傳送至該賬戶。讓我們看看它的外觀。

傳送可替代令牌

我們的主要賬戶(建立了Pinnie令牌的賬戶)目前有60個令牌。讓我們看看是否可以將其中一些令牌傳送到我們的第二個賬戶。

如果您記得較早,則每個賬戶都必須有一個空的保管庫才能接受Pinnie令牌,並且還需要具有指向Pinnie Token合同上的資源的連結。讓我們從建立一個空庫開始。為此,我們需要進行新的交易。因此,請在您的transactions資料夾中建立一個名為的檔案CreateEmptyPinnieVault.cdc。在該檔案內,新增以下內容:

import PinnieToken from 0xf8d6e0586b0a20c7transaction {  prepare(acct: AuthAccount) {    let vaultA <- PinnieToken.createEmptyVault()          acct.save<@PinnieToken.Vault>(<-vaultA, to: /storage/MainVault)    log("Empty Vault stored")    let ReceiverRef = acct.link<&PinnieToken.Vault{PinnieToken.Receiver, PinnieToken.Balance}>(/public/MainReceiver, target: /storage/MainVault)    log("References created")  }    post {        getAccount(NEW_ACCOUNT_ADDRESS).getCapability<&PinnieToken.Vault{PinnieToken.Receiver}>(/public/MainReceiver)                        .check():                          "Vault Receiver Reference was not created correctly"    }}

在此交易中,我們正在匯入Pinnie Token合同,我們正在呼叫public函式createEmptyVault,並且我們正在使用Receiver合同上的資源將其與新賬戶關聯起來。

請注意,在本post節中,我們已經進行了檢查。確保將其替換NEW_ACCOUNT_ADDRESS為之前建立的賬戶地址,並以開頭0x。

讓我們現在執行事務。在專案的根目錄中,執行:

flow transactions send --code transactions/CreateEmptyPinnieVault.cdc --signer second-account

請注意,我們將定義signer為second-account。這是為了確保交易是由正確的賬戶執行的,而不是針對我們的原始賬戶執行的emulator-account。完成此操作後,我們現在可以連結到Pinnie令牌資源。執行以下命令:

flow transactions send --code transactions/LinkPinnie.cdc --signer second-account

所有這些都已設定好,以便我們可以將令牌從傳輸emulator-account到second-account。為此,您需要(您猜對了)另一筆交易。讓我們現在寫。

在您的transactions資料夾中,建立一個名為的檔案TransferPinnieToken.cdc。在該檔案內,新增以下內容:

import PinnieToken from 0xf8d6e0586b0a20c7transaction {  var temporaryVault: @PinnieToken.Vault  prepare(acct: AuthAccount) {    let vaultRef = acct.borrow<&PinnieToken.Vault>(from: /storage/MainVault)        ?? panic("Could not borrow a reference to the owner's vault")          self.temporaryVault <- vaultRef.withdraw(amount: 10.0)  }  execute {    let recipient = getAccount(NEW_ACCOUNT_ADDRESS)    let receiverRef = recipient.getCapability(/public/MainReceiver)                      .borrow<&PinnieToken.Vault{PinnieToken.Receiver}>()                      ?? panic("Could not borrow a reference to the receiver")    receiverRef.deposit(from: <-self.temporaryVault)    log("Transfer succeeded!")  }}

像往常一樣,我們正在匯入Pinnie Token合同。然後,我們將建立對Pinnie令牌保管庫的臨時引用。我們這樣做是因為在處理可替代令牌時,所有事情都在庫中進行。因此,我們需要從emulator-account的保管庫中提取令牌,將其放入臨時保管庫,然後將該臨時保管庫傳送給接收者(second-account)。

在第10行,您看到我們提取併傳送到的金額second-account為10個令牌。看起來很公平。我們的朋友,second-account不要太貪心。

確保NEW_ACCOUNT_ADDRESS用second-account地址替換的值。以開頭0x。完成後,讓我們執行事務,執行:

flow transactions send --code transactions/TransferPinnieTokens.cdc --signer emulator-account

該signer需求是模擬賬戶,因為模擬器賬戶是唯一一個令牌現在。執行上述交易後,我們現在將擁有兩個帶有令牌的賬戶。讓我們證明這一點。

開啟CheckPinnieBalance指令碼,將第3行上的賬戶地址替換為的地址second-account。同樣,請確保在地址前面加上0x。儲存該檔案,然後像這樣執行指令碼:

flow scripts execute --code scripts/CheckPinnieBalance.cdc

您應該看到以下結果:

到此為止,您現在已經鑄造了可以用作貨幣的可替代令牌,並且已將其中一些令牌轉移給了另一個使用者。現在,剩下的就是允許第二個賬戶從市場購買我們的NFT。

建立市場

我們將簡單地更新本系列第二篇教程中的React程式碼,以充當我們的市場。我們將需要進行製作,以便在Pinnie代幣中的價格旁邊顯示NFT。我們將需要一個允許使用者購買NFT的按鈕。

在使用前端程式碼之前,我們需要再建立一個合同。為了擁有一個市場,我們需要一個可以處理市場建立和管理的合同。現在讓我們來照顧它。

在您的cadence/contracts資料夾中,建立一個名為的新檔案MarketplaceContract.cdc。合同比其他合同大,因此我將其分成幾個程式碼段,然後在完成構建後參考完整合同。

首先將以下內容新增到您的檔案中:

import PinataPartyContract from 0xf8d6e0586b0a20c7import PinnieToken from 0xf8d6e0586b0a20c7pub contract MarketplaceContract {  pub event ForSale(id: UInt64, price: UFix64)  pub event PriceChanged(id: UInt64, newPrice: UFix64)  pub event TokenPurchased(id: UInt64, price: UFix64)  pub event SaleWithdrawn(id: UInt64)    pub resource interface SalePublic {      pub fun purchase(tokenID: UInt64, recipient: &AnyResource{PinataPartyContract.NFTReceiver}, buyTokens: @PinnieToken.Vault)      pub fun idPrice(tokenID: UInt64): UFix64?      pub fun getIDs(): [UInt64]  }}

我們將同時匯入我們的NFT合約和可替代令牌合約,因為它們將與該市場合約一起使用。在合同定義中,我們定義了四個事件:ForSale(指示NFT正在出售),PriceChanged(指示NFT的價格變化),TokenPurchased(指示已購買NFT)和SaleWithdrawn(指示從NFT中刪除了NFT)市場)。

在這些事件發射器的下方,我們有一個稱為的資源介面SalePublic。這是我們將向所有人公開的介面,而不僅僅是合同所有者。在此介面內,我們將公開三個我們即將編寫的函式。

接下來,在SalePublic介面下方,我們將新增SaleCollection資源。這是合同的主要重點,所以不幸的是,我無法輕易將其分解成較小的部分。該程式碼段比我想要的要長,但是我們將逐步解決:

pub resource SaleCollection: SalePublic {    pub var forSale: @{UInt64: PinataPartyContract.NFT}    pub var prices: {UInt64: UFix64}    access(account) let ownerVault: Capability<&AnyResource{PinnieToken.Receiver}>    init (vault: Capability<&AnyResource{PinnieToken.Receiver}>) {        self.forSale <- {}        self.ownerVault = vault        self.prices = {}    }    pub fun withdraw(tokenID: UInt64): @PinataPartyContract.NFT {        self.prices.remove(key: tokenID)        let token <- self.forSale.remove(key: tokenID) ?? panic("missing NFT")        return <-token    }    pub fun listForSale(token: @PinataPartyContract.NFT, price: UFix64) {        let id = token.id        self.prices[id] = price        let oldToken <- self.forSale[id] <- token        destroy oldToken        emit ForSale(id: id, price: price)    }    pub fun changePrice(tokenID: UInt64, newPrice: UFix64) {        self.prices[tokenID] = newPrice        emit PriceChanged(id: tokenID, newPrice: newPrice)    }    pub fun purchase(tokenID: UInt64, recipient: &AnyResource{PinataPartyContract.NFTReceiver}, buyTokens: @PinnieToken.Vault) {        pre {            self.forSale[tokenID] != nil && self.prices[tokenID] != nil:                "No token matching this ID for sale!"            buyTokens.balance >= (self.prices[tokenID] ?? 0.0):                "Not enough tokens to by the NFT!"        }        let price = self.prices[tokenID]!        self.prices[tokenID] = nil        let vaultRef = self.ownerVault.borrow()            ?? panic("Could not borrow reference to owner token vault")        vaultRef.deposit(from: <-buyTokens)        let metadata = recipient.getMetadata(id: tokenID)        recipient.deposit(token: <-self.withdraw(tokenID: tokenID), metadata: metadata)        emit TokenPurchased(id: tokenID, price: price)    }    pub fun idPrice(tokenID: UInt64): UFix64? {        return self.prices[tokenID]    }    pub fun getIDs(): [UInt64] {        return self.forSale.keys    }    destroy() {        destroy self.forSale    }}

在此資源中,我們首先定義一些變數。我們forSale在prices變數中定義了一個待售令牌的對映,我們在變數中定義了每個待售令牌的價格對映,然後定義了一個受保護的變數,該變數只能由稱為的合同所有者訪問ownerVault。

像往常一樣,當我們在資源上定義變數時,我們需要對其進行初始化。因此,我們可以在init函式中執行此操作,並僅使用空值和所有者的保管庫資源進行初始化。

接下來是該資源的實質。我們正在定義控制我們所有市場行為的功能。這些功能是:

提取

listForSale

changePrice

購買

idPrice

getIDs

破壞

如果您還記得的話,我們以前只公開公開了其中三個功能。這意味著,without,listForSale,changePrice和destroy僅對所列出的NFT所有者可用,這是有道理的。例如,我們不希望任何人能夠更改NFT的價格。

我們Marketplace合同的最後一部分是createSaleCollection功能,這允許將集合作為資源新增到賬戶。看起來像這樣,位於SaleCollection資源之後:

pub fun createSaleCollection(ownerVault: Capability<&AnyResource{PinnieToken.Receiver}>): @SaleCollection {  return <- create SaleCollection(vault: ownerVault)}

有了該合同後,讓我們使用我們的模擬器賬戶進行部署。從專案的根目錄執行:

flow project deploy

這將部署Marketplace合同,並允許我們在前端應用程式中使用它。因此,讓我們開始更新前端應用程式。

前端

如前所述,我們將使用在上一篇文章中奠定的基礎來建立我們的市場。因此,在您的專案中,您應該已經有一個frontend目錄。轉到該目錄,讓我們看一下該App.js檔案。

目前,我們具有身份驗證功能,並且能夠獲取單個NFT並顯示其後設資料。我們想複製此內容,但獲取儲存在Marketplace合同中的所有令牌。我們還希望啟用購買功能。並且,如果您擁有令牌,則應該能夠設定要出售的令牌並更改令牌的價格。

我們將要更改TokenData.js檔案以支援所有這些功能,因此請開啟它。用以下內容替換該檔案中的所有內容:

import React, { useState, useEffect } from "react";import * as fcl from "@onflow/fcl";const TokenData = () => {  useEffect(() => {        checkMarketplace()  }, []);  const checkMarketplace = async () => {    try {      const encoded = await fcl.send([        fcl.script`       import MarketplaceContract from 0xf8d6e0586b0a20c7        pub fun main(): [UInt64] {            let account1 = getAccount(0xf8d6e0586b0a20c7)            let acct1saleRef = account1.getCapability<&AnyResource{MarketplaceContract.SalePublic}>(/public/NFTSale)                .borrow()                ?? panic("Could not borrow acct2 nft sale reference")            return acct1saleRef.getIDs()        }        `      ]);      const decoded = await fcl.decode(encoded);      console.log(decoded);     } catch (error) {      console.log("NO NFTs FOR SALE")    }      }  return (    <div className="token-data">          </div>  );};export default TokenData;

我們在此處對一些值進行硬編碼,因此在實際應用中,請務必考慮如何動態獲取賬戶地址之類的資訊。您還將注意到,在該checkMarketplace函式中,我們將所有內容包裝在try / catch中。這是因為fcl.send當沒有列出要出售的NFT時,該函式將丟擲。

如果透過轉到前端目錄並執行來啟動前端應用程式npm start,則應該在控制檯中看到“無NFT可供出售”。

為了簡潔起見,我們將透過Flow CLI工具列出要出售的鑄造NFT。但是,您可以透過UI擴充套件本教程。在您的root pinata-party專案的transactions資料夾中,建立一個名為的檔案ListTokenForSale.cdc。在該檔案內,新增以下內容:

import PinataPartyContract from 0xf8d6e0586b0a20c7import PinnieToken from 0xf8d6e0586b0a20c7import MarketplaceContract from 0xf8d6e0586b0a20c7transaction {    prepare(acct: AuthAccount) {        let receiver = acct.getCapability<&{PinnieToken.Receiver}>(/public/MainReceiver)        let sale <- MarketplaceContract.createSaleCollection(ownerVault: receiver)        let collectionRef = acct.borrow<&PinataPartyContract.Collection>(from: /storage/NFTCollection)            ?? panic("Could not borrow owner's nft collection reference")        let token <- collectionRef.withdraw(withdrawID: 1)        sale.listForSale(token: <-token, price: 10.0)        acct.save(<-sale, to: /storage/NFTSale)        acct.link<&MarketplaceContract.SaleCollection{MarketplaceContract.SalePublic}>(/public/NFTSale, target: /storage/NFTSale)        log("Sale Created for account 1. Selling NFT 1 for 10 tokens")    }}

在此交易中,我們將匯入我們建立的所有三個合同。我們需要PinnieToken接收器功能,因為我們正在接受PinnieTokens中的付款。我們還需要訪問createSaleCollectionMarketplaceContract上的功能。然後,我們需要參考要出售的NFT。我們撤回該NFT,將其出售,售價為10.0 PinnieTokens,然後將其儲存到NFTSale儲存路徑中。

如果執行以下命令,則應該可以成功列出之前建立的NFT。

flow transactions execute --code transactions/ListTokenForSale.cdc

現在,返回您的React App頁面並重新整理。在控制檯中,您應該看到類似以下內容的內容:

這是指定要出售的賬戶地址的令牌ID陣列。這意味著,我們知道要查詢和獲取其後設資料的ID。在我們的簡單示例中,僅列出了一個令牌,這是我們建立的也是唯一的令牌,因此其tokenID為1。

在將程式碼新增到React App之前,將以下匯入新增到TokenData.js檔案頂部:

import * as t from "@onflow/types"

這使我們可以將引數傳遞給使用傳送的指令碼fcl。

好的,現在我們可以使用我們的tokenID陣列,並使用以前必須獲取令牌後設資料的一些程式碼。在TokenData.js檔案內部和checkMarketplace函式內部,在decoded變數後新增以下程式碼:

for (const id of decoded) {    const encodedMetadata = await fcl.send([      fcl.script`        import PinataPartyContract from 0xf8d6e0586b0a20c7        pub fun main(id: Int) : {String : String} {          let nftOwner = getAccount(0xf8d6e0586b0a20c7)            let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)          let receiverRef = capability.borrow()              ?? panic("Could not borrow the receiver reference")          return receiverRef.getMetadata(id: 1)        }      `,      fcl.args([        fcl.arg(id, t.Int)          ]),    ]);    const decodedMetadata = await fcl.decode(encodedMetadata);    marketplaceMetadata.push(decodedMetadata);  }  console.log(marketplaceMetadata);

如果您在控制檯中檢視,現在應該看到一個專門與我們要出售的代幣相關聯的後設資料陣列。在渲染任何東西之前,我們需要做的最後一件事就是找出我們列出的代幣的價格。

在decodedMetadata變數下方和marketplaceMetadata.push(decodedMetadata)函式之前,新增以下內容:

const encodedPrice = await fcl.send([  fcl.script`    import MarketplaceContract from 0xf8d6e0586b0a20c7    pub fun main(id: UInt64): UFix64? {        let account1 = getAccount(0xf8d6e0586b0a20c7)        let acct1saleRef = account1.getCapability<&AnyResource{MarketplaceContract.SalePublic}>(/public/NFTSale)            .borrow()            ?? panic("Could not borrow acct nft sale reference")        return acct1saleRef.idPrice(tokenID: id)    }  `,   fcl.args([    fcl.arg(id, t.UInt64)  ])])const decodedPrice = await fcl.decode(encodedPrice)decodedMetadata["price"] = decodedPrice;marketplaceMetadata.push(decodedMetadata);

我們正在獲取列出的每個NFT的價格,當我們收到價格返還價格時,我們會將其新增到令牌後設資料中,然後再將該後設資料推入marketplaceMetadata陣列。

現在,在控制檯中,您應該會看到類似以下內容的內容:

這很棒!現在,我們可以渲染列出的代幣並顯示價格。現在就開始吧。

在顯示marketplaceMetadata陣列的console.log語句下,新增以下內容:

setTokensToSell(marketplaceMetadata)

您還需要在TokenData主函式宣告的開始下方新增以下內容:

const TokenData = () => {  const [tokensToSell, setTokensToSell] = useState([])}

有了這些東西,您就可以渲染您的市場。在return語句中,新增以下內容:

return (  <div className="token-data">    {      tokensToSell.map(token => {        return (          <div key={token.uri} className="listing">            <div>              <h3>{token.name}</h3>              <h4>Stats</h4>              <p>Overall Rating: {token.rating}</p>              <p>Swing Angle: {token.swing_angle}</p>              <p>Swing Velocity: {token.swing_velocity}</p>              <h4>Video</h4>              <video loop="true" autoplay="" playsinline="" preload="auto" width="85%">                <source src={`https://ipfs.io/ipfs/${token["uri"].split("://")[1]}`} type="video/mp4" />              </video>              <h4>Price</h4>              <p>{parseInt(token.price, 10).toFixed(2)} Pinnies</p>              <button className="btn-primary">Buy Now</button>            </div>          </div>        )      })    }  </div>);

如果您使用的是樣式,這就是我新增到App.css檔案中的內容:

.listing {  max-width: 30%;  padding: 50px;  margin: 2.5%;  box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);}

您的應用現在應該看起來像這樣:

我們需要做的最後一件事是連線“立即購買”按鈕,並允許非NFT所有者的人購買NFT。

購買NFT

通常,您需要透過遠端發現節點終結點進行錢包發現和事務處理。我們實際上在本系列的第二部分中的React應用程式中進行了設定。但是,我們正在使用本地Flow模擬器。因此,我們需要執行本地開發者錢包,然後需要更新React應用程式的環境變數。

讓我們進行設定。首先,克隆本地開發者錢包。從pinata-party專案的根目錄執行:

git clone [email protected]:onflow / fcl-dev-wallet.git

完成後,轉到資料夾:

cd fcl-dev-wallet

現在,我們需要複製示例環境檔案並建立開發錢包將使用的本地環境檔案:

cp .env.example .env.local

安裝依賴項:

npm install

好的,完成後,開啟.env.local檔案。您會看到它引用了一個賬戶和一個私鑰。之前,我們建立了一個新賬戶,該賬戶將從市場上購買NFT。更改.env.local檔案中的賬戶以匹配您建立的新賬戶。還要更改私鑰和公鑰。對於FLOW_ACCOUNT_KEY_ID環境變數,將其更改為1。模擬器賬戶是鍵0。

現在,您可以執行npm run dev以啟動錢包伺服器。

返回frontend專案目錄,找到.env檔案,然後更新REACT_APP_WALLET_DISCOVERY以指向http://localhost:3000/fcl/authz。這樣做之後,您需要重新啟動React應用程式。

下一步是連線前端“立即購買”按鈕以實際傳送交易以購買令牌。開啟TokenData.js檔案,讓我們建立一個buyToken函式,如下所示:

const buyToken = async (tokenId) => {  const txId = await fcl  .send([    fcl.proposer(fcl.authz),    fcl.payer(fcl.authz),    fcl.authorizations([fcl.authz]),    fcl.limit(50),    fcl.args([      fcl.arg(tokenId, t.UInt64)    ]),    fcl.transaction`      import PinataPartyContract from 0xf8d6e0586b0a20c7      import PinnieToken from 0xf8d6e0586b0a20c7      import MarketplaceContract from 0xf8d6e0586b0a20c7      transaction {          let collectionRef: &AnyResource{PinataPartyContract.NFTReceiver}          let temporaryVault: @PinnieToken.Vault          prepare(acct: AuthAccount) {              self.collectionRef = acct.borrow<&AnyResource{PinataPartyContract.NFTReceiver}>(from: /storage/NFTCollection)!              let vaultRef = acct.borrow<&PinnieToken.Vault>(from: /storage/MainVault)                  ?? panic("Could not borrow owner's vault reference")              self.temporaryVault <- vaultRef.withdraw(amount: 10.0)          }          execute {              let seller = getAccount(0xf8d6e0586b0a20c7)              let saleRef = seller.getCapability<&AnyResource{MarketplaceContract.SalePublic}>(/public/NFTSale)                  .borrow()                  ?? panic("Could not borrow seller's sale reference")              saleRef.purchase(tokenID: tokenId, recipient: self.collectionRef, buyTokens: <-self.temporaryVault)          }      }    `,        ])  await fcl.decode(txId);  checkMarketplace();}

現在我們只需要onClick為“立即購買”按鈕新增一個處理程式即可。這就像將按鈕更新為如下所示一樣簡單:

<button onClick={() => buyToken(1)} className="btn-primary">Buy Now</button>
我們在這裡對tokenID進行了硬編碼,但是您可以輕鬆地從我們執行的早期指令碼中獲取它。

現在,當您進入React應用程式並單擊“立即購買”按鈕時,您應該會看到類似以下的螢幕:

正如標題中所說的那樣,fcl-dev-wallet處於Alpha狀態,所以事實是,事務的執行可能會成功也可能不會成功。但是,進一步說明您的應用程式可以正常執行,而fcl庫也可以正常執行。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;