Пошаговое руководство по разработке NFT маркетплейса
Поскольку рынок невзаимозаменяемых токенов достигает огромного объема, полезно оглянуться на первые дни NFT и вспомнить проблемы, обнаруженные Cryptokitties.
Cryptokitties были первым примером потенциального массового внедрения NFT, созданным командой Dapper Labs. С тех пор были представлены новые рыночные платформы NFT, такие как Rarible, Sorare и OpenSea, чтобы стимулировать развитие NFT. В исследовании, опубликованном на NonFungible.com, говорится, что общая стоимость транзакций NFT в 2020 году увеличилась на 299% по сравнению с 2019 годом и превысила 250 миллионов долларов. Эксперты прогнозируют, что невзаимозаменяемые токены станут движущей силой виртуальной экономики в ближайшие 10 лет.
Что такое торговая площадка NFT?
Торговые площадки NFT ориентированы на продажу определенных активов. Например, торговая площадка Valuables NFT позволяет пользователям покупать и продавать твиты.
Нишевые торговые площадки набирают популярность, поскольку у них есть четко определенная целевая аудитория. Поэтому вам следует подумать о типе платформы, которую вы запустите, прежде чем создавать рынок NFT.
Как работает торговая площадка NFT?
Как начать разработку NFT Marketplace?
Вам нужно определиться со списком функций и выбрать технологические стеки и стандарты NFT для вашего рынка.
Ниже приведены некоторые функции, которые можно добавить на рынок NFT:
Стек технологий, необходимый для создания торговой площадки NFT
Блокчейн-платформы
Платформы хранения
Стандарты NFT
Front-end Frameworks
В этой статье демонстрируется пример создания торговой площадки NFT с использованием цепочки блоков Flow и сети IPFS Pinata.
Как разработать NFT Marketplace с помощью IPFS и Flow?
Мы поделились примером создания контракта,выпуска токена, создания приложения для просмотра NFT, сделанных с помощью этого контракта, и создания торговой площадки для перемещения NFT другим пользователям.
Начнем с создания контракта и выпуска токена.
Инструменты настройки
Установите Flow CLI в вашу систему.
Существуют разные команды для установки CLI в зависимости от разных операционных систем.
Например, чтобы установить Flow CLI на macOS, используйте команду:
brew install flow-cli
В Windows:
href="https://storage.googleapis.com/flow-cli/install.ps1" rel= https://storage.googleapis.com/flow-cli/install.ps1') }
В Linux:
href="https://storage.googleapis.com/flow-cli/install.sh" rel=>https://storage.googleapis.com/flow-cli/install.sh)
Файлы активов будут храниться в IPFS.
В этом примере мы собираемся использовать Pinata для хранения файлов. Вы можете зарегистрировать бесплатную учетную запись и получить ключ API здесь.
Также важно установить NodeJS и текстовый редактор, чтобы выделить код смарт-контракта Flow.
Второй шаг - создать каталог для проекта с помощью команды:
mkdir pinata-party
Инициализируйте новый потоковый проект и поместите его в этот каталог:
cd pinata-party
flow project init
Теперь откройте проект в редакторе кода и приступим к работе. Сначала создайте папку с именем cadence. Добавьте в эту папку еще одну папку с именем contracts . Наконец, создайте файл в папке контрактов с именем PinataPartyContract.cdc
Прежде чем двигаться дальше, важно указать на все, что мы делаем в отношении платформы блокчейна Flow.
Настройте файл для среды эмулятора, и тогда мы можем приступить к написанию контракта.
Нам нужно обновить объект контракта в flow.json, используя код:
contracts": {
"PinataPartyContract": "./cadence/contracts/PinataPartyContract.cdc"
}
Обновите объект развертывания в этом файле, используя приведенный ниже код:
deployments": {
"emulator": {
"emulator-account": ["PinataPartyContract"]
}
}
Это позволит Flow CLI использовать эмулятор для развертывания нашего контракта. Этот код также ссылается на учетную запись и контракт, которые мы собираемся написать в ближайшее время.
Контракты
Нам нужно создать контракты для выпуска NFT, связывания метаданных с NFT и обеспечения того, чтобы метаданные указывали на базовые активы, хранящиеся в IPFS.
Откройте PinataPartyContract.cdc и выполните следующий код:
pub contract PinataPartyContract {
pub resource NFT {
pub let id: UInt64
init(initID: UInt64) {
self.id = initID
}
}
}
Первый шаг - составить контракт.
Давайте начнем с определения PinataPartyContract и создадим внутри него ресурс. Ресурсы - это элементы, сохраненные в учетных записях пользователей, которые доступны через меры контроля доступа.
NFT должны быть идентифицируемыми, а свойство id позволяет идентифицировать токены.
Затем создается интерфейс ресурсов, чтобы определить, какие возможности доступны другим.
pub resource interface NFTReceiver {
pub fun deposit(token: @NFT, metadata: {String : String})
pub fun getIDs(): [UInt64]
pub fun idExists(id: UInt64): Bool
pub fun getMetadata(id: UInt64) : {String : String}
}
Поместите приведенный выше код под кодом ресурса NFT. Интерфейс ресурса NFTReceiver сообщает, что ресурс может вызывать следующие методы:
getIDs
idExists
deposit
getMetadata
Затем необходимо определить интерфейс сбора токенов. Рассматривайте его как кошелек, в котором хранятся NFT всех пользователей.
pub resource Collection: NFTReceiver {
pub var ownedNFTs: @{UInt64: NFT}
pub var metadataObjs: {UInt64: { String : String }}
init () {
self.ownedNFTs <- {}
self.metadataObjs = {}
}
pub fun withdraw(withdrawID: UInt64): @NFT {
let token <- self.ownedNFTs.remove(key: withdrawID)!
return <-token
}
pub fun deposit(token: @NFT, metadata: {String : String}) {
self.metadataObjs[token.id] = metadata
self.ownedNFTs[token.id] <-! token
}
pub fun idExists(id: UInt64): Bool {
return self.ownedNFTs[id] != nil
}
pub fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
pub fun updateMetadata(id: UInt64, metadata: {String: String}) {
self.metadataObjs[id] = metadata
}
pub fun getMetadata(id: UInt64): {String : String} {
return self.metadataObjs[id]!
}
destroy() {
destroy self.ownedNFTs
}
}
Переменная ownedNFTs отслеживает все NFT, которыми пользователь может владеть от контактора. Переменная metadataObjs уникальна, поскольку мы расширяем функциональность контракта Flow NFT, чтобы хранить отображение метаданных для каждого NFT.
Она сопоставляет идентификатор токена с соответствующими метаданными, что означает, что идентификатор токена необходим, прежде чем мы сможем его установить. Переменные инициализируются, чтобы определить их в ресурсе внутри Flow.
Наконец, у нас будут все доступные функции, необходимые для ресурса коллекции NFT. Так же, как стандартный контракт NFT был расширен для включения отображения metadataObjs, мы расширим функцию депонирования по умолчанию, чтобы она принимала дополнительный параметр метаданных.
Это сделано для того, чтобы гарантировать, что только майнер токена может добавить метаданные к токену.
Мы ограничиваем первоначальное добавление метаданных выполнением майнинга, чтобы сохранить его конфиденциальность. Добавьте следующий код ниже ресурса Collection:
pub fun createEmptyCollection(): @Collection {
return <- create Collection()
}
pub resource NFTMinter {
pub var idCount: UInt64
init() {
self.idCount = 1
}
pub fun mintNFT(): @NFT {
var newNFT <- create NFT(initID: self.idCount)
self.idCount = self.idCount + 1 as UInt64
return <-newNFT
}
}
Во-первых, у нас будет функция для создания пустой коллекции NFT при вызове. Пользователь, взаимодействующий с контрактом, будет иметь место хранения, которое отображает определенный ресурс Collection.
После этого мы создадим еще один ресурс. Без него мы не сможем выпускать токены. NFTMinter включает в себя idCount, который увеличивается каждый раз, чтобы у нас не было дублирующихся идентификаторов для NFT. Он также содержит функцию для создания NFT.
Добавьте инициализатор основного контракта ниже ресурса NFTMinter:
init() {
self.account.save(<-self.createEmptyCollection(),to: /storage/NFTCollection)
self.account.link<&{NFTReceiver}>(/public/NFTReceiver,target:/storage/NFTCollection)
self.account.save(<-create NFTMinter(), to: /storage/NFTMinter)
}
Функция инициализатора вызывается только при развертывании контракта. Она выполняет три действия:
Создает пустую коллекцию для развертывателя коллекции, чтобы контракт-владелец мог создавать и владеть NFT из контракта.
Ресурс NFTMinter хранится в хранилище счета для создателя контракта. Это означает, что только создатель контракта может выпускать токены.
Ресурс Collection публикуется в публичном месте со ссылкой на интерфейс NFTReceiver, созданный в самом начале. Так мы сообщаем контракту, что любой может вызывать функции, определенные в NFTReceiver.
Когда контракт готов, давайте развернем его. Прежде чем развернуть его:
Протестируйте его на игровой площадке Flow Playground.
Перейдите на Flow Playground и нажмите на первый аккаунт в левой боковой панели.
Замените весь код на код контракта и нажмите кнопку Развернуть.
Если все прошло успешно, вы должны увидеть журнал в нижней части экрана:
16:48:55 Deployment Deployed Contract To: 0x01
Поскольку теперь настало время развернуть контракт на локально запущенном эмуляторе, выполните следующую команду:
<p class="in io ip iq ir lt lu dy">flow project start-emulator</p>
Запустив эмулятор и настроив файл flow.json, можно развернуть контракт с помощью следующей команды:
<p class="in io ip iq ir lt lu dy">flow project deploy</p>
Если все прошло успешно, вы должны увидеть результат, подобный этому:
Deploying 1 contracts for accounts: emulator-account
PinataPartyContract → 0xf8d6e0586b0a20c7
Теперь перейдем к выпуску NFT.
Minting NFTs
В этом разделе мы обсудим процесс выпуска NFT с помощью приложения и пользовательского интерфейса. Для получения демонстрации того, как метаданные работают с NFT на Flow, мы будем использовать командную строку и скрипты Cadence.
Создайте новую директорию в корне нашего проекта pinata-party и назовите ее "transactions". После создания папки создайте в ней новый файл с именем MintPinataParty.cdc.
У нас должен быть файл, на который мы будем ссылаться в метаданных, предлагаемых NFT. Файл загружается в IPFS через Pinata. В этом учебном пособии NFT сфокусированы на продаваемых видеороликах о том, как Pinata разбивается на вечеринках. В этой демонстрации мы загрузим видео ребенка, разбивающего Pinata на вечеринке по случаю дня рождения. Вы можете загрузить любой медиафайл и связать его с NFT.
После загрузки файла вам будет присвоен хэш IPFS. Скопируйте хэш, так как он будет использоваться в процессе выпуска. Теперь добавьте следующий код в файл MintPinataParty.cdc.
import PinataPartyContract from 0xf8d6e0586b0a20c7
transaction {
let receiverRef: &{PinataPartyContract.NFTReceiver}
let minterRef: &PinataPartyContract.NFTMinter
prepare(acct: AuthAccount) {
self.receiverRef=acct.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)
.borrow()
?? panic("Could not borrow receiver reference")
self.minterRef = acct.borrow<&PinataPartyContract.NFTMinter>(from: /storage/NFTMinter)
?? panic("could not borrow minter reference")
}
execute {
let metadata : {String : String} = {
"name": "The Big Swing",
"swing_velocity": "29",
"swing_angle": "45",
"rating": "5",
"uri": "ipfs://QmRZdc3mAMXpv6Akz9Ekp1y4vDSjazTx2dCQRkxVy1yUj6"
}
let newNFT <- self.minterRef.mintNFT()
self.receiverRef.deposit(token: <-newNFT, metadata: metadata)
log("NFT Minted and deposited to Account 2's Collection")
}
}
Во-первых, мы определили две ссылочные переменные, minterRef и receiverRef. В этом сценарии мы одновременно являемся получателем и создателем NFT. Эти переменные ссылаются на ресурсы, созданные в контракте. Транзакция завершится неудачей, если лицо, выполняющее ее, не имеет доступа к ресурсу.
Приведенный выше контракт будет выпускать и депонировать НФТ. Теперь мы отправим транзакцию и зачислим NFT. Но перед этим нам нужно подготовить счет. Создайте закрытый ключ для подписи из командной строки в корневой папке проекта.
Выполните приведенную ниже команду:
flow keys generate
Он предоставит вам открытый и закрытый ключи. Обязательно защитите свой закрытый ключ.
Закрытый ключ понадобится вам для подписания транзакции, который нужно вставить в наш файл flow.json. Также необходимо указать алгоритм подписания, и вот как должен выглядеть ваш объект счетов в файле flow.json:
<p style="text-align: left;">"accounts": {
"emulator-account": {
"address": "YOUR ACCOUNT ADDRESS",
"privateKey": "YOUR PRIVATE KEY",
"chain": "flow-emulator",
"sigAlgorithm": "ECDSA_P256",
"hashAlgorithm": "SHA3_256"
}
},
Если вы хотите хранить что-либо из этого проекта в удаленном git-репозитории или на Github, вам не следует включать закрытый ключ. Вам может понадобиться .gitignore всего flow.json. Хотя мы используем только локальный эмулятор, хорошо бы держать ключи под защитой.
Последнее, что нам нужно сделать, это проверить, что токен находится в нашем аккаунте, и получить метаданные. Чтобы проверить это, нам нужно написать простой сценарий и вызвать его из командной строки.
Создайте новую папку scripts в корне вашего проекта. Создайте внутри папки файл CheckTokenMetadata.cdc. Добавьте в этот файл следующий код:
<p style="text-align: left;">import PinataPartyContract from 0xf8d6e0586b0a20c7</p>
<p style="text-align: left;">pub fun main() : {String : String} {</p>
<p style="text-align: left;">let nftOwner = getAccount(0xf8d6e0586b0a20c7) // log("NFT Owner")</p>
<p style="text-align: left;">let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)</p>
<p style="text-align: left;">let receiverRef = capability.borrow()</p>
<p style="text-align: left;">?? panic("Could not borrow the receiver reference")</p>
<p style="text-align: left;">return</p>
<p style="text-align: left;">receiverRef.getMetadata(id: 1)</p>
<p style="text-align: left;">}</p>
В этом сценарии мы импортируем контракт с развернутого адреса. Мы определяем главную функцию и определяем три переменные внутри нее:
This account owns the NFT.
Capabilities are access-controlled. If a capability is not available to the address attempting to borrow it, the script gets failed. In this example, we borrow capabilities from the NFTReceiver resource.
The variable takes our capability and states the script to borrow from the deployed contract.
Мы хотим убедиться, что адрес, о котором идет речь, получил NFT, который мы выпустили, а затем мы хотим просмотреть метаданные, связанные с токеном.
Запустите скрипт с помощью следующей команды и посмотрите, что мы получим:
<p class="in io ip iq ir lt lu dy"><span class="ce lv ko fe ls b cb lw lx s ly">flow scripts execute ./scripts/CheckTokenMetadata.cdc</span></p>
Вы получите результат, подобный этому:
{“name”: “The Big Swing”, “swing_velocity”: “29”, “swing_angle”: “45”, “rating”: “5”, “uri”: “ipfs://QmRZdc3mAMXpv6Akz9Ekp1y4vDSjazTx2dCQRkxVy1yUj6“}
Наконец, вы создали смарт-контракт Flow, создали токен, связали метаданные с токеном и сохранили базовые цифровые активы токена на IPFS.
Затем мы создадим внешнее приложение React, которое позволит вам отображать NFT, получая метаданные.
Отображение предметов коллекционирования NFT
Настройка React и зависимостей
Создайте приложение React в родительском каталоге pinata-party. Выполните следующую команду, чтобы создать приложение React:
<span class="ce lv ko fe ls b cb lw lx s ly">npx create-react-app pinata-party-frontend </span>
Когда вы закончите установку, вы увидите новый каталог с именем pinata-party-frontend. Перейдите в этот каталог и установите зависимости. Для первой части настройки внешнего интерфейса запустите:
<p class="in io ip iq ir mq mr dy"><span class="ce ms ld fe mp b cb mt mu s mv">npm i @onflow/fcl @onflow/types
</span>
Мы будем хранить некоторые значения как глобальные переменные для нашего приложения и использовать переменные среды. В качестве реакции это означает создание файла .env и установку пар ключ-значение, в которых вам нужно добавить префикс REACT_APP.
Затем создайте файл конфигурации, который будет использоваться для взаимодействия с Flow JS SDK. Создайте файл config.js в каталоге src и добавьте следующий код:
<p class="in io ip iq ir mq mr dy"><span class="ce ms ld fe mp b cb mt mu s mv">import {config} from "@onflow/fcl"
config()
.put("accessNode.api", process.env.REACT_APP_ACCESS_NODE)
.put("challenge.handshake", process.env.REACT_APP_WALLET_DISCOVERY)
.put("0xProfile", process.env.REACT_APP_CONTRACT_PROFILE)</span>
Этот файл конфигурации просто помогает JS SDK работать с блокчейном Flow (или в данном случае с эмулятором). Чтобы сделать этот файл доступным во всем приложении, откройте index.js файл и добавьте эту строку:
class="ji jj fe jk b gc jl jm jn gf jo jp jq jr js jt ju jv jw jx jy jz ka kb kc kd ew ce">import "./config"
Важно иметь функцию аутентификации в приложении, чтобы обеспечить безопасную передачу активов NFT. Нам нужен компонент аутентификации. Создайте файл AuthCluster.js в каталоге src. Добавьте в этот файл следующее:
import React, {useState, useEffect} from 'react'
import * as fcl from "@onflow/fcl"
const AuthCluster = () => {
const [user, setUser] = useState({loggedIn: null})
useEffect(() => fcl.currentUser().subscribe(setUser), [])
if (user.loggedIn) {
return (
<div>
<span>{user?.addr ?? "No Address"}</span>
<p style="text-align: left;"><button className="btn-primary" onClick={fcl.unauthenticate}>Log Out</button></p>
</div>
)
} else {
return (
<div>
<p style="text-align: left;"><button className="btn-primary"onClick={fcl.logIn}>Log In</button></p>
<p style="text-align: left;"><button className="btn-secondary" onClick={fcl.signUp}>Sign Up</button></p>
</div>
)
}
}
export default AuthCluster
Чтобы добавить этот компонент в приложение, замените файл app.js следующим:
<p class="hh hi hj hk hl ln lo by"><span class="ec lp ka df lm b jp lq lr s ls">
import './App.css';
import AuthCluster from './AuthCluster';</span><span class="ec lp ka df lm b jp lt lu lv lw lx lr s ls">function App() {
return (
<div className="App">
<AuthCluster />
</div>
);
}</span><span class="ec lp ka df lm b jp lt lu lv lw lx lr s ls">export default App;</span>
После добавления вышеуказанного кода вы увидите страницу с кнопкой регистрации при запуске приложения. Теперь пришло время создать возможность получать NFT для учетной записи и отображать их.
Получение NFT из FLOW
Давайте создадим компонент, который позволяет получать данные и отображать данные NFT. Создайте файл TokenData.js в каталоге src и добавьте в него следующий код:
import React, { useState } from "react";
import * as fcl from "@onflow/fcl";
const TokenData = () => {
const [nftInfo, setNftInfo] = useState(null)
const fetchTokenData = async () => {
const encoded = await fcl
.send([
fcl.script`
import PinataPartyContract from 0xf8d6e0586b0a20c7
pub fun main() : {String : String} {
let nftOwner = getAccount(0xf8d6e0586b0a20c7)
<p style="text-align: left;">let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)
let receiverRef = capability.borrow()
?? panic("Could not borrow the receiver reference")
return receiverRef.getMetadata(id: 1)
}`
])
const decoded = await fcl.decode(encoded)
setNftInfo(decoded)
};
return (
<div className="token-data">
<div className="center">
<button className="btn-primary" onClick={fetchTokenData}>Fetch Token Data</button>
</div>
{
nftInfo &&
<div>
{
Object.keys(nftInfo).map(k => {
return (
<p>{k}: {nftInfo[k]}</p>
)
})
}
<p style="text-align: left;"><button onClick={()=>setNftInfo(null)} className="btn-secondary">Clear Token Info</button></p>
</div>
}
</div>
);
};
export default TokenData;
В этом файле мы создаем компонент с кнопкой для получения данных токена. Мы также создали кнопку для очистки данных токена. При нажатии кнопки выборки вызывается функция fetchTokenData. Функция использует Flow JS SDK для запуска скрипта, который мы выполнили из командной строки. Получение результатов выполнения и установка результатов в переменную состояния nftInfo. Если переменная существует, пары ключ-значение отображаются из метаданных NFT на экране и кнопки для очистки данных.
Получение медиа из IPFS
Поскольку мы уже зарегистрировались в учетной записи Pinata и добавили видеофайл в IPFS через интерфейс загрузки Pinata, вы попадаете на шлюз Pinata IPFS, где содержимое IPFS отображается при нажатии на хэш в проводнике контактов.
В файле TokenData.js добавьте способ отображения видеофайла, полученного из IPFS. Обновите файл, чтобы он выглядел так:
import React, { useState } from "react";
import * as fcl from "@onflow/fcl";
const TokenData = () => {
const [nftInfo, setNftInfo] = useState(null)
const fetchTokenData = async () => {
const encoded = await fcl
.send([
fcl.script`
import PinataPartyContract from 0xf8d6e0586b0a20c7
pub fun main() : {String : String} {
let nftOwner = getAccount(0xf8d6e0586b0a20c7)
<p style="text-align: left;">let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)</p>
let receiverRef = capability.borrow()
?? panic("Could not borrow the receiver reference")
return receiverRef.getMetadata(id: 1)
}
])
const decoded = await fcl.decode(encoded)
setNftInfo(decoded)
};
return (
<div className="token-data">
<div className="center">
<button className="btn-primary" onClick={fetchTokenData}>Fetch Token Data</button>
</div>
{
nftInfo &&
<div>
{
Object.keys(nftInfo).map(k => {
return (
<p>{k}: {nftInfo[k]}</p>
)
})
}
<div className="center video">
<video id="nft-video" canplaythrough controls width="85%">
<p style="text-align: left;"><source src={`https://ipfs.io/ipfs/${nftInfo["uri"].split("://")[1]}`} type="video/mp4" /></p>
</video>
<div>
<p style="text-align: left;"><button onClick={() => setNftInfo(null)} className="btn-secondary">Clear Token Info</button></p>
</div>
</div>
</div>
}
</div>
);
};
export default TokenData;
Элемент видео с источником указывает на файл в IPFS. URI, созданный с помощью NFT, выглядит как ipfs: // Qm…
Мы сделали это так, потому что настольный клиент IPFS позволяет нажимать и открывать ссылки.
Теперь NFT станет настоящим живым цифровым активом на блокчейне.
Теперь мы включим передачу NFT.
Перенос NFT
Прежде всего, нам нужно создать контракты на создание торговой площадки. Контракты будут заключаться на:
Давайте создадим взаимозаменяемый токен-контракт, который будет использоваться для платежей при покупке NFT.
Мы создадим взаимозаменяемый контракт токена, определив пустой контракт:
class="hh hi hj hk hl ln lo by"><span class="ec lp kb df lm b jp lq lr s ls">pub contract PinnieToken {</span><span class="ec lp kb df lm b jp lt lu lv lw lx lr s ls">} </span>
Переменные публикации токена, связанные с ресурсами токена и провайдера, необходимо добавить в контракт.
pub var totalSupply: UFix64
pub var tokenName: String
pub 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"
}
}
}
Добавьте вышеуказанный контракт в пустой контракт.
Интерфейс ресурса под названием Provider определяет общедоступную функцию, но владелец учетной записи может только вызывать ее. Затем мы определим еще два интерфейса общедоступных ресурсов:
pub resource interface Receiver {
pub fun deposit(from: @Vault)
}
pub resource interface Balance {
pub var balance: UFix64
}
Вышеупомянутые интерфейсы располагаются непосредственно под интерфейсом ресурсов провайдера. Интерфейс Receiver включает функцию, которую может выполнить любой. Это гарантирует, что депозиты на счет могут выполняться до тех пор, пока получатель инициализирует хранилище для обработки токенов, созданных в рамках контракта. Ресурс 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
}
}
Добавьте следующую функцию в интерфейс хранилища:
pub fun createEmptyVault(): @Vault {
return <-create Vault(balance: 0.0)
}
Как следует из названия, функция создает пустой ресурс Vault для учетной записи. Баланс равен 0.
Следовательно, теперь нам нужно настроить возможность минтинга. Добавьте следующий код под функцией createEmptyVault:
pub resource VaultMinter {
style="text-align: left;">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)
style="text-align: left;">self.account.link<&VaultMinter>(/private/Minter, target: /storage/MainMinter)
}
При заключении контракта важно установить общий объем поставки. В этом примере мы инициализировали контракт с поставкой 30 и установили имя токена как «Пинни».
Развертывание и выпуск токенов
Обновите файл flow.json в проекте, чтобы развернуть новый контракт. Убедитесь, что файл flow.json ссылается на новый контракт и содержит ссылку на ключ учетной записи эмулятора:
{
"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"]
}
}
}
В другом окне терминала в каталоге проекта стороны пината запустите развертывание проекта потока. Теперь давайте протестируем функцию чеканки. Мы создадим транзакцию, которая позволит нам создавать токены Pinnie. Но сначала нам нужно обновить файл flow.json.
Измените json под учетной записью эмулятора:
style="text-align: left;">"emulator-account": {
"address": "f8d6e0586b0a20c7",
"privateKey": "e5ca2b0946358223f0555206144fe4d74e65cbd58b0933c5232ce195b9058cdd",
"sigAlgorithm": "ECDSA_P256",
"hashAlgorithm": "SHA3_256",
"chain": "flow-emulator"
},
Поле ключа становится полем privateKey, и мы добавляем свойства, включая sigAlgorithm, цепочку и hashAlgorithm.
Разработка торговой площадки NFT
Прежде чем приступить к работе над внешним интерфейсом торговой площадки, у нас должен быть контракт на создание и управление торговой площадкой.
В папке cadence / контракты создайте новый файл с именем MarketplaceContract.cdc.
import PinataPartyContract from 0xf8d6e0586b0a20c7
import PinnieToken from 0xf8d6e0586b0a20c7
pub 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 {
style="text-align: left;">pub fun purchase(tokenID: UInt64, recipient: &AnyResource{PinataPartyContract.NFTReceiver}, buyTokens: @PinnieToken.Vault)
pub fun idPrice(tokenID: UInt64): UFix64?
pub fun getIDs(): [UInt64]
}
}
Нам нужно импортировать как контракт NFT, так и контракт заменяемого токена. В определении контракта мы определили четыре события:
Мы добавили интерфейс ресурса под названием SalePublic под эмиттерами событий. Интерфейс должен быть общедоступным для всех, а не только для владельца контракта.
Нам нужно добавить ресурс SaleCollection под интерфейсом SalePublic. Мы определили несколько переменных в этом ресурсе. Например, отображение токенов для продажи, отображение цен на каждый токен для продажи и защищенная переменная, доступная только владельцу контракта, под названием ownerVault.
Нам нужно инициализировать переменные при их определении на ресурсе. Это выполняется в функции init и инициализируется ресурсом хранилища владельца и пустыми значениями.
Затем важно определить функции для управления действиями торговой площадки NFT. Функции:
Как упоминалось выше, три из этих функций общедоступны, это означает, что listForSale, remove, destroy и changePrice доступны только для перечисленных владельцев NFT. Например, changePrice не доступен публично, потому что мы не хотим, чтобы кто-либо менял цену NFT.
Последняя часть рыночного контракта - это функция CreateSaleCollection. Это позволяет добавить коллекцию в качестве ресурса к учетной записи. После написания этого контракта мы развернем его с учетной записью эмулятора. Выполните следующую команду из корня вашего проекта:
flow project deploy
Наконец, он развернет рыночный контракт и позволит нам использовать его во внешнем приложении.
Как только это будет сделано, нам нужно поработать над интерфейсом и подключить эти контракты с помощью инструмента Flow CLI.