使用 Node.js + JIMP 生成随机 NFT

公众号:元壤

元壤:一分钟搭建您专属的 NFT 数字藏品平台

使用 Node.js 和 JIMP 随机混合图层并使用图像和元数据创建独特的 NFT。

https://img.chengxuka.com

在本教程中,我们将使用 OpenSea 标准,使用不同的特征生成具有自己元数据的随机 NFT(本教程也可以适用于 Solana 和其他链,请记住,您需要按照您正在使用的链),这意味着按照本指南,您最终将获得一包准备上传到以太坊或 Polygon 链的 NFT。

从头开始创建 NFT 不是一项独立的工作,我们需要一位设计师/艺术家逐层绘制特征。所以我们需要所有的眼睛、嘴巴、鼻子,以及我们想要包含在 NFT 中的每一个特征。

重要提示:每个layer/image 应具有相同的尺寸;我们的脚本将专注于覆盖具有透明背景的图层,这比一层一层地定位要容易得多。所有特征 PNG 文件都将具有与背景特征相同的尺寸。

下面是我们将要构建的 NFT 示例,其中包含正确的层顺序、错误层的示例以及 27 个 NFT。为了本教程,我用 iPad Pro 和 Procreate 应用程序画了几层小家伙(我是开发人员,不是设计师,但至少我们可以得到一个真实的项目):

https://img.chengxuka.com

如上所示,我们将具有以下特征,按照这个精确的顺序:

1- Background
2- Body
3- Outfit
4- Head
5- Nose
6- Mouth
7- Eyes
8- Sunglasses
9- Headwear

使用 Node.js 编写脚本

现在我们有了要在 NFT 生成器中使用的特征,我们就可以继续对其进行编码了。这里你要事先装好了 Node.js 和 NPM,并且你要知道创建 NPM 项目的基础知识。

让我们创建一个新文件夹,运行npm init(您可以在所有字段中输入空白), 然后将 Traits 文件夹复制到里面。现在我们将安装我们将要使用的库:fs(文件系统)、Jimp(Node.js 的图像处理库)、dotenv(安全地保存我们的 pinata 凭据)和Pinata SDK(将我们的图像上传到 IPFS )。

npm i jimp fs @pinata/sdk

现在我们将创建 index.js 文件,即脚本的入口点。我们将编写一个 while 循环来从 1 开始到我们的 NFT 供应编号。让我们在本教程中做 100,但你想要其他的数量也可以。只需记住添加足够多的特征变体以避免重复(或在此脚本上添加中间件以防止它们创建具有重复元数据的 NFT)。在 index.js 中写入以下内容:

const initial = async () => {
    var _thisIndex = 1;
    const _maxSupply = 100;
    while(_thisIndex <= _maxSupply) {
        try{
            console.log('Generating NFT '+_thisIndex);
            _thisIndex++;
        }catch(e){
            console.error('Error while generating NFT '+_thisIndex)
            console.log(e);
            _thisIndex = _maxSupply + 1;
        }
    }
}
initial();

我们将在另一个名为generator.js的文件中处理特征生成,以保持启动与逻辑分离。此生成器将使用特征文件随机获取具有常见、不常见、稀有和传奇稀有度的特征变体。

让我们首先在同一个文件夹中创建一个traits.js文件,其中包含多个方法来生成眼睛、嘴巴、头部等。我们将使用Math.random函数并返回一个字符串。

从背景特性开始,我选择 Lava 和 Forest 背景为普通,Mountain 和 Aqua 为不常见, Psycho 和 Snow 为稀有,Galaxy 为传奇。

使用我们正在使用的名称格式,每个特征文件名都称为 CATEGORY_NAME。例如,Aqua 背景特征称为 Background_Aqua。保持这样的格式,不仅是为了更好的管理,而且是为了避免数十(或数百)行代码,还为了重用命名。

我还通过以下方式设置稀有度范围的常数:

https://img.chengxuka.com

const COMMON_MAX_RARITY = 50; //Starts from 1
const UNCOMMON_MAX_RARITY = 75;
const RARE_MAX_RARITY = 95;
const LEGENDARY_MAX_RARITY = 100;
const randomElement = (list) => {
    const _random = Math.floor(Math.random() * list.length);
    return list[_random];
}
const getBackground = () => {
    const _random = Math.floor(Math.random() * LEGENDARY_MAX_RARITY);
if(_random < COMMON_MAX_RARITY) {
        return randomElement([
             'Lava', 'Forest'
        ]);
    }else if(_random < UNCOMMON_MAX_RARITY) {
        return randomElement([
            'Mountain', 'Aqua'
        ]);
    }else if(_random < RARE_MAX_RARITY) {
        return randomElement([
            'Psycho', 'Snow'
        ]);
    }else if(_random < LEGENDARY_MAX_RARITY) {
        return randomElement([
            'Galaxy'
        ]);
    }
}
module.exports = {
    getBackground
}

现在我们可以添加其余的特征。

您会注意到,对于身体和头部,我们使用相同的函数:getBodyAndHead。这是因为我们希望身体和头部匹配,alien 和 alien,zombie 和 zombie,等等。

