2

手把手從零發行 StarkNet NFT(二). 前一篇文章中我們已經部署一個 ERC-721 合約在 S...

 1 year ago
source link: https://medium.com/taipei-ethereum-meetup/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%BE%9E%E9%9B%B6%E7%99%BC%E8%A1%8C-starknet-nft-%E4%BA%8C-bfa3a10e63f7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

前一篇文章中我們已經部署一個 ERC-721 合約在 StarkNet Goerli 了,接下來我們的目標有以下:

  1. 建置一個 React 框架的前端 Mint Dapp,讓其他人可以透過這個網站連接錢包並且 mint NFT。
  2. 讓 Token 在 mint 之後能夠顯示在 StarkNet 的 NFT Marketplace 中。
  3. 擁有者透過與合約互動來設定 tokenURI(from IPFS),以此顯示 NFT 資訊與圖片。

Author: ChiHaoLu(chihaolu.eth) @ imToken Labs

0*ezN_A3nxDRsMlstM.png

Minting Dapp

我們的目標是讓使用者可以進到我們的 Minting Dapp,然後串連錢包,Mint NFT。在建立 React 之後首先要做的是 import 套件:

import React, { useState } from "react";
import {
connect,
} from "@argent/get-starknet"
import {
Contract,
Provider
} from "starknet"

我們會需要使用 @argent/get-starknet 來串連錢包(包含 ArgentX 以及 Bravvos 兩種皆可使用),以及 starknet.js 來與合約互動(鑄造)。

前端主要內容如下:

const Web3Button = () => {

const [msg, setMsg] = useState("Login") // 按鈕上的文字
const [wallet, setWallet] = useState("") // 當前登入者的錢包物件

// 連接錢包
const handleConnect = async () => {
// ...
}

// 鑄造 NFT
const sendMintTx = async () => {
// ...
}

// 按鈕物件
const Button = () => {
return (
// ...
)
}

return (
<>
<div >
<Button />
</div>
</>
);
};
export default Web3Button;

大概知道框架之後先實作串連錢包的功能,使用 connect() 來建立連接 StarkNet 網路的物件,並且使用 member function .enable() 來要求當前瀏覽器上的 StarkNet Wallet 進行連接。完成後將回傳物件存入 wallet 並且更新按鈕顯示資訊即可。

const handleConnect = async () => {
const starknet = await connect()
const account = await starknet.enable();
console.log("Connect the wallet: ", account)
setWallet(account || "")
setMsg("Mint")
}

接下來我們得給按鈕一個函式為觸發後能夠送出 ERC721 中的 mint() 函式。starknet.js 在使用上與 ethers.js 很像。

比較特別的在於我們要先在宣告 Provider 物件時需要告訴它我們要使用 sequencer 的哪個 network('goerli-alpha' or 'mainnet-alpha')。

