手把手從零發行 StarkNet NFT(一)

ChiHaoLu
Taipei Ethereum Meetup
13 min readFeb 1, 2023

本次系列文除了介紹以 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:

  1. StarkNet and Cairo Documentation
  2. Cairo Community
  3. Cairo Documentation
  4. Cairo Resource Guide

Background

本系列文會希望大家已經熟悉怎麼在 Ethereum 發 NFT 了,內容會著重在 StarkNet 開發時比較不一樣的地方,同樣在 L1 開發時有的概念我就不會再做敘述。

在 StarkNet 中只有合約帳戶(CA, Contract Account)存在,沒有像 Ethereum 一樣有 EOA 與 CA 之分。同時若非透過錢包商(ArgentX, Bravvos)建立一個帳戶的過程將會非常繁瑣,因此希望大家仔細地跟著步驟做!

Please Note

  1. Cairo 1.0 上線後,當前合約在未來可能需要進行升級,但本文不會實作可升級合約,因此如果想在主網上線的人請多加小心!不過目前還有許多功能 Cairo 1.0 沒有呈現,所以先用此文章版本的 Cairo 中短期是蠻穩定的!
  2. 本文不包含在 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: ...

--

--

ChiHaoLu
Taipei Ethereum Meetup

Multitasking Master & Mr.MurMur | Blockchain Dev. @ imToken Labs | chihaolu.me | Advisory Services - https://forms.gle/mVGKQwPQEUP37fLYA