如何以 Python Brownie 開發 DeFi 應用?- 發個 NFT 吧!Deploy 到 Ganache

本系列文將分享如何使用 Python 的 Brownie 框架開發 ERC-721,也就是 NFT。並且 deploy contract 到 Ethereum 的測試網路 Rinkeby,圖檔和 Metadata 則上傳到 IPFS,再透過測試版本的 OpenSea 來看看成果!

在上一篇 自己寫 NFT 吧!- 實現 ERC-721 – 以 Python Brownie 開發 我們使用 OpenZeppelin 的 ERC-721 樣板快速實現一個 NFT 的 smart contract。這篇我們要 deploy 到 Ganache 來實測與互動。

啟動 Local Ganache Network

如果 local 環境沒有啟動任何 Ganache,透過 Browine 跑 script 時會自動起一個「一次性」的 Ganache,跑玩後隨即關閉。如果我們需要做多次互動的測試,就需要 Ganache 一直活著,因此在 console 輸入

$ ganache-cli

Ganache 節點就會一直執行,監聽 8545 port,直到按下 ctrl + C 停止。

用 Python Deploy NFT Smart Contract

我們在專案目錄下的 scripts 資料夾中新增一個 deploy.py 的檔案,並貼入以下 script:

from brownie import network, accounts, KongLongNFT

def main():
    owner = accounts[0]
    kong_long = KongLongNFT.deploy({"from": owner})
    print(f"Contract deployed to {kong_long.address}")
    return kong_long

解釋一下上面的 script:

  • 首先由 browine 匯入套件中的 network、accounts、以及我們寫的 ERC-721 smart contract KongLongNFT。brownie 會幫我們把 Solidity 寫的 contract 包裝成同名 class,如此可用 python 與 smart contract 互動
  • 定義 main 函數。使用 brownie 跑 script 時如果沒有特別指定跑檔案中哪一個函數,預設會跑 main
  • 因為 Ganache 會幫我們建立十個測試用的 account,我們直接從 accounts 取用第一個當作執行 deploy 的帳戶
  • 接下來呼叫 KongLongNFT 中的方法 deploy 發布 smart contract。deploy 方法帶入 dictionary 傳入執行者的 account,回傳 deploy 後的 contract instance
  • 印出 contract 的地址

我們來實測看看,在 console 中輸入

$ brownie run script/deploy.py

會看到 shell 回應 transaction 的 hash,deploy 過程用的 gas,以及最後 contract 的位置

Brownie v1.17.2 - Python development framework for Ethereum

TokenProject is the active project.
Attached to local RPC client listening at '127.0.0.1:8545'...

Running 'scripts/deploy.py::main'...
Transaction sent: 0x28fe8322442356b1ea88fe80925973a53dcae1a651dfa01558607bedc4b4f00c
  Gas price: 0.0 gwei   Gas limit: 6721975   Nonce: 0
  KongLongNFT.constructor confirmed   Block: 1   Gas used: 1421519 (21.15%)
  KongLongNFT deployed at: 0x684f51E9A4Ec41DDaC067A9E03eECad3274F4FDd

Contract deployed to 0x684f51E9A4Ec41DDaC067A9E03eECad3274F4FDd

用 Python Mint 一個 NFT

deploy 完成,我們開一個新的檔案 mint.py,準備鑄造用的 script

from brownie import network, accounts, KongLongNFT

def main():
    owner = accounts[0]
    receiver = accounts[1]
    nft = KongLongNFT.at('0x684f51E9A4Ec41DDaC067A9E03eECad3274F4FDd')
    nft.mintToken(receiver, "ipfs://test-hash", {"from": owner})
    current_token_id = nft.getCurrentTokenId({"from": owner})
    print(f"mint successfully, token id: {current_token_id}")

解釋一下 mint 的 script

  • 這邊一樣以第一個 account 作為發起 mint 的帳戶,第二個 account 作為新鑄造的 NFT 擁有者
  • 透過 KongLongNFT.at() 方法,傳入剛剛 deploy 的 contract address,取得 contract instance
  • 呼叫 smart contract 中定義的 mintToken 方法。這個方法在 Solidity 中需要傳入兩個參數「address」和「tokenURI」,因此我們依序放入 receiver 和假的 tokenURI 字串。最後一樣要傳入發起 transaction 的帳戶位置
  • 透過 smart contract 中定義的 getCurrentTokenId 方法,取得目前最新一張 NFT 的 token id,並列印出來