https://img.chengxuka.com

现在我们已经准备好 traits.js 文件,让我们创建 generator.js 文件,我们将使用它来实际生成图像和元数据。由于我们将使用 IPFS 将我们的图像保存到区块链,我们将需要Pinata凭据。

打开 Pinata 网站:https ://www.pinata.cloud/并创建一个帐户。这个过程非常简单,Pinata 团队做得很好,让一切尽可能简单。您必须通过电子邮件验证您的帐户,然后再次登录。

注意:免费 Pinata 计划的上传限制为 1GB。之后,您可以支付订阅费用并无限上传。

登录后,您将在屏幕右上角看到一个菜单。选择API 密钥选项。

https://img.chengxuka.com

现在创建一个具有管理员权限的新 API 密钥,设置名称并保存 API Key 和 API Secret,我们将在 Node.js 上需要使用它们。

由于我们想保护我们的凭据,我们将使用 dotenv 库。让我们创建一个包含以下内容的 .env 文件:

PINATA_KEY="你的API Key"
PINATA_API_SECRET="你的API Secret"

回到我们的 generator.js 文件,让我们开始添加基础知识:导入库并使用我们的密钥设置 Pinata。我们还需要引用我们的项目文件夹(参见下面的_path变量):

require('dotenv').config();
const Jimp = require('jimp');
const fs = require('fs');
const pinataSDK = require('@pinata/sdk');
const pinata = pinataSDK(process.env.PINATA_KEY, process.env.PINATA_API_SECRET);
const Traits = require('./traits');
const sleep = (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
}
const build = async(index, onComplete) => {
    const _path = '/Users/jcmacur/Documents/Projects/nftgenerator/';
    onComplete();
}
module.exports = {
    build
}

在继续之前,我们可以更新我们的 index.js 文件,它会在每次循环时调用 Generator。我们现在对其进行更新,以便我们可以在开发时进行测试并查看结果。将 index.js 内容替换为以下内容:

const Generator = require('./generator')
const initial = async () => {
    var _thisIndex = 1;
    const _maxSupply = 100;
    while(_thisIndex <= _maxSupply) {
        try{
            console.log('Generating NFT '+_thisIndex);
            await Generator.build(_thisIndex, () => {
                _thisIndex++;
            })
        }catch(e){
            console.error('Error while generating NFT '+_thisIndex)
            console.log(e);
            _thisIndex = _maxSupply + 1;
        }
    }
}
initial();

我们可以开始测试脚本了。运行:

node index.js

如果到目前为止一切顺利,您应该会看到以下输出,此时,除了记录之外什么都不做。

https://img.chengxuka.com

现在我们继续,但在此之前让我们快速解释一下元数据到底是什么,以及我们需要以正确的方式构建它。

在以太坊上,NFT 智能合约使用一个同步每个代币的 URL。因此 ID 为 1 的代币将从您的 baseUrl(通常是您上传的 IPFS 文件夹)+ ID 中获取其元数据。
这意味着如果您的元数据文件夹上传到“www.myhost.com/nfts/”,则 ID 为 1 的 NFT 将是“www.myhost.com/nfts/1”。

每个元数据文件都是一个 JSON 格式的文件,没有扩展名,从 1 到我们的 Max Supply 命名。您可以在OpenSea 文档站点上阅读完整的规范,我们将使用以下结构:

{
"image":"IPFS URL",
"name":"NFT #0",
"traits":
 [
   {"trait_type":"Trait 1","value":"Blue"},
   {"trait_type":"Trait 2","value":"Red"},     
   {"trait_type":"Trait 3","value":"Green"},
 ]
}

现在我们可以完全专注于我们的generator.js文件。让我们解释一下要遵循的步骤:

  1. 我们将按顺序加载每个特征。对于每一个,我们将获得随机变化,用 Jimp 加载图像,将其添加到元数据数组中,并将其重叠到上一层(除了第一层,背景)。
  2. 我们将图像保存在 Output/images 文件夹中,该文件夹将自动创建。我们将在下一个动作之前休眠 20 毫秒,因此我们确保图像确实被保存。
  3. 我们将创建一个可读的图像流,并将其上传到 IPFS。不用担心,这也就是一两行代码。
  4. 最后,我们将保存一个没有扩展名的文件(例如,简单的“1”而不是“1.json”),其中包含 NFT 的名称、IPFS 图像 URL 和特征列表。

将我们的构建方法替换为以下代码并运行它。由于我们要通过循环 100 个 NFT,这可能需要几分钟。您可以在它生成几个之后停止它,以确保它正常工作。至此,我们只生成一个带有背景的图像:

const build = async(index, onComplete) => {
    const _path = '/Users/jcmacur/Documents/Projects/nftgenerator/';
    var _traits = [];
const background = Traits.getBackground();
    const backgroundJimp = await Jimp.read(_path+'/Traits/Background/Background_'+background+'.png');
    _traits.push({
        'trait_type': 'Background',
        'value': background
    });
var _composedImage = backgroundJimp;
await _composedImage.write('Output/images/'+index+'.png');
    await sleep(20); //We give some time for the image to be actually saved in our files
    const _readableStream = await fs.createReadStream(_path + '/Output/images/'+index+'.png');
    const _ipfs = await pinata.pinFileToIPFS(_readableStream);
await fs.writeFileSync('Output/'+index, JSON.stringify({
        "name": "My NFT #"+index,
        "traits": _traits,
        "image": "https://ipfs.io/ipfs/"+_ipfs.IpfsHash
    }))
onComplete();
}

如果您运行 node index.js ,,您应该会看到以下内容:

https://img.chengxuka.com

现在我们可以添加其余的特征,从头部和身体开始。为此,我们还要添加衣服,因为它的层应该在头部特征和身体特征之间。让我们继续在下面添加 var _composedImage = backgroundJimp; 代码如下:

const bodyAndHead = Traits.getBodyAndHead();
    const bodyJimp = await Jimp.read(_path+'/Traits/Body/Body_'+bodyAndHead+'.png');
    _traits.push({
        'trait_type': 'Body',
        'value': bodyAndHead
    });
_composedImage.blit(bodyJimp, 0, 0);
const outfit = Traits.getOutfit();
    const outfitJimp = await Jimp.read(_path+'/Traits/Outfit/Outfit_'+outfit+'.png');
    _traits.push({
        'trait_type': 'Outfit',
        'value': outfit
    });
_composedImage.blit(outfitJimp, 0, 0);
const headJimp = await Jimp.read(_path+'/Traits/Head/Head_'+bodyAndHead+'.png');
    _traits.push({
        'trait_type': 'Head',
        'value': bodyAndHead
    });
_composedImage.blit(headJimp, 0, 0);

如果我们再次运行我们的脚本,我们将看到 2 个随机物体和它们各自的头!

https://img.chengxuka.com

我们现在可以继续添加所有其余的特征,要按照涂层顺序,在我们最后一个_composedImage.blit(headJimp, 0, 0); 之下。

const nose = Traits.getNose();
    const noseJimp = await Jimp.read(_path+'/Traits/Nose/Nose_'+nose+'.png');
    _traits.push({
        'trait_type': 'Nose',
        'value': nose
    });
_composedImage.blit(noseJimp, 0, 0);
const mouth = Traits.getMouth();
    const mouthJimp = await Jimp.read(_path+'/Traits/Mouth/Mouth_'+mouth+'.png');
    _traits.push({
        'trait_type': 'Mouth',
        'value': mouth
    });
_composedImage.blit(mouthJimp, 0, 0);
const eyes = Traits.getEyes();
    const eyesJimp = await Jimp.read(_path+'/Traits/Eyes/Eyes_'+eyes+'.png');
    _traits.push({
        'trait_type': 'Eyes',
        'value': eyes
    });
_composedImage.blit(eyesJimp, 0, 0);
const sunglasses = Traits.getSunglasses();
    const sunglassesJimp = await Jimp.read(_path+'/Traits/Sunglasses/Sunglasses_'+sunglasses+'.png');
    _traits.push({
        'trait_type': 'Sunglasses',
        'value': sunglasses
    });
_composedImage.blit(sunglassesJimp, 0, 0);
const headwear = Traits.getHeadwear();
    const headwearJimp = await Jimp.read(_path+'/Traits/Headwear/Headwear_'+headwear+'.png');
    _traits.push({
        'trait_type': 'Headwear',
        'value': headwear
    });
_composedImage.blit(headwearJimp, 0, 0);

现在已经完成了!让我们再次运行 node index.js 来查看新随机创建的 NFT:

https://img.chengxuka.com

这里是我的运行结果:

WX20220921-102342@2x

生成的图片:

WX20220921-102403@2x

结论

我们还没有完成。一旦我们创建了所有 NFT,我们或指定的开发人员,将必须将 IPFS 文件夹链接到智能合约。所以我们需要上传那个文件夹并获取 URL。

我们该怎么做呢?我们将所有没有扩展名的文件移动到一个单独的文件夹(图像文件夹不是必需的,创建它只是为了实时查看结果,因为 IPFS 可能需要一些时间来刷新,它可能会占用您 Pinata 的大部分空间空间)。现在我们拥有包含所有元数据文件的文件夹,打开https://app.pinata.cloud/pinmanager并转到上传,文件夹。选择我们的文件夹,为其命名,然后上传:

https://img.chengxuka.com

上传后,您将能够看到 IPFS 哈希:

https://img.chengxuka.com

您的元数据文件夹 URL 将是https://ipfs.io/ipfs/Your CID/。这样,当 NFT 被链接到索引时,我们以index 为 5 为例,url 将为https://ipfs.io/ipfs/Your CID/5,您可以打开该 URL(将其替换为您的 CID上传的文件夹)来自己测试。

参考文献:https://juancurti.medium.com/generate-random-nfts-with-node-js-sourcecode-b93a2ab411fe

相关新闻

联系我们

联系我们

133-3118-4066

在线咨询:点击这里给我发消息

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部