手把手從零發行 StarkNet NFT(一)
本次系列文除了介紹以 Cairo 語言在 StarkNet 實作 ERC-721 Token 的智能合約(包含開發環境、編譯、部署)之外,還會介紹如何撰寫一個 StarkNet 上的 Mint Dapp。
本文作為系列文的第一篇,會帶著大家先建置 StarkNet Contract 的開發環境,之後在 StarkNet 這個 L2 的 Goerli 測試網 Deploy 屬於自己的 NFT 合約。
Author: ChiHaoLu(chihaolu.eth) @ imToken Labs
Intro.
Cairo 作為 StarkNet 的底層語言(撰寫 StarkNet OS)和合約語言(StarkNet Contract),他之所以是一個強大的擴容工具的原因是:Validity Proof 能夠讓龐大的計算(也就是過往在 Layer1 Ethereum 會耗費大量 Gas 的那些 Operation)移動到鏈下,然後使用 ZKP(這邊是 STARKS)證明其運算(或者是 Transaction Batchs)的過程(trace)與結果,提交到 L1 鏈上驗證。
同時 Cairo 本身就是一個撰寫零知識證明 Program 的語言,可以說 StarkNet Contract 只是 Cairo Program 的一個子集合。
Cairo Official Link:
Background
本系列文會希望大家已經熟悉怎麼在 Ethereum 發 NFT 了,內容會著重在 StarkNet 開發時比較不一樣的地方,同樣在 L1 開發時有的概念我就不會再做敘述。
在 StarkNet 中只有合約帳戶(CA, Contract Account)存在,沒有像 Ethereum 一樣有 EOA 與 CA 之分。同時若非透過錢包商(ArgentX, Bravvos)建立一個帳戶的過程將會非常繁瑣,因此希望大家仔細地跟著步驟做!
Please Note
- Cairo 1.0 上線後,當前合約在未來可能需要進行升級,但本文不會實作可升級合約,因此如果想在主網上線的人請多加小心!不過目前還有許多功能 Cairo 1.0 沒有呈現,所以先用此文章版本的 Cairo 中短期是蠻穩定的!
- 本文不包含在 Mint 時收取 Ether 作為費用(因為那有點複雜),言下之意為這將會是一個 Free Mint NFT。
Development Environment
以 Cairo 撰寫 StarkNet Contract 的工具主要有以下,這次會選擇我覺得擴容性最佳且開發起來最穩定的 StarkNet CLI Tool。
StarkNet CLI
要使用 StarkNet CLI 我們必須在電腦中有 python3.9,因此請各位先按照自己的作業系統在電腦中下載 python3.9。檢查方式為打開 Terminal 或 Command Line 輸入指令能進入互動模式就為成功:
$ python
Python 3.9.7
>>> print("Hello World!")
Hello World!
首先我們找到一個自己喜歡的資料夾,未來這裡將會儲存我們的合約,然後使用以下指令進入 python3.9 的 VE(Vitual Environment)模式:
$ python3.9 -m venv myVE
$ cd myVE
$ source ./bin/activate
$ python --version
> Python 3.9.15 // ✅ Your python in the VE is 3.9
以上使用 MacOS 做為示範,Windows 需要使用 Docker 來進行 StarkNet 開發。
接下來是下載兩個我們主要會使用的套件,一是 cairo-lang
(包含了我們將要使用的 StarkNet CLI),二是 openzeppelin-cairo-contracts
,我們未來要引用的 NFT 合約依然是 OpenZeppelin 幫我們寫好的。
$ CFLAGS=-I`brew --prefix gmp`/include LDFLAGS=-L`brew --prefix gmp`/lib pip3 install ecdsa fastecdsa sympy cairo-lang
$ pip install openzeppelin-cairo-contracts
下載 fastecdsa 時可能會遇到問題,可以查看 官方 Repo 是否有缺少東西。
$ starknet --version
> starknet 0.10.1 // ✅
$ starknet get_block --network alpha-goerli
> { ... } // ✅ the terminal will print lots of block information
下載完之後接著要建置 StarkNet CLI 的環境變數,分別是設定當前 CLI Tool 指令會在哪個網路執行,以及 CLI Tool 操作的錢包設定檔為何,設定完之後打上 $ starknet tx_status
的指令應該要能印出當前該網路的交易資訊。
$ export STARKNET_NETWORK=alpha-goerli
$ export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount
$ starknet tx_status --hash 0x7067a0b671587f52adc49f11d79eb17b5df86820fc3bf0e71b82f460cc40738
>
// Print the tx which is accepted in L1✅
關於 StarkNet 上的主流帳戶設計有兩種,一是 OpenZeppelin 的帳戶合約,另一則是 ArgentX。設定檔的目的在於假設我們今天要使用 StarkNet CLI 以我們的帳戶去呼叫某個函式,CLI 要知道怎麼使用我們的帳戶合約。
所以這個設定檔會是一個 python 檔,他會藉由不同帳戶類型以不同方式(函式名、參數)去呼叫帳戶合約上的函式,以此完成我們輸入的指令想達到的目的。
Begin the StarkNet Journey
完成以上所有工作之後,接下來我們需要建立一個 StarkNet 的帳戶。這個帳戶將會預設在 StarkNet CLI 中完成所有我們輸入指令的操作,也可以做為大家在 StarkNet 上的錢包(能夠收發 Ether 和所有 Token,也能與合約互動等)。
輸入 $ starknet new_account
這個指令後,StarkNet 將會替我們隨機生產私鑰,並且對應出公鑰及合約帳戶地址。此時此刻僅僅是生出「帳戶資料」而已,並沒有部屬任何帳戶合約到鏈上,目前這個帳戶地址在 StarkNet 上也是空空如也。
$ starknet new_account
>
Account address: ...
Public key: ...
Move the appropriate amount of funds to the account, and then deploy the account
by invoking the 'starknet deploy_account' command.
輸入以下指令可以找到帳戶資訊:
$ ls ~/.starknet_accounts/starknet_open_zeppelin_accounts.json
在 StarkNet 中並沒有 EOA 的存在,所有帳戶都是 CA。一個合約帳戶想要被部屬在 StarkNet 上,他必須在自己的地址上有 Ether,之後自己部屬自己。
我知道聽起來有一點奇怪,這個帳戶沒有部屬的話,匯錢到帳戶地址然後讓他部署自己?但因為這裡如果解釋太多會變得過於複雜,因此大家先繼續照著做吧!有興趣的人可以再針對 StarkNet AA 和
deploy_account
的 Transaction Type 做深入探討。或者看看我之前寫過的 Account Abstraction 介紹系列文!
首先我們到 StarkNet Goerli Faucet 貼上自己地址並且領取,我們就能夠到 StarkScan(就是 StarkNet 上的 Etherscan) 來查看這筆交易,完成後就可以繼續下一步。
接下來我們要把自己的帳戶合約進行部署,輸入以下指令:
$ starknet deploy_account
>
Sending the transaction with max_fee: 0.000087 ETH (86844966419942 WEI).
Sent deploy account contract transaction.
Contract address: ...
Transaction hash: ...
完成之後到 StarkScan 上查看自己的帳戶地址(記得要選對網路),可以在 Portfolio 看見自己有一定的 ETH。到這裡就成功完成 StarkNet CLI 開發環境的建置了!
ERC-721 StarkNet Contract
Cairo 這個語言本身有點像 Rust 和 C 綜合起來的 Solidity,因此撰寫起來除了需要注意原本智能合約的語法不同,還需要多注意記憶體的指標和一些 hash 相關的內容。
但本篇文章不打算介紹 Cairo 的語法,主要是帶過一次簡單的開發流程,讓大家基本了解在 StarkNet 開發的感覺大概是怎麼樣,還有度過一開始最容易讓人怯步的環境建置環節。
Import OpenZeppelin Contract
首先在當前資料夾(也就是 myVE
之中)建立一個檔案叫做 MyBFT.cairo
,Cairo 程式的副檔名就是 .cairo
,之於 Solidity 程式的副檔名是 .sol
。
然後我們要做的事情就是把以下的文字貼上,最簡單的 NFT 合約就完工了!
// MyNFT.cairo
%lang starknet
from openzeppelin.token.erc721.presets.ERC721MintableBurnable.cairo import (
constructor,
// View Function
supportsInterface,
name,
symbol,
balanceOf,
ownerOf,
getApproved,
isApprovedForAll,
tokenURI,
owner,
// External Function
approve,
setApprovalForAll,
transferFrom,
safeTransferFrom,
mint,
burn,
setTokenURI,
transferOwnership,
renounceOwnership
)
基本上大家有寫過 Solidity 版本的 NFT 合約的話,應該都會知道以上引入的函式作用為何,那由於 Cairo 並沒有繼承(Inheritance)只有引用(Import),所以最簡單的情況下,我們就使用 import
導入 OZ 幫我們寫好的 NFT 合約即可。
那如果大家想要在自己的 NFT 合約增加更多功能,可以到模組資料夾找到 openzeppelin,然後順著路徑 openzeppelin.token.erc721.presets.ERC721MintableBurnable.cairo
,就可以找到我們引用的合約了!
Deploy Contract on Goerli
有了合約之後我們就需要編譯他:
$ starknet-compile MyNFT.cairo \
--output MyNFT_compiled.json \
--abi MyNFT_abi.json
在 StarkNet 上部署(deploy)一個合約之前需要先宣告(declare)他,宣告之後我們就會將這個合約的編譯後資料存在鏈上,未來如果要部署「一模一樣的」合約,就可以直接用宣告得到的 Class Hash 進行部署即可,不用再自己重新編譯一次:
$ starknet declare --contract MyNFT_compiled.json
>
Declare transaction was sent.
Contract class hash: 0x4f583696b59f2c3b81becd117c14b95933d186111eff633d215e7a0e3fdb2d2
Transaction hash: ...
宣告完得到 Contract Class Hash 之後,我們準備部署這個合約,部署的指令如下,其中角括弧中代表的是我們需要放進去的參數。
$ starknet deploy --class_hash <Class_Hash> \
--inputs <name: felt> <symbol: felt> <owner: felt>
那由於在 Cairo 中並沒有 string
,只有 felt
(為 Cairo 最基本的單位,字串及數字都以 felt
表達),所以我們需要利用好用的 小工具 將 string
轉為 felt
,如果大家對 Nile 有興趣也可以使用以下指令:
$ pip install cairo-nile
$ python
>>> from nile.utils import str_to_felt
>>> str_to_felt('MyToken')
21806976760243566
>>> str_to_felt('MT')
19796
>>> str_to_felt('0x04112383E7deFC9A697aE0fE9452B22437f4c73A06D8330cEE4d147B1390FA26')
1839532911728319949577805561336351143590444159269087242333698784473075743270
>>> exit()
以上就萬事具備了,此時輸入指令就可以成功部署我們的 NFT 合約!
$ starknet deploy --class_hash 0x4f583696b59f2c3b81becd117c14b95933d186111eff633d215e7a0e3fdb2d2 \
--inputs 21806976760243566 19796 1839532911728319949577805561336351143590444159269087242333698784473075743270
>
Sending the transaction with max_fee: 0.000714 ETH (714047439973103 WEI).
Invoke transaction for contract deployment was sent.
Contract address: ...
Transaction hash: ...