本文介绍了如何在Eclipse区块链上使用Nifty资产标准创建NFT,包括准备工作、桥接ETH到Eclipse、NFT铸造和数据验证等步骤。文章详细阐述了所需依赖、代码实例和每一步的执行过程,适合想要在Eclipse上进行NFT开发的开发者。
Eclipse 是一个新区块链,声称是“以太坊最快的第二层”,这是通过在以太坊上运行 Solana 虚拟机(SVM)作为 Rollup 实现的。Eclipse 最近向开发者推出了主网,其发布为社区带来了很多兴奋。一个令人激动的发布是Nifty Asset Standard 程序在 Eclipse 上的部署,这是一个轻量级工具和高效的标准,用于管理 Solana 上的数字资产。
在本指南中,我们将学习如何使用 Eclipse 上的 Nifty Asset Standard 创建 NFT。
在开始之前,请确保你已安装以下内容:
虽然不是必需的,我们建议你了解以下内容:
依赖项 | 版本 |
---|---|
@metaplex-foundation/umi | ^0.9.2 |
@metaplex-foundation/umi-bundle-defaults | ^0.9.2 |
@nifty-oss/asset | ^0.6.1 |
solana cli | 1.18.8 |
本指南将引导你如何将以太坊桥接到 Eclipse 并使用 Nifty Asset Standard 在 Eclipse 上铸造 NFT。具体来说,我们将涵盖以下功能:
让我们开始吧!
本项目将利用 Eclipse 上的Nifty Asset Standard。Nifty Asset 是一种新的 NFT 方法,在 Solana 上提高了效率和灵活性。与基于 SPL Token 程序的传统 NFT 标准不同,Nifty Asset 使用单个账户来表示资产,从而优化存储和计算使用。它提供了诸如特征、元数据、版税执行和通过扩展锁定资产等功能。资产账户结构包括基本元数据和扩展数据,所有者在资产账户内定义,以便更有效的管理。有关 Nifty Asset 的更详细信息,包括其功能和实现,请参考我们的Nifty Asset 指南。
未经审计的程序
Nifty Asset 标准仍在开发中,尚未经过审计。部署到主网时请谨慎,并确保你在 devnet 或 testnet 上充分测试你的应用后再部署到主网。请查看他们的GitHub 以获取最新的更新和文档。
要开始,你需要在 Eclipse 上拥有一些 ETH 代币。要桥接到 Eclipse,你需要在以太坊主网或 Sepolia 测试网(在 Eclipse 测试网上使用)上拥有一些 ETH。如果你已经拥有 ETH,可以跳到桥接;如果没有,你需要获取一些 Sepolia 测试 ETH。
在测试网工作?获取 Sepolia ETH 代币
如果你尚未准备好主网,可以获取可以桥接到 Eclipse 测试网的 Sepolia ETH 代币。
前往 QuickNode 的多链水龙头并选择 Sepolia ETH 网络。输入你的钱包地址并单击“发送我 ETH”:
确保在 Metamask 的“测试网络”下拉列表中选择“Sepolia”(或者使用你的 QuickNode 端点添加自己的网络 - 你可以在这里免费创建一个)。你应该在几秒钟后在钱包中看到 Sepolia ETH 代币:
干得不错!你现在在钱包中拥有 Sepolia ETH 代币。接下来,让我们把这些代币桥接到 Eclipse。
Eclipse 基金会创建了一个桥接合约和 脚本 将主网或 Sepolia ETH 代币转移到 Eclipse 的主网和测试网。让我们创建一个目标 Solana 钱包并用一些 ETH 进行资助。
如果你还没有用于 Solana CLI 的 Solana 纸钱包,你需要创建一个。我们可以使用 Solana CLI 与 Eclipse 网络进行交互,因为它是 SVM 的一个实例! 注意:目前有一些小瑕疵(主要是围绕 UI);例如,使用 solana balance
将返回正确的余额,但它将显示 X SOL
而不是 X ETH
(尽管正在表示的底层代币实际上是 ETH)。
你可以通过在终端中运行以下命令创建一个新钱包:
solana-keygen new --outfile /path-to-wallet/my-wallet.json
然后,更新你的 Solana CLI 配置以使用新钱包和适当的 Eclipse 集群。根据你希望的网络,将以下命令输入你的终端:
solana config set --url https://mainnetbeta-rpc.eclipse.xyz
solana config set --url https://testnet.dev2.eclipsenetwork.xyz/
并且
solana config set --keypair /path-to-wallet/my-wallet.json
通过运行以下命令获取你的地址:
solana address
保留好这个--我们稍后需要它!
克隆 Eclipse Bridge 仓库。在你希望克隆该仓库的目录中打开一个终端窗口并运行:
git clone https://github.com/Eclipse-Laboratories-Inc/eclipse-deposit
并导航到 eclipse-deposit
目录:
cd eclipse-deposit
安装依赖项:
yarn install
在你的以太坊钱包中复制你的私钥。在 MetaMask 中,你可以通过以下步骤找到这一点:
“账户详情” -> “查看账户” -> “显示私钥” -> “复制私钥”
这应该是一个 64 字符的十六进制字符串。将其保存到名为 private-key.txt
的文件中。留着这个--我们稍后需要用它来运行我们的脚本。
你应该能够按照克隆的 repos 的 README 中的说明或者 Eclipse 的文档这里进行操作。你需要在终端中运行以下命令(不带括号):
node bin/cli.js -k [path_to_private_key] -d [solana_destination_address] -a [amount_in_ether] --mainnet
node bin/cli.js -k [path_to_private_key] -d [solana_destination_address] -a [amount_in_ether] --sepolia
这里是参数说明:
[path_to_private_key]
是你刚刚从 MetaMask 复制的 64 字符字符串的路径,例如 private-key.txt
。[solana_destination_address]
是你使用 Solana CLI 生成的,在 my-wallet.json
中保存的地址。[amount_in_ether]
是你想要转移到 Eclipse 的 ETH 数量,例如 0.01
。--mainnet
标志用于转移到 Eclipse 主网,--sepolia
标志用于转移到 Eclipse 测试网。你应该看到类似以下内容:
Transaction successful: 0xb763990f73f1801197d...
你可以在 Etherscan 这里(或 Sepolia Etherscan)查看交易。在过了几秒钟后,你应该能够在你创建的 Solana 钱包中看到 ETH 余额。由于你已经将 Solana CLI 配置为 Eclipse 测试网,你只需运行以下命令即可检查余额:
solana balance
你将看到类似 0.001 SOL
的内容,具体取决于你存入了多少。请注意,这里 SOL 代表的是 ETH。你可以通过在 Eclipse 区块浏览器这里 检查你的钱包来进行验证。 确保你的浏览器设置为正确的集群(请查看浏览器窗口右上角)。如果你在错误的集群上,你将看不到你的余额。 粘贴你的钱包地址,你应该会看到你的账户余额。
非常不错!你已成功将 ETH 代币桥接到 Eclipse 主网。现在,让我们在 Eclipse 上铸造一个 NFT!
首先,为你的项目创建一个新目录并初始化一个 Node.js 项目。
mkdir eclipse-nft
然后更改到新目录:
cd eclipse-nft
然后,初始化一个新的 Node.js 项目:
npm init -y
接下来,安装必要的依赖项:
npm install @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @nifty-oss/asset
如果你使用的是低于 18 的 Node.js 版本,你可能需要将 @types/node
包作为开发依赖项安装:
npm install @types/node --save-dev
在你的项目目录中创建一个名为 index.ts
的新文件。
echo > index.ts
首先,导入所需的模块并设置 UMI 实例和签名者。在 index.ts
文件中添加以下代码:
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import {
TransactionBuilderSendAndConfirmOptions,
createGenericFile,
createGenericFileFromJson,
createSignerFromKeypair,
generateSigner,
keypairIdentity,
} from '@metaplex-foundation/umi';
import {
metadata,
mint,
niftyAsset,
fetchAsset,
Metadata,
royalties,
creators,
Royalties,
Creators,
} from '@nifty-oss/asset';
import { readFile } from "fs/promises";
import { uploadToIpfs } from './upload';
import fs from 'fs';
createUmi
函数使用默认选项初始化 Umi 客户端。Umi 是一个 Solana 客户端库,提供与 Solana 区块链交互的高级 API。Nifty Asset 库包括用于铸造和管理数字资产的函数,我们将稍后详细讨论这些函数。
一旦客户端就绪,我们还需要设置一些常量。我们需要以下内容:
在 index.ts
文件中添加以下代码:
const CLUSTERS = {
'mainnet': 'https://mainnetbeta-rpc.eclipse.xyz',
'testnet': 'https://testnet.dev2.eclipsenetwork.xyz',
'devnet': 'https://staging-rpc.dev2.eclipsenetwork.xyz',
'localnet': 'http://127.0.0.1:8899',
};
const OPTIONS: TransactionBuilderSendAndConfirmOptions = {
confirm: { commitment: 'processed' }
};
const NFT_DETAILS = {
name: "QuickNode Pixel",
symbol: "QP",
royalties: 500, // 基点(5%)
description: '为每个人提供的像素基础设施!',
imgType: 'image/png',
attributes: [\
{ trait_type: 'Speed', value: 'Quick' },\
]
};
const IPFS_API = 'REPLACE_WITH_YOUR_KEY'; // 👈 用你的 IPFS API 端点替换此内容
可以根据你希望的 NFT 更新 NFT_DETAILS
字段。我们将在下面使用每个字段来定义 NFT 的元数据。
在继续之前,请确保将 REPLACE_WITH_YOUR_KEY
占位符替换为你的 IPFS API 密钥。你可以从 QuickNode Dashboard 获取 API 密钥。如果你尚未拥有 QuickNode 帐户,你可以在这里免费创建一个。要了解有关 QuickNode 上 IPFS 的更多信息,请查看我们的IPFS 指南。
接下来,初始化 Umi 客户端并为 creator
、owner
和 asset
账户创建签名者。我们还将设置用于发送和确认交易的默认选项。
在 index.ts
文件中添加以下代码:
const umi = createUmi(CLUSTERS.mainnet, OPTIONS.confirm).use(niftyAsset()); // 👈 用你的集群替换此内容
const wallet = './my-wallet.json'; // 👈 用你的钱包路径替换此内容
const secretKey = JSON.parse(fs.readFileSync(wallet, 'utf-8'));
const keypair = umi.eddsa.createKeypairFromSecretKey(new Uint8Array(secretKey));
umi.use(keypairIdentity(keypair));
const creator = createSignerFromKeypair(umi, keypair);
const owner = creator; // 铸造给创建者
const asset = generateSigner(umi);
确保在 createUmi
函数中根据你的集群相应更新 CLUSTERS
选择(例如,CLUSTERS.mainnet
或 CLUSTERS.testnet
)。
确保用你钱包文件的路径替换 wallet
占位符。如果你不确定钱包文件在何处,可以通过在终端中运行以下命令找到 Solana CLI 配置:
solana config get
你应该会看到类似如下内容:
Keypair Path: ./my-wallet.json
现在我们已经配置了钱包和环境,我们可以编写一些助手函数,将图像和元数据上传到 IPFS。
在 index.ts
文件中添加以下代码:
async function uploadImage(path: string, contentType = 'image/png'): Promise<string> {
try {
const image = await readFile(path);
const fileName = path.split('/').pop() ?? 'unknown.png';
const genericImage = createGenericFile(image, fileName, { contentType });
const cid = await uploadToIpfs(genericImage, IPFS_API);
console.log(`1. ✅ - 上传图像到 IPFS`);
return cid;
} catch (error) {
console.error('1. ❌ - 上传图像时出错:', error);
throw error;
}
}
async function uploadMetadata(imageUri: string): Promise<string> {
try {
const metadata = {
name: NFT_DETAILS.name,
description: NFT_DETAILS.description,
image: imageUri,
attributes: NFT_DETAILS.attributes,
properties: {
files: [\
{\
type: NFT_DETAILS.imgType,\
uri: imageUri,\
},\
]
}
};
const file = createGenericFileFromJson(metadata, 'metadata.json');
const cid = await uploadToIpfs(file, IPFS_API);
console.log(`2. ✅ - 上传元数据到 IPFS`);
return cid;
} catch (error) {
console.error('2. ❌ - 上传元数据时出错:', error);
throw error;
}
}
让我们来分解这段代码。
uploadImage
接受图像的路径和可选的内容类型。它从文件系统读取图像,创建一个通用文件对象(这是 Umi 上传器配置所必要的),并将其上传到 IPFS。它返回上传图像的 CID。uploadMetadata
接受上传图像的 URI 并将元数据上传到 IPFS。它返回上传元数据的 CID。
这两个函数都使用 uploadToIpfs
函数将文件上传到 IPFS。所用的这个函数是从一个名为 upload.ts
的文件中导入的。现在让我们创建这个文件。在终端中运行以下命令创建文件:
echo > upload.ts
然后,将以下代码复制到文件中:
import {
GenericFile,
request,
HttpInterface,
HttpRequest,
HttpResponse,
} from '@metaplex-foundation/umi';
interface QuickNodeUploadResponse {
requestid: string;
status: string;
created: string;
pin: {
cid: string;
name: string;
origins: string[];
meta: Record<string, unknown>;
};
info: {
size: string;
};
delegates: string[];
}
const createQuickNodeFetch = (): HttpInterface => ({
send: async <ResponseData, RequestData = unknown>(
request: HttpRequest<RequestData>
): Promise<HttpResponse<ResponseData>> => {
let headers = new Headers(
Object.entries(request.headers).map(([name, value]) => [name, value] as [string, string])
);
if (!headers.has('x-api-key')) {
throw new Error('缺少 x-api-key 头');
}
const isJsonRequest = headers.get('content-type')?.includes('application/json') ?? false;
const body = isJsonRequest && request.data ? JSON.stringify(request.data) : request.data as string | undefined;
try {
const response = await fetch(request.url, {
method: request.method,
headers,
body,
redirect: 'follow',
signal: request.signal as AbortSignal,
});
const bodyText = await response.text();
const isJsonResponse = response.headers.get('content-type')?.includes('application/json');
const data = isJsonResponse ? JSON.parse(bodyText) : bodyText;
return {
data,
body: bodyText,
ok: response.ok,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
};
} catch (error) {
console.error('获取请求失败:', error);
throw error;
}
},
});
const getUrl = (cid: string, gatewayUrl = 'https://qn-shared.quicknode-ipfs.com/ipfs/'): string => {
if (!cid) throw new Error('无效 CID:CID 不能为空。');
const baseUrl = gatewayUrl.endsWith('/') ? gatewayUrl : `${gatewayUrl}/`;
return `${baseUrl}${encodeURIComponent(cid)}`;
};
export const uploadToIpfs = async <T>(
file: GenericFile,
apiKey: string
): Promise<string> => {
const http = createQuickNodeFetch();
const endpoint = 'https://api.quicknode.com/ipfs/rest/v1/s3/put-object';
const formData = new FormData();
const fileBlob = new Blob([file.buffer], { type: 'application/json' });
formData.append('Body', fileBlob);
formData.append("Key", file.fileName);
formData.append("ContentType", file.contentType || '');
const qnRequest = request()
.withEndpoint('POST', endpoint)
.withHeader("x-api-key", apiKey)
.withData(formData);
try {
const response = await http.send<QuickNodeUploadResponse, FormData>(qnRequest);
if (!response.ok) throw new Error(`${response.status} - 请求发送失败: ${response.statusText}`);
return getUrl(response.data.pin.cid, /* OPTIONAL_GATEWAY_URL */); // 👈 在此处添加你的网关 URL
} catch (error) {
console.error('请求发送失败:', error);
throw error;
}
};
这看起来代码量很多,但其实这只是一个简单的 HTTP 请求到 QuickNode IPFS API。我们使用 Umi SDK 的 request
函数创建请求对象,再使用 HttpInterface
将请求发送到 IPFS API。如果请求成功,我们返回上传文件的 URL。如果出现错误,我们记录错误并抛出它。如果你想了解更多关于 QuickNode 的 IPFS API,请查看我们的文档,或者如果你想了解更多关于 Umi SDK 处理 HTTP 请求的信息,请查阅Metaplex 文档。
_如果需要,你可以将自己的网关 URL 传入 getUrl
函数。如果这样做,请确保在 uploadToIpfs
函数中更新 OPTIONAL_GATEWAY_URL
。_
干得不错!让我们返回 index.ts
文件并完成剩下的代码。请注意导入 uploadToIpfs
函数的任何错误现在应该都已解决。
现在我们已经有了用于上传 NFT 的图像和元数据的函数,我们可以编写铸造 NFT 的函数。在 index.ts
中添加以下功能以铸造新的数字资产:
async function mintAsset(metadataUri: string): Promise<void> {
try {
await mint(umi, {
asset,
owner: owner.publicKey,
authority: creator.publicKey,
payer: umi.identity,
mutable: false,
standard: 0,
name: NFT_DETAILS.name,
extensions: [\
metadata({\
uri: metadataUri,\
symbol: NFT_DETAILS.symbol,\
description: NFT_DETAILS.description,\
}),\
royalties(NFT_DETAILS.royalties),\
creators([{ address: creator.publicKey, share: 100 }]),\
]
}).sendAndConfirm(umi, OPTIONS);
const nftAddress = asset.publicKey.toString();
console.log(`3. ✅ - 铸造了新资产: ${nftAddress}`);
} catch (error) {
console.error('3. ❌ - 铸造新 NFT 时出错。', error);
}
}
我们的函数将接受上传的元数据的 URI,并铸造新的 NFT,然后使用 Nifty mint
函数铸造 NFT。
metadata
扩展定义上传元数据的 URIroyalties
扩展定义 NFT 的版税creators
扩展定义 NFT 的创造者可以自由探索其他 Nifty 扩展,并根据你的具体需求修改代码。
一旦我们铸造 NFT,让我们验证链上的数据是否与预期匹配。我们来创建一个新的函数 verifyOnChainData
来实现此目的。此步骤不是必需的,但我们包括它是为了让你了解如何通过 Nifty SDK 访问数据。在 index.ts
中添加以下函数以验证链上数据:
async function verifyOnChainData(metadataUri: string): Promise<void> {
try {
const assetData = await fetchAsset(umi, asset.publicKey, OPTIONS.confirm);
const onChainCreators = assetData.extensions.find(ext => ext.type === 3) as Creators;
const onChainMetadata = assetData.extensions.find(ext => ext.type === 5) as Metadata;
const onChainRoyalties = assetData.extensions.find(ext => ext.type === 7) as Royalties;
const checks = [\
// 资产检查\
{ condition: assetData.owner.toString() === owner.publicKey.toString(), message: '所有者匹配' },\
{ condition: assetData.publicKey.toString() === asset.publicKey.toString(), message: '公钥匹配' },\
{ condition: assetData.name === NFT_DETAILS.name, message: '资产名称匹配' },\
\
// 创建者扩展检查\
{ condition: !!onChainCreators, message: '未找到创建者扩展' },\
{ condition: onChainCreators.values.length === 1, message: '创建者数量匹配' },\
{ condition: onChainCreators.values[0].address.toString() === creator.publicKey.toString(), message: '创建者地址匹配' },\
{ condition: onChainCreators.values[0].share === 100, message: '创建者分享匹配' },\
{ condition: onChainCreators.values[0].verified === true, message: '创建者未验证' },\
\
// 元数据扩展检查\
{ condition: !!onChainMetadata, message: '未找到元数据扩展' },\
{ condition: onChainMetadata.symbol === NFT_DETAILS.symbol, message: '符号匹配' },\
{ condition: onChainMetadata.description === NFT_DETAILS.description, message: '描述匹配' },\
{ condition: onChainMetadata.uri === metadataUri, message: '元数据 URI 匹配' },\
\
// 版税扩展检查\
{ condition: !!onChainRoyalties, message: '未找到版税扩展' },\
{ condition: onChainRoyalties.basisPoints.toString() === NFT_DETAILS.royalties.toString(), message: '版税基点匹配' },\
];
checks.forEach(({ condition, message }) => {
if (!condition) throw new Error(`验证失败: ${message}`);
});
console.log(`4. ✅ - 验证资产数据`);
} catch (error) {
console.error('4. ❌ - 验证资产数据时出错:', error);
}
}
这里的内容比较多,我们来逐步分析。
fetchAsset
函数获取资产数据assetData
中找到预期的扩展数据。我们通过 Nifty SDK 中 ExtensionType
的枚举值位置找到正确的扩展数据(来源:这里)。condition
(布尔值)和一个 message
(字符串),如果条件不满足则记录。我们的 checks
数组包含多个对比 NFT_DETAILS
和 assetData
对象的要求。checks
数组,并在任何条件未满足时记录错误。创建一个 main()
函数,将所有单个函数串联在一起,以顺序执行整个过程。在 index.ts
文件的末尾添加以下内容:
async function main() {
const imageCid = await uploadImage('./pixel.png'); // 👈 用你的图像路径替换此内容
const metadataCid = await uploadMetadata(imageCid);
await mintAsset(metadataCid);
await verifyOnChainData(metadataCid);
}
main();
确保向你的项目目录根目录添加一个 ./pixel.png
文件,或在 uploadImage
函数中更新图像的路径。
要运行代码,请在终端中执行以下命令:
ts-node index.ts
如果一切设置正确,你应该看到指示每个步骤成功执行的控制台日志,或者在遇到问题时显示详细的错误信息。
ts-node index.ts
1. ✅ - 上传图像到 IPFS
2. ✅ - 上传元数据到 IPFS
3. ✅ - 铸造了新资产: F66dGYgKhRKzqkGAJEnJSHEL5qjLHG9vZSw6jMYcaDTM
4. ✅ - 验证资产数据
太棒了!相当不错吧?让我们在 Eclipse Explorer 上查看我们的 NFT:
只需搜索控制台输出中的账户地址,你就可以看到你的 NFT:
以及 NFT 元数据:
干得不错!你现在在 Eclipse 上拥有一个 NFT!
在本指南中,我们介绍了在 Eclipse 上铸造 NFTs 的基础知识。你现在具备了构建可以在 Eclipse 上处理各种数字资产的客户端应用程序的工具。那你还在等什么呢?
如果你有任何问题或想分享的想法,请在 Discord 或 Twitter 上与我们联系!
告诉我们 如果你有任何反馈或请求新主题。我们很乐意听到你的声音。
- 原文链接: quicknode.com/guides/oth...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!