立刻實測看看,在 console 中輸入

$ brownie run script/mint.py

一樣會印出 transaction 的基本資訊,以及目前的 token id 為 1

Transaction sent: 0xb1cef49556a6ee1628487198b593729da0624ebbfa1b19996da96f6dc46da348
  Gas price: 0.0 gwei   Gas limit: 6721975   Nonce: 2
  KongLongNFT.mintToken confirmed   Block: 3   Gas used: 112400 (1.67%)

<Transaction '0xb1cef49556a6ee1628487198b593729da0624ebbfa1b19996da96f6dc46da348'>

mint successfully, token id: 1

到這裡可以了解,「鑄造」其實就是寫入一筆 token_id 對 owner address ,以及 token_id 對 URI 的 mapping。

用 Python 測試呼叫 NFT Smart Contract

接下來我們要透過 Brownie 提供的 console 互動模式,用 Python 與 ERC-721 smart contract 做即時互動,驗證我們剛剛 deploy 和 mint 的資料是正確的。

使用以下指令進入 console:

$ brownie console
Brownie v1.17.2 - Python development framework for Ethereum

TokenProject is the active project.
/Users/jim/.local/pipx/venvs/eth-brownie/lib/python3.9/site-packages/brownie/network/main.py:44: BrownieEnvironmentWarning: Development network has a block height of 2
  warnings.warn(
Attached to local RPC client listening at '127.0.0.1:8545'...
Brownie environment is ready.
>>>

首先先來抓 NFT token 的 name 和 symbol:

>>> from brownie import KongLongNFT
>>> nft = KongLongNFT.at('0x684f51E9A4Ec41DDaC067A9E03eECad3274F4FDd')
>>> nft.name()
'KongLongNFT'
>>> nft.symbol()
'KLG'

我們要怎麼知道剛剛 mint 的 NFT 真的屬於 owner 呢?

>>> nft.ownerOf(1)
'0xEC63C0c7ed151b7eeC6E4e8571F0363B32359878'
>>> accounts[1]
<Account '0xEC63C0c7ed151b7eeC6E4e8571F0363B32359878'>

透過 ERC-721 定義的 ownerOf(uint256 tokenId) 方法,傳入愈查詢的 tokenId,回傳擁有者的地址。同時我們檢查 Ganache 預設的第二個地址,的確相同。
這邊的 accounts 跟之前 script 中從 brownie import 的是一樣的,在 console 中 brownie 會自動幫我們匯入,很貼心。

接下來檢查 tokenURI 是否與鑄造時傳入的一致:

>>> nft.tokenURI(1)
'ipfs://test-hash'

透過這幾個方法,就可以知道一個 NFT 的基本資訊、擁有者、以及該張 NFT 對應的 URI 位置,就能夠顯示在 Opensea 了……..嗎?還沒!我們還沒研究 tokenURI 指向的內容!下一篇我們會分享如何準備 meta data 讓 NFT 可以正確顯示在 Opensea 上!

延伸閱讀:
如何以 Python Brownie 開發 DeFi 應用?- 環境準備
自己寫 NFT 吧!- 實現 ERC-721 – 以 Python Brownie 開發
自己寫 NFT 吧!- 準備 Metadata – 以 Python Brownie 開發
自己寫 NFT 吧!- Deploy 到 Rinkeby testnet – 以 Python Brownie 開發

Written by J
雖然大學唸的是生物,但持著興趣與熱情自學,畢業後轉戰硬體工程師,與宅宅工程師們一起過著沒日沒夜的生活,做著台灣最薄的 intel 筆電,要與 macbook air 比拼。 離開後,憑著一股傻勁與朋友創業,再度轉戰軟體工程師,一手扛起前後端、雙平台 app 開發,過程中雖跌跌撞撞,卻也累計不少經驗。 可惜不是那 1% 的成功人士,於是加入其他成功人士的新創公司,專職開發後端。沒想到卻在採前人坑的過程中,拓寬了眼界,得到了深層的領悟。