最近對NFT生態很感興趣,也進場體驗了一下,身為工程師不免開始好奇是否我也有能力開發一個NFT專案,於是便開始研究發行一個NFT項目會使用到的技術。
目前市面上大多的NFT項目都是生成式的圖片項目,這些圖片可以讓你做為大頭貼(PFP
-Profile Picture)或其他用途使用,也可能會有一些額外的賦能,很多項目一次發行都是幾千個NFT起跳,為了圖片的差異性會使用程式協助拼湊出各種不重複的圖片,因此我的第一步是研究產圖框架的使用,而實作一個NFT專案的簡易流程會像這樣:
這邊使用的是hashlips這套node.js產圖引擎,使用上非常簡單,我們只需要把各個圖層的圖片準備好並稍加配置,就可以產出最終圖片以及metadata,接著再由我們自行上傳至ipfs或其他空間即可。
首先我們把hashlips的code抓下來並npm install
,資料夾結構會長這樣:
constants
: 一些常量配置 layers
: 產圖用的各個圖層依照資料夾劃分在這目錄底下 modules
src
: 主程式及配置檔 utils
: 一些額外的擴充工具如產metadata及像素化等等 index.js
我們基本上只會更動到layers
及src
兩個地方,這邊假設我們的NFT圖片資源是一個由頭、身體、衣服、帽子等元素構成的,我們在畫圖的時候必須讓每個圖層輸出的圖片大小都是統一的尺寸,如此才能讓圖層一層一層推疊上去而位置不會跑掉。
因此我們的layers文件夾內大致會變成這樣:
這邊要提到metadata
一下,注意到資料夾名稱及檔名了嗎?這個名稱是有用意的,hashlips引擎會幫我們把這個名稱轉為metadata中的屬性,我們參考一下當前最大的NFT交易平台opensea所支援的metadata結構,可以看到attributes
的定義是一個物件陣列,其中的物件有著trait_type
及value
兩個屬性,而他們的值就分別對應了我們的文件夾及檔案名稱。
而hashlips還有個很貼心的設計讓我們可以輕易的分配稀有度,就是在檔名加上權重的標示,以衣服來說,若我把兩件衣服的檔名分別改成: Red T#1.png
及Yellow T#4.png
,#字號後面的數字便代表權重,此時有20%機率產出紅衣服的圖片,80%機率產出黃衣服的圖片,若不設置的話預設權重都是1。
若不想要以#字號當作標記也可以在config中修改:
// config.js
const rarityDelimiter = "#";
而config中還有一些我們必須修改的配置,如下:
const namePrefix = "Your Collection"; // NFT名稱,對應到metadata中的name
const description = "Remember to replace this description"; // 描述,對應到metadata中的description
const baseUri = ""; // 這邊目前我們還沒取得,待產出圖片並上傳後需填上並generate metadata
const layerConfigurations = [
{
growEditionSizeTo: 20, // 產出幾張圖
layersOrder: [ // 用來生成圖片的圖層,這邊順序需正確,譬如衣服會在身體後面加上
{ name: "BODY" },
{ name: "CLOTH" },
{ name: "HEAD" },
{ name: "HAT" },
// ...
],
},
// 這邊可以添加其他組物件
// 假設在這邊設置另一組growEditionSizeTo: 30
// 則會產出另外10張該配置的圖
];
const format = { // 產出圖片大小
width: 512,
height: 512,
smoothing: false,
};
還有很多配置,像是背景及gif等等,大家有興趣可以自行研究。
配置好後執行指令產圖。
npm run generate
hashlips在產圖時會先幫我們把組成該圖的所有圖層名組合起來,像是這樣: 0:Black.png-1:Yellow T.png-5:Yellow 2.png-2:Snapback.png
,接著透過sha1
加密成DNA數值並記錄下來,往後若其他圖也產出了相同的DNA則會重新產圖直到每張圖都有獨特的DNA。
若你想讓某個圖層不列入DNA的計算當中,也可針對該圖層設置
bypassDNA
執行完成後便可在/build
資料夾底下找到images
及json
。
而這邊json中的metadata由於我們還沒上傳圖片,所以其中image
欄位所指的資源路徑還不正確,接下來我們要將整個images目錄上傳雲端,而這邊我決定透過pinata上傳至ipfs。
pinata有提供免費的流量,創建帳號後便可以很直覺的上傳檔案,於是我們先將整個images資料夾上傳。
上傳ipfs後會取得一組CID,而
ipfs://[CID]/1.png
就是我們第一張圖片的路徑。
chrome目前尚未支援直接訪問ipfs協議,我們可以透過ipfs gateway訪問。
如此一來我們的圖片資源便公諸於世了,接下來我們終於可以生成正式的metadata。
// config.js
const baseUri = "ipfs://[CID]";
接著更新metadata json
npm run update_info
最終metadata會長這樣:
{
"name": "Your Collection #1",
"description": "Remember to replace this description",
"image": "ipfs://QmSvc9ZhUvqJ9CdK9UTgDMNJj4LrrFngy8vmx8P9eqvGsb/1.png",
"dna": "60063f8929616026c7af50e668945bc39c5bec6e",
"edition": 1,
"date": 1649991209805,
"attributes": [
{
"trait_type": "BODY",
"value": "Black"
},
{
"trait_type": "CLOTH",
"value": "Yellow T"
},
{
"trait_type": "HEAD",
"value": "Yellow 2"
},
{
"trait_type": "HAT",
"value": "Snapback"
}
],
"compiler": "HashLips Art Engine"
}
如同剛剛上傳images的流程,我們同樣將整個json資料夾透過pinata上傳至ipfs,並取得ipfs CID。
至此我們便完成了NFT靜態資源的部分,而metadata的資源路徑將會在後續的合約開發中使用。
製作一個NFT project: 產圖、前端、合約經驗分享(二)