本文介绍了如何在Eclipse区块链上使用Nifty Asset Standard创建NFT。内容涵盖了将ETH从以太坊桥接到Eclipse,使用QuickNode的IPFS网关上传图片和元数据,并使用Nifty Asset Standard铸造新的数字资产,最后验证NFT的链上数据。文章提供了详细的步骤和代码示例,帮助开发者在Eclipse上轻松创建和管理NFT。
Eclipse 是一个新的区块链,声称是 "以太坊上最快的 Layer 2",这是通过在以太坊上以 rollup 的形式运行 Solana 虚拟机(SVM)来实现的。Eclipse 最近向开发者推出了主网,社区对此次发布感到非常兴奋。其中一个令人兴奋的发布是在 Eclipse 上部署了 Nifty Asset Standard 程序,这是一个用于管理 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,并在 Eclipse 上使用 Nifty Asset Standard 铸造 NFT。具体来说,我们将涵盖以下功能:
让我们开始吧!
该项目将利用 Eclipse 上的 Nifty Asset Standard。 Nifty Asset 是 Solana 上 NFT 的一种新方法,可提高效率和灵活性。与构建在 SPL Token 程序之上的传统 NFT 标准不同,Nifty Asset 使用单个账户来表示资产,从而优化存储和计算使用。它通过扩展提供 traits、元数据、版税强制执行和资产锁定等功能。资产账户结构包括基本元数据和扩展数据,所有者在资产账户中定义,以实现更有效的管理。 有关 Nifty Asset 的更多详细信息(包括其特性和实现),请参阅我们的 Nifty Asset 指南。
未经审计的程序
Nifty Asset 标准仍在开发中,尚未经过审计。部署到主网时请务必小心,并确保在部署到主网之前已在 devnet 或测试网上彻底测试过你的应用程序。请查看他们的 GitHub 以获取最新更新和文档。
要开始使用,你需要在 Eclipse 上获得一些 ETH 代币。要桥接到 Eclipse,你需要在 Ethereum Mainnet 或 Sepolia Testnet 上获得一些 ETH(用于 Eclipse Testnet)。 如果你已经拥有 ETH,你可以跳到桥接; 如果你没有,你需要获得一些 Sepolia Test ETH。
在测试网上工作? 获取 Sepolia ETH 代币
如果你尚未准备好使用主网,则可以获取可以桥接到 Eclipse Testnet 的 Sepolia ETH 代币。
前往 QuickNode 的多链水龙头 并选择 Sepolia ETH 网络。 输入你的钱包地址,然后单击“发送我 ETH”:
确保你已从 Metamask“测试网络”下拉列表中选择了“Sepolia”(或使用你的 QuickNode 端点添加自己的网络 - 你可以在此处免费创建一个)。 几秒钟后,你应该会在你的钱包中看到 Sepolia ETH 代币:
做得好! 现在你的钱包中有了 Sepolia ETH 代币。 接下来,让我们将这些代币桥接到 Eclipse。
Eclipse 基金会创建了一个桥接合约和 脚本,以分别将 Mainnet 或 Sepolia ETH 代币转账到 Eclipse 的 Mainnet 和 Testnet。 让我们创建一个目标 Solana 钱包,并用一些 ETH 为它提供资金。
如果你还没有 Solana 纸钱包以用于 Solana CLI,则必须创建一个。 我们可以使用 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](https://mainnetbeta-rpc.eclipse.xyz/)
solana config set --url [https://testnet.dev2.eclipsenetwork.xyz/](https://testnet.dev2.eclipsenetwork.xyz/)
和
solana config set --keypair /path-to-wallet/my-wallet.json
通过运行以下命令获取你的地址:
solana address
保留此信息——我们稍后会用到它!
克隆 Eclipse 桥接 存储库。 在你想要克隆存储库的目录中打开一个终端窗口并运行:
git clone https://github.com/Eclipse-Laboratories-Inc/eclipse-deposit
并导航到 eclipse-deposit
目录:
cd eclipse-deposit
安装依赖项:
yarn install
在你的以太坊钱包中,复制你的私钥。 在 MetaMask 中,你可以通过以下方式找到它:
"帐户详细信息" -> "查看帐户" -> "显示私钥" -> "复制私钥"
这应该是一个 64 个字符的十六进制字符串。 将其保存到名为 private-key.txt
的文件中。 保留此信息——我们需要它来运行我们的脚本。
你应该能够按照 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
如果你使用的 Node.js 版本低于 18,你可能需要将 @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 客户端库,提供了一个高级 API,用于与 Solana 区块链交互。 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: 'Pixel infrastructure for everyone!',
imgType: 'image/png',
attributes: [ { trait_type: 'Speed', value: 'Quick' }, ]
};
const IPFS_API = 'REPLACE_WITH_YOUR_KEY'; // 👈 将此替换为你的 IPFS API 端点
随意更新 NFT_DETAILS
字段以适合你所需的 NFT。 我们将使用每个字段来定义下面的 NFT 元数据。
在我们继续之前,请确保将 REPLACE_WITH_YOUR_KEY
占位符替换为你的 IPFS API 密钥。 你可以从 QuickNode 仪表板 获取 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. ✅ - Uploaded image to IPFS`);
return cid;
} catch (error) {
console.error('1. ❌ - Error uploading image:', 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. ✅ - Uploaded metadata to IPFS`);
return cid;
} catch (error) {
console.error('2. ❌ - Error uploading metadata:', 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('Missing x-api-key header');
}
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('Fetch request failed:', error);
throw error;
}
},
});
const getUrl = (cid: string, gatewayUrl = 'https://qn-shared.quicknode-ipfs.com/ipfs/'): string => {
if (!cid) throw new Error('Invalid CID: CID cannot be empty.');
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} - Failed to send request: ${response.statusText}`);
return getUrl(response.data.pin.cid, /* OPTIONAL_GATEWAY_URL */); // 👈 在此处添加你的网关 URL
} catch (error) {
console.error('Failed to send request:', error);
throw error;
}
};
代码可能很多,但这实际上只是对 QuickNode IPFS API 的一个简单的 HTTP 请求。 我们使用 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. ✅ - Minted a new Asset: ${nftAddress}`);
} catch (error) {
console.error('3. ❌ - Error minting a new 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. ✅ - Verified Asset Data`);
} catch (error) {
console.error('4. ❌ - Error verifying Asset Data:', 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. ✅ - Uploaded image to IPFS
2. ✅ - Uploaded metadata to IPFS
3. ✅ - Minted a new Asset: F66dGYgKhRKzqkGAJEnJSHEL5qjLHG9vZSw6jMYcaDTM
4. ✅ - Verified Asset Data
出发吧!!! 很棒,不是吗? 让我们在 Eclipse Explorer 上查找我们的 NFT:
只需搜索来自你的控制台输出的帐户地址,你应该会看到你的 NFT:
以及 NFT 元数据:
做得好! 你现在在 Eclipse 上有一个 NFT 了!
在本指南中,我们介绍了在 Eclipse 上铸造 NFT 的基础知识。 你现在拥有构建客户端应用程序的工具,该应用程序可以在 Eclipse 上使用数字资产执行各种操作。 那你还在等什么呢?
如果你有要分享的问题或想法,请在 Discord 或 Twitter 上给我们留言!
如果你对新主题有任何反馈或要求,请 告诉我们。 我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/ecl...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!