逐步创建 NFT 集合的教程
👋 引言
非同质化代币(NFT)已成为数字艺术和收藏品世界中最热门的话题之一。NFT是使用区块链技术验证所有权和真实性的独特数字资产。它们为创作者和收藏家提供了将数字艺术、音乐、视频和其他形式的数字内容货币化和交易的新可能性。近年来,NFT市场爆炸性增长,一些高调的销售额达到了数百万美元。在本文中,我们将逐步在TON上构建我们的NFT集合。
这是你在本教程结束时将创建的鸭子集合的精美图片:
🦄 你将会学到什么
- 你将在TON上铸造NFT集合
- 你将理解TON上的NFT是如何工作的
- 你将把NFT出售
- 你将把元数据上传到pinata.cloud
💡 必要条件
你必须已经有一个测试网钱包,里面至少有2 TON。可以从@testgiver_ton_bot获取测试网币。
要在tonkeeper中打开测试网网络,请转到设置并点击位于底部的tonkeeper logo 5次,之后选择测试网而不是主网。
我们将使用Pinata作为我们的IPFS存储系统,因此你还需要在pinata.cloud上创建一个帐户并获取api_key & api_secreat。官方Pinata 文档教程可以帮助完成这一点。只要你拿到这些api令牌,我就在这里等你!!!
💎 什么是 TON 上的 NFT?
在开始我们教程的主要部分之前,我们需要了解一下通常意义上TON中NFT是如何工作的。出乎意料的是,我们将从解释ETH中NFT的工作原理开始,为了理解TON中NFT实现的特殊性,与行业中常见的区块链相比。
ETH 上的 NFT 实现
ETH中NFT的实现极其简单 - 存在1个主要的集合合约,它存储一个简单的哈希映射,该哈希映射反过来存储此集合中NFT的数据。所有与此集合相关的请求(如果任何用户想要转移NFT、将其出售等)都特别发送到 此1个集合合约。
在 TON 中如此实现可能出现的问题
在TON的上下文中,此类实现的问题由TON的NFT标准完美描述:
-
不可预测的燃料消耗。在TON中,字典操作的燃料消耗取决于确切的键集。此外,TON是一个异步区块链。这意味着,如果你向一个智能合约发送一个消息,那么你不知道有多少来自其他用户的消息会在你的消息之前到达智能合约。因此,你不知道当你的消息到达智能合约时字典的大小会是多少。这对于简单的钱包 -> NFT智能合约交互是可以的,但对于智能合约链,例如钱包 -> NFT智能合约 -> 拍卖 -> NFT智能合约,则不可接受。如果我们不能预测燃料消耗,那么可能会出现这样的情况:NFT智能合约上的所有者已经更改,但拍卖操作没有足够的Toncoin。不使用字典的智能合约可以提供确定性的燃料消耗。
-
不可扩展(成为瓶颈)。TON的扩展性基于分片的概念,即在负载下自动将网络划分为分片链。流行NFT的单个大智能合约与这一概念相矛盾。在这种情况下,许多交易将引用一个单一的智能合约。TON架构为分片的智能合约提供了设施(参见白皮书),但目前尚未实现。
简而言之,ETH的解决方案不可扩展且不 适用于像TON这样的异步区块链。
TON 上的 NFT 实现
在TON中,我们有1个主合约-我们集合的智能合约,它存储它的元数据和它所有者的地址,以及最重要的 - 如果我们想要创建("铸造")新的NFT项目 - 我们只需要向这个集合合约发送消息。而这个集合合约将为我们部署新NFT项目的合约,并提供我们提供的数据。
如果你想更深入地了解这个话题,可以查看TON上的NFT处理文章或阅读NFT标准
⚙ 设置开发环境
让我们从创建一个空项目开始:
- 创建新文件夹
mkdir MintyTON
- 打开这个文件夹
cd MintyTON
- 初始化我们的 项目
yarn init -y
- 安装typescript
yarn add typescript @types/node -D
- 将以下配置复制到tsconfig.json中
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"lib": ["ES2022"],
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": "src",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strict": true,
"esModuleInterop": true,
"strictPropertyInitialization": false
},
"include": ["src/**/*"]
}
- 向package.json添加脚本以构建并启动我们的应用程序
"scripts": {
"start": "tsc --skipLibCheck && node dist/app.js"
},
- 安装所需的库
yarn add @pinata/sdk dotenv ton ton-core ton-crypto
- 创建
.env
文件并根据此模板添加你自己的数据
PINATA_API_KEY=your_api_key
PINATA_API_SECRET=your_secret_api_key
MNEMONIC=word1 word2 word3 word4
TONCENTER_API_KEY=aslfjaskdfjasasfas
你可以从@tonapibot获取toncenter api key并选择mainnet或testnet。在 MNEMONIC
变量中存储集合所有者钱包种子短语的24个单词。
太好了!现在我们准备好开始为我们的项目编写代码了。
编写辅助函数
首先,让我们在src/utils.ts
中创建一个函数,该函数将通过助记词 打开我们的钱包并返回它的publicKey/secretKey。
我们根据24个单词(种子短语)获取一对密钥:
import { KeyPair, mnemonicToPrivateKey } from "ton-crypto";
import {
beginCell,
Cell,
OpenedContract,
TonClient,
WalletContractV4,
} from "ton";
export type OpenedWallet = {
contract: OpenedContract<WalletContractV4>;
keyPair: KeyPair;
};
export async function openWallet(mnemonic: string[], testnet: boolean) {
const keyPair = await mnemonicToPrivateKey(mnemonic);
}
创建一个类实例以与toncenter交互:
const toncenterBaseEndpoint: string = testnet
? "https://testnet.toncenter.com"
: "https://toncenter.com";
const client = new TonClient({
endpoint: `${toncenterBaseEndpoint}/api/v2/jsonRPC`,
apiKey: process.env.TONCENTER_API_KEY,
});
最后打开我们的钱包:
const wallet = WalletContractV4.create({
workchain: 0,
publicKey: keyPair.publicKey,
});
const contract = client.open(wallet);
return { contract, keyPair };
很好,之后我们将创建我们项目的主要入口点app.ts
。
在这里,我们将使用刚刚创建的openWallet
函数并调用我们的主函数init
。
目前足够了。
import * as dotenv from "dotenv";
import { openWallet } from "./utils";
import { readdir } from "fs/promises";
dotenv.config();
async function init() {
const wallet = await openWallet(process.env.MNEMONIC!.split(" "), true);
}
void init();
最后,让我们创建delay.ts
文件,在这个文件中,我们将创建一个函数来等待seqno
增加。
import { OpenedWallet } from "utils";
export async function waitSeqno(seqno: number, wallet: OpenedWallet) {
for (let attempt = 0; attempt < 10; attempt++) {
await sleep(2000);
const seqnoAfter = await wallet.contract.getSeqno();
if (seqnoAfter == seqno + 1) break;
}
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
简单来说,seqno就是由钱包发送的外部交易的计数器。 Seqno用于预防重放攻击。当交易发送到钱包智能合约时,它将交易的seqno字段与其存储中的字段进行比较。如果它们匹配,交易被接受并且存储的seqno增加一。如果它们不匹配,交易被丢弃。这就是为什么我们需要在每次发送外部交易后稍等一会儿。