{"content":{"title":"如何使用 Crossmint 的铸造 API 空投 Solana NFT","body":"### 概述\r\n\r\n传递 NFT从未如此简单。无论你是一家希望开始使用NFT的Web2公司，还是有一个希望通过NFT奖励的Web3社区，Crossmint的Mint API允许你只需一个POST请求就能向用户铸造NFT。Crossmint的NFT API可以直接通过QuickNode的市场获得，因此开始起来更加轻松。在本指南中，你将创建一个简单的脚本，允许你将 NFT 直接铸造并交付给一列客户（通过钱包地址或电子邮件地址👀）。\r\n\r\n#### 你将要做什么\r\n\r\n在本指南中，我们将：\r\n\r\n- 创建带有Crossmint API附加功能的QuickNode端点\r\n- 创建NFT元数据\r\n- 使用Crossmint API（使用TypeScript）将NFT空投到Solana钱包和电子邮件地址的组合列表中\r\n- 验证空投是否成功\r\n- 将空投细节记录到`.json`文件中\r\n\r\n#### 你将需要什么\r\n\r\n要跟随本指南，你将需要以下内容：\r\n\r\n- JavaScript/TypeScript编程语言的基础知识\r\n- 安装[Nodejs](https://nodejs.org/en/)（版本16.15或更高）\r\n- 安装npm或yarn（我们将使用yarn来初始化我们的项目并安装必要的包。如果你更喜欢使用npm，也可以）\r\n- TypeScript经验和已安装[ts-node](https://www.npmjs.com/package/ts-node)\r\n\r\n### 设置你的项目\r\n\r\n在终端中创建一个新的项目目录：\r\n\r\n```\r\nmkdir crossmint-drop\r\ncd crossmint-drop\r\n```\r\n\r\n为你的应用创建一个文件，`app.ts`：\r\n\r\n```\r\necho > app.ts\r\n```\r\n\r\n使用“yes”标志初始化你的项目，以使用新包的默认值：\r\n\r\n```\r\nyarn init --yes\r\n#or\r\nnpm init --yes\r\n\r\n```\r\n\r\n创建一个启用.json导入的`tsconfig.json`：\r\n\r\n```bash\r\ntsc -init --resolveJsonModule true --target es2020\r\n\r\n```\r\n\r\n创建一个`results`文件夹，用于保存我们空投的输出：\r\n\r\n```\r\nmkdir results\r\n\r\n```\r\n\r\n#### 安装Solana Web3依赖项\r\n\r\n虽然本练习不是必需的，但我们将使用Solana-Web3库在调用Crossmint API之前验证钱包地址。我们还将使用Axios来向Crossmint发出HTTP请求。在终端中输入：\r\n\r\n```\r\nyarn add @solana/web3.js@1 axios\r\n#or\r\nnpm install @solana/web3.js@1 axios\r\n\r\n```\r\n\r\n我们需要从这些库和fs中获取一些组件，以便将结果写入文件。在你选择的IDE中打开`crossmint-drop`文件夹（我们将使用VSCode），并在`app.ts`的第1行添加以下导入语句：\r\n\r\n```\r\nimport { PublicKey } from '@solana/web3.js';\r\nimport axios from 'axios';\r\nimport fs from 'fs';\r\n\r\n```\r\n\r\n\r\n\r\n#### 使用你的QuickNode端点连接到Solana集群\r\n\r\n要快速访问Crossmint API，以便你能够使用一行代码创建并发送NFT给用户，即使他们没有加密钱包，你需要创建带有Crossmint附加功能的QuickNode端点。在[此处](https://www.quicknode.com/signup?utm_source=internal&utm_campaign=guides&utm_content=how-to-use-crossmint-nft-api)注册免费账户。创建一个新的Solana Devnet端点，并确保用Crossmint NFT Mint API进行配置：\r\n\r\n![新附加功能](https://img.learnblockchain.cn/2025/02/25/0-2f7af0e0b1c41d46af05ed5ec26ef94a.png)\r\n\r\n复制HTTP提供程序链接：\r\n\r\n![新节点](https://img.learnblockchain.cn/2025/02/25/newNodeDevnet-c685bd4807744e357b9761c1822a904d.png)\r\n\r\n在`app.ts`中的导入语句下，声明你的RPC（我们在运行脚本时需要这一点）：\r\n\r\n```\r\nconst QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';\r\n\r\n```\r\n\r\n你的环境应该看起来像这样。\r\n\r\n![准备构建](https://img.learnblockchain.cn/2025/02/25/2-cf21255ca132c353befab2275e631e38.png)\r\n\r\n你已准备就绪。让我们开始构建应用！\r\n\r\n### 声明类型\r\n\r\n我们将为脚本使用TypeScript，因此我们将定义一些类型和接口，以帮助保持代码的整洁和准确。如果你是JavaScript开发人员或不喜欢使用TypeScript，也没关系。\r\n\r\n在代码中的`// 类型`部分创建，并在应用的顶部（在导入后）添加以下代码：\r\n\r\n```\r\n// TYPES\r\n\r\ntype Email = string;\r\ntype StringPubKey = string;\r\ntype Destination = Email | StringPubKey;\r\n\r\ninterface NftMetadata {\r\n    name: string,\r\n    image: string,\r\n    description: string\r\n    attributes: {trait_type: string, value: string}[],\r\n    properties: {\r\n        files: {uri: string, type: string}[],\r\n        category: string\r\n    }\r\n}\r\n\r\ninterface MintNftProps {\r\n    destination: Destination,\r\n    qnEndpoint: string,\r\n    collectionId: string,\r\n    nftInfo: NftMetadata\r\n}\r\n\r\ninterface FetchMintProps {\r\n    qnEndpoint: string,\r\n    collectionId: string,\r\n    crossmintId: string\r\n}\r\n\r\ninterface MintResult {\r\n    destination: string,\r\n    crossmintId: string,\r\n    mint: string\r\n}\r\n\r\n```\r\n\r\n以下是每个声明的小介绍：\r\n\r\n- 首先，我们将我们的_destination_声明为_Email_或_StringPubKey_（这将是我们希望用户的NFT发送到的位置）。\r\n- 接下来，我们定义了我们的_NftMetadata_，它接受关于我们的NFT的基本信息，包括一个_traits_数组。\r\n- _MintNftProps_定义了我们将在铸造函数中使用的输入。我们需要一个接收地址，一个QuickNode端点，集合ID，以及正在铸造的NFT的元数据。\r\n- _FetchNftProps_定义了我们将用来检查铸造状态的输入。\r\n- _MintResult_概述了我们将如何记录结果（包括接收地址、来自CrossMint的唯一ID以及铸造地址——在Solana区块链上的唯一公钥）。\r\n\r\n### 定义关键常量\r\n\r\n让我们为我们的项目定义一些关键常量。在你的类型下面，添加：\r\n\r\n```\r\n// CONSTANTS\r\n\r\nconst QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';\r\nconst COLLECTION_ID = 'default-solana';\r\nconst DROP_LIST: Destination[] = [\\\r\n    'quickguides@test.com',\\\r\n    'CTrLzkrcnqgqSTmzJ146ZTRkLAvwcjnxGSZBvqC5BH3w',\\\r\n    'quickdemo@test.com',\\\r\n    'DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H'\\\r\n];\r\nconst DELAY = 1000;\r\n\r\n```\r\n\r\n- 我们首先将`QUICKNODE_RPC`移动到其余常量同处。\r\n- 然后将我们的`COLLECTION_ID`设置为`default-solana`。这是Crossmint NFT在Solana上的默认铸造值。如果你计划启动一系列NFT，创建自己的集合可能是明智的。你可以使用`cm_createCollection`方法做到这一点（[文档](https://docs.crossmint.com/docs/cm-createcollection)）。出于本示例的目的，我们将使用默认值。\r\n- 一份我们希望发送NFT的电子邮件地址和Solana钱包地址列表（我们将设置我们的应用程序以能够同时处理两者）。Crossmint的Mint API为新用户创建钱包，使你可以将NFT发送到他们的电子邮件地址，并轻松地引导新用户。\r\n- 我们将添加一个简单的延迟，以防止触发任何速率限制。我们建议你查看Crossmint的[最佳实践](https://docs.crossmint.com/docs/best-practices)，如果你计划发起任何大规模的NFT空投。我们将`DELAY`设置为1秒（1,000毫秒）。\r\n\r\n让我们添加一个额外的工具常量，即`wait`函数。我们将使用它在API调用之间创建延迟：\r\n\r\n```\r\nasync function wait(ms: number):Promise<void> {\r\n    return new Promise<void>((resolve) => {\r\n      setTimeout(() => {\r\n        resolve();\r\n      }, ms);\r\n    });\r\n}\r\n\r\n```\r\n\r\n好的！让我们进入Crossmint API。\r\n\r\n### 创建铸造函数\r\n\r\n让我们创建一个HTTP请求到Crossmint来铸造我们的NFT。此函数将完成三件事：\r\n\r\n1. 验证我们的接收地址是否为有效的电子邮件地址或Solana公钥\r\n2. 组装POST请求\r\n3. 发送POST请求并处理响应\r\n\r\n在`app.ts`中，添加一个`requestCrossMintNft`函数：\r\n\r\n```\r\nconst requestCrossMintNft = async ({ destination, qnEndpoint, collectionId, nftInfo }: MintNftProps) => {\r\n    // 正则表达式验证电子邮件地址\r\n    const emailRegex: RegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\r\n\r\n    // 验证并定义接收者（作为电子邮件地址或Solana钱包，否则抛出错误）\r\n    let recipient: string;\r\n    if (emailRegex.test(destination)) {\r\n        recipient = `email:${destination}:sol`;\r\n    } else if (new PublicKey(destination)) {\r\n        recipient = `solana:${destination}`;\r\n    }\r\n    else {\r\n        throw new Error('无效的接收地址（必须是有效的电子邮件地址或Solana钱包地址）。');\r\n    }\r\n\r\n    // 组装POST请求\r\n    const metadata = {\r\n        \"name\": nftInfo.name,\r\n        \"image\": nftInfo.image,\r\n        \"description\": nftInfo.description\r\n    };\r\n    const data = {\r\n        jsonrpc: \"2.0\",\r\n        id: 1,\r\n        method: \"cm_mintNFT\",\r\n        params: [collectionId, recipient, metadata], // https://docs.crossmint.com/docs/cm-mintnft\r\n    };\r\n    const config = {\r\n        headers: {\r\n            \"Content-Type\": \"application/json\",\r\n        },\r\n    };\r\n\r\n    // 发送POST请求\r\n    return new Promise<string>(async (resolve, reject) => {\r\n        try {\r\n          let response =  await axios.post(qnEndpoint, data, config);\r\n          resolve(response.data.result.id as string);\r\n        } catch (error) {\r\n          reject(\"发送请求到CrossMint时出现错误。检查输入。\");\r\n        }\r\n      });\r\n}\r\n\r\n```\r\n\r\n我们所做的事情如下：\r\n\r\n1. 首先，我们使用JavaScript正则表达式（_RegExp_）和Solana _PublicKey_类来检查提供的地址是否为有效的目的地。基于你的分发列表（例如，如果你只发送到电子邮件地址，你就不需要Solana PublicKey检查，反之亦然），可以随意修改此内容。如果无效，我们将抛出错误。\r\n\r\n正则表达式\r\n\r\n如果你从未使用过正则表达式，`emailRegex`模式检查输入字符串是否以一个或多个来自集合`a-z`、`A-Z`、`0-9`、`.!#$%&'*+/=?^_{|}~-`的字符开始，后面跟上`@`符号，然后是一个或多个来自集合`a-z`、`A-Z`、`0-9`及连字符`-`的字符，有61个字符的最大限制，并且可以选择在最后添加一个点`.`后面跟上更多字符，至多61个字符，来自集合`a-z`、`A-Z`、`0-9`和连字符`-`。\r\n\r\n2. 然后，我们根据[Crossmint文档](https://docs.crossmint.com/docs/cm-mintnft)组装我们的POST请求。请注意，我们的`metadata`是使用我们的`nftInfo`参数编译的。这意味着我们可以为每个铸造的NFT创建自定义属性！\r\n\r\n3. 最后，我们返回一个承诺以POST `cm_mintNFT`方法。如果收到成功的响应，承诺将返回Crossmint ID（一个唯一的交易标识符）。\r\n\r\n### 创建获取铸造函数\r\n\r\n在我们向Crossmint发送铸造请求后，我们需要一种方法来验证我们的铸造是否成功（到目前为止，我们只知道Crossmint成功接收了我们的请求，而不知道Solana是否确认了铸造的NFT）。为此，我们可以使用从前一步返回的ID进行状态检查。\r\n\r\n创建一个新函数，`fetchMintAddress`：\r\n\r\n```\r\nconst fetchMintAddress = async ({ collectionId, qnEndpoint, crossmintId }: FetchMintProps) => {\r\n\r\n    // 组装POST请求\r\n    const data = {\r\n        jsonrpc: \"2.0\",\r\n        id: 1,\r\n        method: \"cm_getNFTMintStatus\",\r\n        params: [collectionId,crossmintId], //https://docs.crossmint.com/docs/cm-getnftmintstatus\r\n    };\r\n    const config = {\r\n        headers: {\r\n            \"Content-Type\": \"application/json\",\r\n        },\r\n    };\r\n\r\n    // 发送POST请求\r\n    return new Promise<string>(async (resolve, _reject) => {\r\n        try {\r\n          let response =  await axios.post(qnEndpoint, data, config);\r\n          resolve(response.data.result.onChain.mintHash as string);\r\n        } catch (error) {\r\n          //reject(\"获取铸造地址时出错。\");\r\n        }\r\n      });\r\n}\r\n\r\n```\r\n\r\n从结构上看，这与我们上一步非常相似：\r\n\r\n1. 我们组装POST请求（这次调用`cm_getNFTMintStatus`并传入从上一步返回的`crossmintId`）。\r\n2. 发送POST请求，并在成功时返回铸造的NFT的唯一链上铸造哈希。\r\n\r\n将这两者结合起来，你可以运行这两个函数以铸造单个NFT，但我们想要一些乐趣——让我们空投一堆👀\r\n\r\n### 创建批量空投函数\r\n\r\n我们具备了使用Crossmint API铸造和确认NFT所需的所有工具——我们需要一种能够执行批量投放并具有自定义每个NFT的能力的方法。我们需要创建一个`dropNfts`函数，它将：\r\n\r\n1. 使用`.map`为`dropList`中的每个_目的地_创建一个承诺。\r\n2. 基于某个唯一特征定义我们的元数据（在此情况下，我们将使用索引`i`为每个NFT赋予一个唯一的名称和特性。你可以根据自己的风格更新元数据！）。\r\n3. 创建一个承诺：\r\n   - 使用`requestCrossMintNft`请求Crossmint铸造NFT\r\n   - 通过调用等待1分钟来等待铸造的发生。（注意：这是我们演示的简单方法。根据你的需求，你可能希望设置一些查询/重试逻辑。）\r\n   - 使用`fetchMintAddress`验证铸造是否成功\r\n   - 返回关于投放NFT的信息\r\n4. 使用`Promise.allSettled()`执行所有承诺\r\n5. 在`results`目录中创建我们结果的日志\r\n\r\n以下函数有点长，但我们已经在前面的步骤中完成了所有繁重的工作！在`app.ts`中创建一个`dropNfts`函数，该函数接受_目的地_列表、你的QuickNode端点和集合ID：\r\n\r\n```\r\nconst dropNfts = async (dropList: Destination[], qnEndpoint: string, collectionId: string) => {\r\n    console.log('生成承诺中...');\r\n    let promises = dropList.map((drop, i) => {\r\n        // 1-定义自定义元数据\r\n        const nftNumber = (i+1).toString();\r\n        const nftInfo = {\r\n            name: `Demo Airdrop # ${nftNumber}`,\r\n            image: 'https://arweave.net/UTFFfaVA3HoFcxwoMHEcvBLq19HrW6FuzpyygXqxduk',\r\n            description: '通过Quicknode附加功能使用Crossmint铸造API的演示空投NFT',\r\n            attributes: [\\\r\n                {\\\r\n                    trait_type: \"background\",\\\r\n                    value: \"blue\"\\\r\n                },\\\r\n                {\\\r\n                    trait_type: \"type\",\\\r\n                    value: \"pixel\"\\\r\n                },\\\r\n                {\\\r\n                    trait_type: \"id\",\\\r\n                    value: nftNumber\\\r\n                }\\\r\n            ],\r\n            properties: {\r\n                files: [\\\r\n                    {\\\r\n                        \"uri\": \"https://arweave.net/UTFFfaVA3HoFcxwoMHEcvBLq19HrW6FuzpyygXqxduk\",\\\r\n                        \"type\": \"image/png\"\\\r\n                    }\\\r\n                ],\r\n                category: \"image\"\r\n            }\r\n        };\r\n        // 2-创建承诺\r\n        return new Promise< MintResult >(async (resolve, reject)=>{\r\n            setTimeout(async ()=>{\r\n                try {\r\n                    let crossmintId = await requestCrossMintNft({\r\n                      destination: drop,\r\n                      qnEndpoint,\r\n                      collectionId,\r\n                      nftInfo\r\n                    });\r\n                    if (!crossmintId) throw new Error('未收到CrossMint ID。');\r\n                    await wait(60000); // 等待1分钟\r\n                    let mint = await fetchMintAddress({\r\n                        collectionId,\r\n                        qnEndpoint,\r\n                        crossmintId\r\n                    });\r\n                    resolve({\r\n                        destination: drop,\r\n                        crossmintId: crossmintId,\r\n                        mint: mint ?? ''\r\n                    });\r\n                } catch (error) {\r\n                    reject('发送请求到CrossMint时出现未知错误。');\r\n                }\r\n            },i * DELAY);\r\n        })\r\n    });\r\n    // 3-执行承诺\r\n    console.log('执行承诺...(这将需要1分钟以上)');\r\n    let results = await Promise.allSettled(promises);\r\n    // 4-保存结果\r\n    console.log('正在写入结果到./results/results.json');\r\n    let data = JSON.stringify(results);\r\n    fs.writeFileSync('./results/results.json',data);\r\n}\r\n\r\n```\r\n\r\n太棒了！让我们开始吧。\r\n\r\n### 空投你的NFT 🪂\r\n\r\n在你的`app.ts`末尾，调用你的`dropNfts`函数：\r\n\r\n```\r\ndropNfts(DROP_LIST, QUICKNODE_RPC, COLLECTION_ID);\r\n\r\n```\r\n\r\n在终端中输入以下内容：\r\n\r\n```\r\nts-node app\r\n\r\n```\r\n\r\n大约等待一分钟后，你应该看到新生成的`results.json`，其中包含你的铸造结果数组：\r\n\r\n```\r\n[\\\r\n    {\\\r\n        \"status\": \"fulfilled\",\\\r\n        \"value\": {\\\r\n            \"destination\": \"quickguides@test.com\",\\\r\n            \"crossmintId\": \"60f33f72-a8d8-41ce-b28a-bc899aa7b929\",\\\r\n            \"mint\": \"AbJjT4j9MYQTya9aZ9qmCd36dspwoMnhfsEpZL91sFwG\"\\\r\n        }\\\r\n    },\\\r\n    {\\\r\n        \"status\": \"fulfilled\",\\\r\n        \"value\": {\\\r\n            \"destination\": \"DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H\",\\\r\n            \"crossmintId\": \"0c4836cb-26dc-48a2-a55f-5d3ca40699da\",\\\r\n            \"mint\": \"5RRDSrq6ME5Yz9qXcKRvSQeGD1F2AEnURnjfCs7BTEvN\"\\\r\n        }\\\r\n    },\\\r\n    // 等等。\\\r\n]\r\n\r\n```\r\n\r\n干得不错！\r\n\r\n### 结束语\r\n\r\n就这样，你可以批量空投NFT给你的web2和web3客户！如果你在本指南中遇到任何问题或只是想展示你的NFT项目，请联系我们，通过[Discord](https://discord.gg/quicknode)或[Twitter](https://twitter.com/QuickNode)告诉我们你在做什么！\r\n\r\n \r\n\r\n>- 原文链接： [quicknode.com/guides/sol...](https://www.quicknode.com/guides/solana-development/nfts/how-to-use-crossmint-nft-api)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/25306","address":null},"history":null,"timestamp":1740447646,"version":1}