このチュートリアルでは、ブロックチェーン フロー上でデジタル コレクション (または NFT) を収集するための Web サイトを構築する方法を学びます。すべてを実現するために、スマート コントラクト言語 Cadence と React を使用します。また、Flow、その利点、使用できる楽しいツールについても学びます。
この記事を読み終えるまでに、Flow ブロックチェーン上に独自の分散アプリケーションを作成するために必要なツールと知識が得られるでしょう。
さっそく飛び込んでみましょう!
私たちはデジタル コレクション用のアプリケーションを構築しています。各収集品は非代替トークン (NFT) です。
これらすべてを機能させるために、Flow の NonFungibleToken Standard を使用します。これは、これらの特別なデジタル アイテムの管理に役立つ一連のルールです (イーサリアムの ERC-721 に似ています)。
開始する前に、システムに Flow CLI を必ずインストールしてください。まだ行っていない場合は、次のインストール手順に従ってください。
プロジェクトを開始する準備ができたら、まずコマンド フロー設定を入力します。
このコマンドは、プロジェクトの基盤を設定するために舞台裏で魔法を実行します。フォルダー システムが作成され、プロジェクトを構成するために flow.json というファイルがセットアップされ、すべてが整理されて準備が整っていることが確認されます。
プロジェクトには、 cadence
フォルダーとflow.json
ファイルが含まれます。 (flow.json ファイルはプロジェクトの構成ファイルであり、自動的に維持されます。)
Cadence フォルダーには次のものが含まれています。
Flow NFT Standardを使用するには、以下の手順に従ってください。
まず、 flow-collectibles-portal
フォルダーに移動し、 cadence
フォルダーを見つけます。次に、 contracts
フォルダーを開きます。新しいファイルを作成し、 NonFungibleToken.cdc
という名前を付けます。
ここで、NFT 標準が含まれているNonFungibleTokenという名前のリンクを開きます。そのファイルからすべてのコンテンツをコピーし、作成したばかりの新しいファイル (「NonFungibleToken.cdc」) に貼り付けます。
それでおしまい!プロジェクトの標準が正常に設定されました。
では、コードを書いてみましょう。
ただし、コーディングに入る前に、開発者はコードをどのように構造化するかについてのメンタル モデルを確立することが重要です。
トップレベルでは、コードベースは 3 つの主要コンポーネントで構成されます。
NFT: 各収集品は NFT として表されます。
コレクション:コレクションとは、特定のユーザーが所有するNFTのグループを指します。
グローバル関数と変数: これらは、スマート コントラクトのグローバル レベルで定義された関数と変数であり、特定のリソースには関連付けられません。
Collectibles.cdc
という名前の新しいファイルをcadence/contracts
内に作成します。ここにコードを記述していきます。
契約構造
import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ pub var totalSupply: UInt64 // other code will come here init(){ self.totalSupply = 0 } }
コードを 1 行ずつ分解してみましょう。
まず、いわゆる「NonFungibleToken」を含めることによって、NFT を構築していることを標準化する必要があります。これは、Flow によって構築された NFT 標準であり、各 NFT スマート コントラクトに含める必要がある次の機能セットを定義します。
インポートしたら、契約を作成しましょう。これを行うには、 pub contract [contract name]
を使用します。新しいコントラクトを作成するたびに、同じ構文を使用します。 contract name
には、契約を任意に呼び出すことができます。ここでは、これをCollectibles
と呼びましょう。
次に、コントラクトが NonFungibleToken の特定の機能とルールに従っていることを確認したいと思います。これを行うには、「:」を使用して NonFungibleToken インターフェースを追加します。
このように ( `pub contract Collectibles: NonFungibleToken{}`
)
すべてのコントラクトにはinit()
関数が必要です。これは、コントラクトが最初にデプロイされるときに呼び出されます。これは、Solidity がコンストラクターと呼ぶものに似ています。
ここで、データ型UInt64
のtotalSupply
というグローバル変数を作成しましょう。この変数は、収集品の合計を追跡します。
ここで、 totalSupply
値0
で初期化します。
それでおしまい!私たちはCollectibles
契約の基礎を確立しました。これからは、機能をさらに追加して、さらにエキサイティングなものにすることができます。
先に進む前に、コード スニペットをチェックして、Cadence で変数を定義する方法を理解してください。
次のコードをスマート コントラクトに追加します。
import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ // above code… pub resource NFT: NonFungibleToken.INFT{ pub let id: UInt64 pub var name: String pub var image: String init(_id:UInt64, _name:String, _image:String){ self.id = _id self.name = _name self.image = _image } } // init()... }
前に見たように、コントラクトはpub contract Collectibles: NonFungibleToken
で表される NFT 標準インターフェイスを実装しています。同様に、リソースもさまざまなリソース インターフェイスを実装できます。
そこで、 NonFungibleToken.INFT
インターフェイスを NFT リソースに追加しましょう。これにより、リソース内に id と呼ばれるパブリック プロパティの存在が必須になります。
NFT リソースで使用する変数は次のとおりです。
id:
NFTのIDを保持しますname:
NFT の名前。image:
NFTの画像URL。
変数を定義した後は、必ずinit()
関数で変数を初期化してください。
次に進んで、 Collection Resource
という名前の別のリソースを作成しましょう。
まず、 Collection Resources
どのように機能するかを理解する必要があります。
音楽ファイルと数枚の写真をラップトップに保存する必要がある場合、どうしますか?
通常は、ローカル ドライブ (D ドライブとしましょう) に移動し、 music
フォルダーとphotos
フォルダーを作成します。次に、音楽ファイルと写真ファイルをコピーして、宛先フォルダーに貼り付けます。
同様に、これが Flow 上のデジタル コレクションの仕組みです。
ラップトップがFlow Blockchain Account
、D ドライブがAccount Storage
、フォルダーがCollection
であると想像してください。
したがって、NFTを購入するためにプロジェクトと対話すると、プロジェクトはDドライブにフォルダーを作成するのと同様に、 account storage
にcollection
を作成します。 10 個の異なる NFT プロジェクトを操作すると、アカウント内に 10 個の異なるコレクションが存在することになります。
それは、自分だけのデジタル宝物を保管、整理するための個人的なスペースを持つようなものです。
import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ //Above code NFT Resource… // Collection Resource pub resource Collection{ } // Below code… }
各collection
NFT Resources
を保持するためのownedNFTs
変数があります。
pub resource Collection { pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} init(){ self.ownedNFTs <- {} } }
リソースインターフェース
Flow のresource
インターフェイスは、他のプログラミング言語のインターフェイスに似ています。これはリソースの最上位に位置し、それを実装するリソースがインターフェイスで定義されている必要な機能を備えていることを保証します。
また、リソース全体へのアクセスを制限したり、アクセス修飾子に関してリソース自体よりも制限を厳しくしたりするために使用することもできます。
NonFungibleToken
標準には、 INFT
、 Provider
、 Receiver
、 CollectionPublic
などのいくつかのリソース インターフェイスがあります。
これらの各インターフェイスには、それらを使用するリソースによって実装する必要がある特定の機能とフィールドがあります。
このコントラクトでは、 NonFungibleToken: Provider
、 Receiver
、およびCollectionPublic
を使用します。これらのインターフェイスは、 deposit
、 withdraw
、 borrowNFT
、 getIDs
の関数を定義します。それぞれについて詳しく説明していきます。
また、これらの関数から発行するいくつかのイベントを追加し、チュートリアルの後半で使用するいくつかの変数を宣言します。
pub contract Collectibles:NonFungibleToken{ // rest of the code… pub event ContractInitialized() pub event Withdraw(id: UInt64, from: Address?) pub event Deposit(id: UInt64, to: Address?) pub let CollectionStoragePath: StoragePath pub let CollectionPublicPath: PublicPath pub resource interface CollectionPublic{ pub fun deposit(token: @NonFungibleToken.NFT) pub fun getIDs(): [UInt64] pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT } pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} init(){ self.ownedNFTs <- {} } } }
撤回する
次に、インターフェースに必要なwithdraw()
関数を作成しましょう。
pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ // other code pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } init()... }
この機能を使用すると、NFT リソースをコレクションから移動できます。それであれば:
その後、呼び出し元はこのリソースを使用して、自分のアカウント ストレージ内に保存できます。
デポジット
ここで、 NonFungibleToken.Receiver
に必要なdeposit()
関数を使用します。
pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ // other code pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } pub fun deposit(token: @NonFungibleToken.NFT) { let id = token.id let oldToken <- self.ownedNFTs[id] <-token destroy oldToken emit Deposit(id: id, to: self.owner?.address) } init()... }
借りてIDを取得
ここで、 NonFungibleToken.CollectionPublic: borrowNFT()
とgetID()
に焦点を当てましょう。
pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ // other code pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } pub fun deposit(token: @NonFungibleToken.NFT) { let id = token.id let oldToken <- self.ownedNFTs[id] <-token destroy oldToken emit Deposit(id: id, to: self.owner?.address) } pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { if self.ownedNFTs[id] != nil { return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! } panic("NFT not found in collection.") } pub fun getIDs(): [UInt64]{ return self.ownedNFTs.keys } init()... }
デストラクター
コレクション リソースに最後に必要なのはデストラクターです。
destroy (){ destroy self.ownedNFTs }
Collection リソースには他のリソース (NFT リソース) が含まれるため、デストラクターを指定する必要があります。デストラクターは、オブジェクトが破棄されるときに実行されます。これにより、親リソースが破壊されたときにリソースが「ホームレス」のままにされることがなくなります。 NFT リソースには他のリソースが含まれていないため、デストラクターは必要ありません。
完全なコレクション リソースのソース コードを見てみましょう。
import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ pub var totalSupply: UInt64 pub resource NFT: NonFungibleToken.INFT{ pub let id: UInt64 pub var name: String pub var image: String init(_id:UInt64, _name:String, _image:String){ self.id = _id self.name = _name self.image = _image } } pub resource interface CollectionPublic{ pub fun deposit(token: @NonFungibleToken.NFT) pub fun getIDs(): [UInt64] pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT } pub event ContractInitialized() pub event Withdraw(id: UInt64, from: Address?) pub event Deposit(id: UInt64, to: Address?) pub let CollectionStoragePath: StoragePath pub let CollectionPublicPath: PublicPath pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} init(){ self.ownedNFTs <- {} } destroy (){ destroy self.ownedNFTs } pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } pub fun deposit(token: @NonFungibleToken.NFT) { let id = token.id let oldToken <- self.ownedNFTs[id] <-token destroy oldToken emit Deposit(id: id, to: self.owner?.address) } pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { if self.ownedNFTs[id] != nil { return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! } panic("NFT not found in collection.") } pub fun getIDs(): [UInt64]{ return self.ownedNFTs.keys } } init(){ self.CollectionPublicPath = /public/NFTCollection self.CollectionStoragePath = /storage/NFTCollection self.totalSupply = 0 emit ContractInitialized() } }
これで、すべてのリソースが完成しました。次に、グローバル関数を見ていきます。
グローバル関数は、スマート コントラクトのグローバル レベルで定義される関数であり、リソースの一部ではないことを意味します。これらは一般からアクセスして呼び出すことができ、スマート コントラクトの中核機能を一般に公開します。
createEmptyCollection : この関数は、空のCollectibles.Collection
を呼び出し元アカウント ストレージに初期化します。
checkCollection : この便利な関数は、アカウントにcollection
リソースが既に存在するかどうかを確認するのに役立ちます。
mintNFT : この機能は、誰でも NFT を作成できるため、非常にクールです。
// pub resource Collection… pub fun createEmptyCollection(): @Collection{ return <- create Collection() } pub fun checkCollection(_addr: Address): Bool{ return getAccount(_addr) .capabilities.get<&{Collectibles.CollectionPublic}> (Collectibles.CollectionPublicPath)! .check() } pub fun mintNFT(name:String, image:String): @NFT{ Collectibles.totalSupply = Collectibles.totalSupply + 1 let nftId = Collectibles.totalSupply var newNFT <- create NFT(_id:nftId, _name:name, _image:image) return <- newNFT } init()...
そしてついに、すべてが整ったので、スマート コントラクトの作成が完了しました。ここで最終的なコードを見てください。
次に、ユーザーが Flow ブロックチェーンにデプロイされたスマート コントラクトとどのように対話するかを見てみましょう。
Flow ブロックチェーンを操作するには 2 つの手順があります。
トランザクションは、フロー状態を更新するためにスマート コントラクトと対話する一連の命令を含む、暗号化された署名付きデータです。簡単に言えば、これはブロックチェーン上のデータを変更する関数呼び出しのようなものです。通常、トランザクションにはある程度のコストがかかりますが、そのコストは使用しているブロックチェーンによって異なります。
トランザクションには、複数のオプションのフェーズ ( prepare
、 pre
、 execute
フェーズ、およびpost
フェーズ) が含まれます。
詳細については、トランザクションに関する Cadence リファレンス ドキュメントを参照してください。各フェーズには目的があります。最も重要な 2 つのフェーズは、 prepare
とexecute
です。
Prepare Phase
: このフェーズは、署名者のアカウント内のデータと情報にアクセスするために使用されます (AuthAccount タイプによって許可されます)。
Execute Phase
: このフェーズはアクションを実行するために使用されます。
次に、プロジェクトのトランザクションを作成しましょう。
以下の手順に従って、プロジェクト フォルダーにトランザクションを作成します。
まず、プロジェクト フォルダーに移動し、 cadence
フォルダーを開きます。その中にtransaction
フォルダーを開き、 Create_Collection.cdc
およびmint_nft.cdc
という名前の新しいファイルを作成します。
import Collectibles from "../contracts/Collectibles.cdc" transaction { prepare(signer: AuthAccount) { if signer.borrow<&Collectibles.Collection>(from: Collectibles.CollectionStoragePath) == nil { let collection <- Collectibles.createEmptyCollection() signer.save(<-collection, to: Collectibles.CollectionStoragePath) let cap = signer.capabilities.storage.issue<&{Collectibles.CollectionPublic}>(Collectibles.CollectionStoragePath) signer.capabilities.publish( cap, at: Collectibles.CollectionPublicPath) } } }
このコードを 1 行ずつ分解してみましょう。
このトランザクションは、Collectibles スマート コントラクトと対話します。次に、指定されたストレージ パスCollectibles.CollectionStoragePath
から Collection リソースへの参照を借用することで、送信者 (署名者) のアカウントに Collection リソースが保存されているかどうかを確認します。参照が nil の場合、署名者がまだコレクションを持っていないことを意味します。
署名者がコレクションを持たない場合は、 createEmptyCollection()
関数を呼び出して空のコレクションを作成します。
空のコレクションを作成した後、それを指定されたストレージ パスCollectibles.CollectionStoragePath
の下の署名者のアカウントに配置します。
これにより、 link()
を使用して、署名者のアカウントと新しく作成されたコレクションの間にリンクが確立されます。
import NonFungibleToken from "../contracts/NonFungibleToken.cdc" import Collectibles from "../contracts/Collectibles.cdc" transaction(name:String, image:String){ let receiverCollectionRef: &{NonFungibleToken.CollectionPublic} prepare(signer:AuthAccount){ self.receiverCollectionRef = signer.borrow<&Collectibles.Collection>(from: Collectibles.CollectionStoragePath) ?? panic("could not borrow Collection reference") } execute{ let nft <- Collectibles.mintNFT(name:name, image:image) self.receiverCollectionRef.deposit(token: <-nft) } }
このコードを 1 行ずつ分解してみましょう。
まず、 NonFungibleToken
とCollectibles contract
をインポートします。
transaction(name: String, image: String)
この行は、新しいトランザクションを定義します。これは 2 つの引数、name と image を受け取り、どちらも String 型です。これらの引数は、鋳造される NFT の名前とイメージを渡すために使用されます。
let receiverCollectionRef: &{NonFungibleToken.CollectionPublic}
この行は、新しい変数receiverCollectionRef.
これは、 NonFungibleToken.CollectionPublic
型の NFT のパブリック コレクションへの参照です。この参照は、新しく鋳造された NFT を預けるコレクションと対話するために使用されます。
prepare(signer: AuthAccount)
この行は、トランザクションの前に実行される準備ブロックを開始します。これは、 AuthAccount
型の引数署名者を受け取ります。 AuthAccount
トランザクションの署名者のアカウントを表します。
これは、準備ブロック内の署名者のストレージからCollectibles.Collection
への参照を借用します。これは、borrow 関数を使用してコレクションへの参照にアクセスし、それをreceiverCollectionRef
変数に保存します。
参照が見つからない場合 (たとえば、コレクションが署名者のストレージに存在しない場合)、「コレクション参照を借用できませんでした」というエラー メッセージがスローされます。
execute
ブロックには、トランザクションの主な実行ロジックが含まれています。このブロック内のコードは、 prepare
ブロックが正常に完了した後に実行されます。
nft <- Collectibles.mintNFT(_name: name, image: image)
execute
ブロック内で、この行は指定された name および image 引数を使用してCollectibles
コントラクトからmintNFT
関数を呼び出します。この関数は、指定された名前とイメージで新しい NFT を作成することが期待されます。 <-
記号は、NFT が移動可能なオブジェクト (リソース) として受信されていることを示します。
self.receiverCollectionRef.deposit(token: <-nft)
この行は、新しく作成された NFT を指定されたコレクションにデポジットします。これは、 receiverCollectionRef
のデポジット関数を使用して、NFT の所有権をトランザクションの実行アカウントからコレクションに移します。ここの<-
記号は、NFT がdeposit
プロセス中にリソースとして移動されていることも示します。
スクリプトを使用して、ブロックチェーンからデータを表示または読み取ります。スクリプトは無料で、署名の必要はありません。
以下の手順に従って、プロジェクト フォルダーにスクリプトを作成します。
まず、プロジェクト フォルダーに移動し、 cadence
フォルダーを開きます。その中にあるscript
フォルダーを開き、 view_nft.cdc
という名前の新しいファイルを作成します。
import NonFungibleToken from "../contracts/NonFungibleToken.cdc" import Collectibles from "../contracts/Collectibles.cdc" pub fun main(user: Address, id: UInt64): &NonFungibleToken.NFT? { let collectionCap= getAccount(user).capabilities .get<&{Collectibles.CollectionPublic}>(/public/NFTCollection) ?? panic("This public capability does not exist.") let collectionRef = collectionCap.borrow()! return collectionRef.borrowNFT(id: id) }
このコードを 1 行ずつ分解してみましょう。
まず、 NonFungibleToken
とCollectibles
コントラクトをインポートします。
pub fun main(acctAddress: Address, id: UInt64): &NonFungibleToken.NFT?
この行は、main という名前のパブリック関数であるスクリプトのエントリ ポイントを定義します。この関数は 2 つのパラメータを取ります。
acctAddress
: Flow ブロックチェーン上のアカウントのアドレスを表すAddress
タイプのパラメーター。
id
: コレクション内の NFT の一意の識別子を表すUInt64
タイプのパラメーター。
次に、 getCapability
を使用して、指定されたacctAddress
のCollectibles.Collection
機能を取得します。ケイパビリティは、その機能とデータへのアクセスを許可するリソースへの参照です。この場合、 Collectibles.Collection
リソース タイプの機能を取得しています。
次に、 borrowNFT
関数を使用してcollectionRef
から NFT を借用します。 borrowNFT
関数は、コレクション内の NFT の一意の識別子であるid
パラメーターを受け取ります。機能のborrow
関数を使用すると、リソース データを読み取ることができます。
最後に、関数から NFT を返します。
次に、スマート コントラクトを Flow テストネットにデプロイします。
1. Flowアカウントを設定します。
ターミナルで次のコマンドを実行して、フロー アカウントを生成します。
flow keys generate
公開キーと秘密キーを必ず書き留めてください。
次に向かうのは、
指定された入力フィールドに公開キーを貼り付けます。
署名とハッシュ アルゴリズムをデフォルトに設定したままにします。
キャプチャを完了します。
「アカウントの作成」をクリックします。
アカウントを設定した後、1,000 個のテスト フロー トークンを含む新しいフロー アドレスを含むダイアログを受け取ります。今後使用できるようにアドレスをコピーします。
2. プロジェクトを構成します。
次に、プロジェクトを構成しましょう。最初にプロジェクトをセットアップすると、 flow.json
ファイルが作成されました。
これは Flow CLI の構成ファイルであり、Flow CLI が実行できるアクションの構成を定義します。これは、イーサリアムのhardhat.config.js
とほぼ同等であると考えてください。
次に、コード エディターを開き、以下のコードをコピーしてflow.json
ファイルに貼り付けます。
{ "contracts": { "Collectibles": "./cadence/contracts/Collectibles.cdc", "NonFungibleToken": { "source": "./cadence/contracts/NonFungibleToken.cdc", "aliases": { "testnet": "0x631e88ae7f1d7c20" } } }, "networks": { "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { "testnet-account": { "address": "ENTER YOUR ADDRESS FROM FAUCET HERE", "key": "ENTER YOUR GENERATED PRIVATE KEY HERE" } }, "deployments": { "testnet": { "testnet-account": [ "Collectibles" ] } } }
生成された秘密キーをコード内の場所 (キー: 「ENTER YOUR GENERATED PRIVATE KEY HERE」) に貼り付けます。
次に、テストネット上でコードを実行します。ターミナルに移動し、次のコードを実行します。
flow project deploy --network testnet
5. 確認を待ちます。
トランザクションを送信すると、トランザクション ID が届きます。テストネット上でトランザクションが確認され、スマート コントラクトが正常にデプロイされたことが示されるまで待ちます。
デプロイされた契約をここで確認してください。
GitHubで完全なコードを確認してください。
おめでとう!これで、Flow ブロックチェーン上に収集品ポータルが構築され、テストネットにデプロイされました。次は何ですか?これで、このシリーズのパート 2 で説明するフロントエンドの構築に取り組むことができます。
本当に素晴らしい一日をお過ごしください!
ここでも公開されています