宣告完 Provider 物件後的步驟是:

  1. 宣告合約物件(new Contract(...)
  2. 連接錢包物件(ERC721Contract.connect(wallet)
  3. 送出交易(await ERC721Contract.mint())。
const sendMintTx = async () => {
const contractABI = [] // 合約編譯完之後得到的 ABI
const contractADDR = "" // 合約佈署之後得到的地址
const starknetProvider = new Provider({
sequencer: {
network: 'goerli-alpha' // 'mainnet-alpha'
}
})
const ERC721Contract = new Contract(contractABI, contractADDR, starknetProvider);
ERC721Contract.connect(wallet);
const { transaction_hash: TxHash } = await ERC721Contract.mint();
await starknetProvider.waitForTransaction(TxHash);
}

Sequencer 是類似於礦工的存在,會挑選交易並且打包送出,但與大家可能使用過的 RPCProvider 是不同的存在,有興趣可以再去 Starknet.js 的相關文件中了解。

有了以上的程式碼之後就可以透過 msg === "Login" 的判斷來決定當前按鈕要顯示何種內容,以及觸發之後要執行哪個函式。

const Button = () => {
return (
msg === "Login" ?
<button
className="bg-gradient-to-r from-thOrange to-thOrange py-2 px-6 text-black rounded-lg duration-300 hover:scale-110"
onClick={() => {
handleConnect()
}}
>
Login
</button >
:
<button
className="bg-gradient-to-r from-thOrange to-thOrange py-2 px-6 text-black rounded-lg duration-300 hover:scale-110"
onClick={() => {
sendMintTx()
}}
>
Mint
</button >
)
}

StarkNet NFT Marketplace

目前在 StarkNet 上的 NFT Marketplace 中有以下兩個:

其中兩者都有支援測試網,只是不好找到,連結分別為 Mint Square GoerliAspect Goerli。如果現在就去我們佈署的地址查看的話,會發現 NFT 有 Mint 出來但是沒有任何圖片、Token 資訊和 Collection 資訊,想要顯示以上內容就需要設定 TokenURI 和 ContractURI。

但因為詳細解釋這些函式的撰寫會耗盡大部分篇幅,因此這邊就做到提點的作用說明用途和流程,此部分有三者需要注意:

ContractURI

所謂的 ContractURI 指的是我們需要在合約中實作一個名稱為 contractURI 的 View Function,呼叫之後能夠回傳一個 IPFS 網址。但此函式並沒有在 OpenZeppelin 中實作,因此如果大家想要在 Collection 上呈現自己的 NFT 名稱、圖片、橫幅等資訊,得自己在合約中加上以下程式碼:

// ipfs://...
@view
func contractURI() -> (
contractURI_len: felt, contractURI: felt*
) {
return (<Your_IPFS_Link_Len>, new ( 105, 112, 102, 115, 58, 47, 47, ... ));
}

回傳的兩個值分別為此網址的長度,以及 IPFS 網址轉為 ASCII 的陣列,可以發現 ipfs:// 這七個字元在 Cairo 中代表 [105, 112, 102, 115, 58, 47, 47]

此 IPFS 指向處存著 Contract 的 Information。內容如下:

{
"name": "<Your_NFT_Name>",
"description": "<Your_NFT_Statement>",
"image": "https://ipfs.io/ipfs/...",
"banner_image_url": "https://ipfs.io/ipfs/...",
"external_link": "<Your_NFT_Website_or_SocialMedia>",
"seller_fee_basis_points": 1000,
"fee_recipient": "<Your_Fee_Recipient_Address>"
}

不實作這個部分的結果是在 Marketplace 上看不見 Collection 資訊,但 NFT 本身仍然可以正常呈現。

TokenURI

StarkNet 上的 TokenURI 和 Ethereum 上 NFT 的意義一模一樣,都是有一個 baseTokenURI 再加上 tokenID 來取得該 Token 的 MetaData IPFS 網址。

我們引用的 OpenZeppelin ERC721 合約 已經有替我們完成這個部分:

@view
func tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
tokenId: Uint256
) -> (tokenURI: felt) {
let (tokenURI: felt) = ERC721.token_uri(tokenId);
return (tokenURI=tokenURI);
}

@external
func setTokenURI{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
tokenId: Uint256, tokenURI: felt
) {
Ownable.assert_only_owner();
ERC721._set_token_uri(tokenId, tokenURI);
return ();
}

RoyaltyInfo

這個部份是當我們發行的 NFT 在 Marketplace 上轉手時,我們都可以收取手續費(即便買賣雙方不是我們),此時需要在合約中實作以下函式,讓 Marketplace 呼叫,以此知道要把這筆轉手的 Royalty Fee ETH 匯給誰。

@view
func royaltyInfo{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
tokenId: Uint256, salePrice: Uint256
) -> (receiver: felt, royaltyAmount: Uint256) {
// ...
return (owner, royaltyAmount);
}

不實作這個部分的結果是我們的 NFT 在 Marketplace 上被他人轉手時可能無法正確收取到 royalty fee。

Setting TokenURI

如果有成功一路做到這裡的大家,即便不實作 ContractURI 以及 RoyaltyInfo,應該也會發現我們的 NFT 還沒有圖片跟資訊,那是因為我們還沒有設定 TokenURI 來抓到該 Token 的 MetaData 以及其中的 Images Link。

我自己測是起來 MetaData 的格式基本上和 Opensea 定義的差不多,大概如下:

{
"name": "<Your_NFT_Name> #<TokenID>",
"description": "<Your_NFT_Statement>",
"image": "ipfs://.../<TokenID>.png",
"attributes": [
{
"trait_type": "<Attributes1_Name>",
"value": "<Attributes1_Value>"
},
{
"trait_type": "<Attributes2_Name>",
"value": "<Attributes2_Value>"
},
{
"trait_type": "<Attributes3_Name>",
"value": "<Attributes3_Value>"
},
]
}

我自己認為設定 TokenURI 最好的方法是 Starknet CLI 的 invoke Command:

$ starknet invoke \
--address <MY_NFT_CONTRACT_ADDRESS> \
--abi MyNFT_abi.json \
--function setTokenURI \
--inputs \
<TOKEN_ID_LOW> <TOKEN_ID_HIGH> \
105 112 102 115 58 47 47 ... 47 ...
>
Invoke transaction was sent.
Contract address: <MY_NFT_CONTRACT_ADDRESS>
Transaction hash: ...

這邊需要注意在 --input 的後面我們需要傳入兩個參數,一個是 Uint256 型態的 tokenId,一個是 felt 型態的 tokenURI。關於 Uint256 我們可以透過前篇介紹過的 小工具 進行轉換。

舉例來說我們想要將 TokenID 為 456 的 TokenURI 改為 ipfs://abcdefg/456.json 這個存有該 Token MetaData 的 IPFS 地址,Input Filed 應該要為:

--inputs \
456 0 \
105 112 102 115 58 47 47 \
97 98 99 100 101 102 103 \
47 \
52 53 54 46 106 115 111 110

想要快速從字串轉換到 ASCII 可使用以下 python 程式碼:

$ python
>>> s = "ipfs://abcdefg/456.json"
>>> [ord(c) for c in s]
[105, 112, 102, 115, 58, 47, 47, 97, 98, 99, 100, 101, 102, 103, 47, 52, 53, 54, 46, 106, 115, 111, 110]

Closing

使用 Cairo 撰寫 StarkNet Contract 目前還不是非常友善,許多工具都還在實驗階段,且隨時可能大改版(一切重學),希望能夠透過這兩篇文章讓大家最基本地體會跟理解在 StarkNet 上開發的流程。

Special thanks to

, for reviewing these series.

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK