ちょっと高度な Hello World !
Hello World! スマートコントラクトのデプロイ方法
バックエンドで実行するためにはウォレットが作成したアドレスでは上手くいきません。ウォレットが作成した秘密鍵はウォレットにアクセスすることでしか取得できないからです。これはバックエンドでトランザクションを行いたい時に.exeなどの実行ファイルだけでは実行できないことを意味します。
そこで、自分たちで秘密鍵とブロックチェーンアドレスを作成します。そしてそれらを使ってバックエンドからトランザクションを実行します。
まず、その前にFlow CLIというツールで flow init を実行したら何ができているのかを紹介します。
{
"contracts": {
},
"dependencies": {
"Burner": {
"source": "mainnet://f233dcee88fe0abe.Burner",
"hash": "71af18e227984cd434a3ad00bb2f3618b76482842bae920ee55662c37c8bf331",
"block_height": 149816757,
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "f233dcee88fe0abe",
"testnet": "9a0766d93b6608b7"
}
},
"FlowToken": {
"source": "mainnet://1654653399040a61.FlowToken",
"hash": "f82389e2412624ffa439836b00b42e6605b0c00802a4e485bc95b8930a7eac38",
"block_height": 149816757,
"aliases": {
"emulator": "0ae53cb6e3f42a79",
"mainnet": "1654653399040a61",
"testnet": "7e60df042a9c0868"
}
},
"FungibleToken": {
"source": "mainnet://f233dcee88fe0abe.FungibleToken",
"hash": "3b04b3191e0c5d4d9ee3ce9c671b11985d29bf2732a5721b526cc8523c536f8f",
"block_height": 149816757,
"aliases": {
"emulator": "ee82856bf20e2aa6",
"mainnet": "f233dcee88fe0abe",
"testnet": "9a0766d93b6608b7"
}
},
"FungibleTokenMetadataViews": {
"source": "mainnet://f233dcee88fe0abe.FungibleTokenMetadataViews",
"hash": "70477f80fd7678466c224507e9689f68f72a9e697128d5ea54d19961ec856b3c",
"block_height": 149816757,
"aliases": {
"emulator": "ee82856bf20e2aa6",
"mainnet": "f233dcee88fe0abe",
"testnet": "9a0766d93b6608b7"
}
},
"MetadataViews": {
"source": "mainnet://1d7e57aa55817448.MetadataViews",
"hash": "b290b7906d901882b4b62e596225fb2f10defb5eaaab4a09368f3aee0e9c18b1",
"block_height": 149816757,
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "1d7e57aa55817448",
"testnet": "631e88ae7f1d7c20"
}
},
"NonFungibleToken": {
"source": "mainnet://1d7e57aa55817448.NonFungibleToken",
"hash": "a258de1abddcdb50afc929e74aca87161d0083588f6abf2b369672e64cf4a403",
"block_height": 149816757,
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "1d7e57aa55817448",
"testnet": "631e88ae7f1d7c20"
}
},
"ViewResolver": {
"source": "mainnet://1d7e57aa55817448.ViewResolver",
"hash": "374a1994046bac9f6228b4843cb32393ef40554df9bd9907a702d098a2987bde",
"block_height": 149816757,
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "1d7e57aa55817448",
"testnet": "631e88ae7f1d7c20"
}
}
},
"networks": {
"emulator": "127.0.0.1:3569",
"mainnet": "access.mainnet.nodes.onflow.org:9000",
"testnet": "access.devnet.nodes.onflow.org:9000"
},
"accounts": {
"emulator-account": {
"address": "f8d6e0586b0a20c7",
"key": {
"type": "file",
"location": "emulator-account.pkey"
}
}
},
"deployments": {
"mainnet": {
},
"testnet": {
}
}
}
こういう感じのJSONがflow.jsonファイルに作成されています。このflow.jsonがスマートコントラクトをデプロイする時の全ての参照先になります。
最初は"accounts"に"emulator-account"だけあって、"testnet-account"や"mainnet-account"がありません。また、"deployments"が最初はflow init実行した直後は無いですが、ここは今後必要になってきます。
アカウントを作成する
これはFlow CLIを使って作成します。
flow accounts create
実行すると、以下のような出力がされます。
Address 0x348175517cb1c79e
Balance 0.00200000
Keys 1
Key 0 Public Key e21fd469bf39be5718fbd16f6444c1c0cef51ec40db865df8a9dc90c443b68946aa7a63d6083666109864511b6c8d589a4cc7202b3257c34c3d1a7885c3de880
Weight 1000
Signature Algorithm ECDSA_P256
Hash Algorithm SHA3_256
Revoked false
Sequence Number 0
Index 0
Contracts Deployed: 0
ECDSA_P256アルゴリズムでPrivate KeyとPublic Key、ブロックチェーンアドレス が一度に作成されます。
flow.jsonを見てみます。
"accounts": {
"emulator-account": {
"address": "f8d6e0586b0a20c7",
"key": {
"type": "file",
"location": "emulator-account.pkey"
}
},
"testnet-account": {
"address": "348175517cb1c79e",
"key": {
"type": "file",
"location": "testnet-account.pkey"
}
}
},
このように作成されたブロックチェーンアドレス(348175517cb1c79e)がすでにセットされています。Public Keyは見当たりませんが、実はアドレスを生成したときにそこに引数として渡されています。Scanサイト(FlowScan)でこのアドレスを検索するとそこでPublic Keyを見つけることができます。
testnet-account.pkeyというファイルがflow.jsonと同じ階層に作成されているので、それを開いてみるとPrivate Keyが見つかります。
同様にメインネットのアカウントも作成します。
flow accounts create
対象のネットワークにmainnetを選択するとflow.jsonは以下のようになります。
"accounts": {
"emulator-account": {
"address": "f8d6e0586b0a20c7",
"key": {
"type": "file",
"location": "emulator-account.pkey"
}
},
"mainnet-account": {
"address": "deef3052ad5a0289",
"key": {
"type": "file",
"location": "mainnet-account.pkey"
}
},
"testnet-account": {
"address": "348175517cb1c79e",
"key": {
"type": "file",
"location": "testnet-account.pkey"
}
}
}
スマートコントラクトをデプロイする
この生成したアカウントにスマートコントラクトをデプロイすることで、好きな時にコントラクトメソッドをトランザクションで実行することができます。Private Keyがウォレットにではなく、testnet-account.pkeyやmainnet-account.pkeyにあるのでこれを.envファイルに貼り付けることで、どこからでもトランザクションを実行することができます。
flow.jsonファイルと同じ階層にcadence/contractsフォルダがすでにあります。そこにHelloWorld.cdcファイルを作成します。そこに以下を貼り付けて保存します。
access(all) contract HelloWorld {
// Declare a public field of type String.
//
// All fields must be initialized in the init() function.
access(all) var greeting: String
// Public function that sets our friendly greeting!
// In your own applications you may want to tighten up this access control.
access(all) fun changeGreeting(newGreeting: String) {
self.greeting = newGreeting
}
// Public function that returns our friendly greeting!
access(all) fun hello(): String {
return self.greeting
}
// The init() function is required if the contract contains any fields.
init() {
self.greeting = "Hello, World!"
}
}
ではデプロイしましょう。
flow.jsonを開きます。
"contracts": {
"HelloWorld": {
"source": "./cadence/contracts/HelloWorld.cdc",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "deef3052ad5a0289",
"testnet": "348175517cb1c79e"
}
}
},
"emulator"のアドレスは常に"f8d6e0586b0a20c7"です。これは変わりません。
"HelloWorld"の部分はコントラクト名と合わせます。"testnet"と"mainnet"はこれからデプロイするアドレスです。先ほど作成ずみのアドレスを指定します。
次に以下を記入します。
"deployments": {
"mainnet": {
"mainnet-account": [
"HelloWorld"
]
},
"testnet": {
"testnet-account": [
"HelloWorld"
]
}
}
"mainnet-account"と"testnet-account"の部分はflow accounts createコマンドを実行したときに指定した名前です。そのため、人によっては"mainnet-acc"だったり、"testnet-acc"だったりします。入力したアカウント名を入れます。その下に配列を作り、"HelloWorld"をセットします。(一度に複数のコントラクトをデプロイできます)
そして以下を実行します。
flow project deploy --network testnet
おそらく失敗します。Σ\( ̄д ̄;)ナンデヤネン
出力結果はこう出ます。
❌ Failed to deploy contract HelloWorld: failed to deploy contract HelloWorld: [Error Code: 1103] error caused by: 1 error occurred:
* storage limit check failed: [Error Code: 1103] The account with address (348175517cb1c79e) uses 1930 bytes of storage which is over its capacity (0 bytes). Capacity can be increased by adding FLOW tokens to the account.
このstorage limit check failed: [Error Code: 1103]というのは開発していると必ず1回は見ることになります。これはトークンを持っていないよ、ということなので、トークンを補充します。そうすればそこからトランザクションフィーが支払われて実行が成功します。
https://faucet.flow.com/fund-account
にアクセスしてブロックチェーンアドレスを入力するとテストネット用のトークンを100,000もらえます。
再度実行するとこのように表示されるはずです。
Deploying 1 contracts for accounts: testnet-account
HelloWorld -> 0x348175517cb1c79e (a811542caa8f4ce5e897669d887b7b7c12c741e2c798e0f920b5440ab6e2f83e)
🎉 All contracts deployed successfully
更新する時は以下のコマンドを実行します。
flow project deploy --network testnet --update
スマートコントラクトのフィールドを変更していない限り更新に成功します。(フィールド: initの中で初期化が必要なプロパティ)
Hello, World!の実行
トランザクションの実行
/cadence/transactionsの下にChangeGreeting.cdcファイルを生成します。
そして以下を貼り付けて保存します。
import "HelloWorld"
transaction(greeting: String) {
prepare(acct: &Account) {
log(acct.address)
}
execute {
HelloWorld.changeGreeting(newGreeting: greeting)
}
}
以下のコマンドを実行します。
flow transactions send ./cadence/transactions/ChangeGreeting.cdc "Hello" --network testnet --signer testnet-account
以下のように結果が返されます。
Go言語でもNode.jsでもできます。もちろんOS実行をするのであればFlow CLIはLinuxでもインストールできますので、Pythonでも呼び出して実行することができます。
スクリプトの実行
/cadence/scriptsの下にGetGreeting.cdcというファイルを作成し、以下を貼り付けて保存します。
import "HelloWorld"
access(all) fun main(): String {
return HelloWorld.hello()
}
以下を実行します。
flow scripts execute ./cadence/scripts/GetGreeting.cdc --network testnet
以下が返されます。
Result: "Hello"
Go言語、Node.jsで実行する時はGeminiなどの生成AIに聞きながら行うと自分で調べるより早く実装できたりします。
秘密鍵は.envファイル等に入れて決してGithubにアップロードしないでください。Private Keyさえあれば以下を実行されてあっという間に全額抜き取られます。
const fcl = require("@onflow/fcl");
const { SHA3 } = require("sha3");
var EC = require("elliptic").ec;
var ec = new EC("p256");
require("dotenv").config();
fcl
.config()
.put("flow.network", "mainnet")
.put("accessNode.api", "https://rest-mainnet.onflow.org");
// CHANGE THESE THINGS FOR YOU
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const ADDRESS = process.env.ADDRESS;
const KEY_ID = parseInt(process.env.KEY_ID);
const hash = (message) => {
const sha = new SHA3(256);
sha.update(Buffer.from(message, "hex"));
return sha.digest();
};
const sign = (message) => {
const key = ec.keyFromPrivate(Buffer.from(PRIVATE_KEY, "hex"));
const sig = key.sign(hash(message)); // hashMsgHex -> hash
const n = 32;
const r = sig.r.toArrayLike(Buffer, "be", n);
const s = sig.s.toArrayLike(Buffer, "be", n);
return Buffer.concat([r, s]).toString("hex");
};
async function authorizationFunction(account) {
return {
...account,
tempId: `${ADDRESS}-${KEY_ID}`,
addr: fcl.sansPrefix(ADDRESS),
keyId: Number(KEY_ID),
signingFunction: async (signable) => {
return {
addr: fcl.withPrefix(ADDRESS),
keyId: Number(KEY_ID),
signature: sign(signable.message),
};
},
};
}
async function sendTx() {
const addr = process.env.RECIPIENT_ADDRESS;
const transactionId = await fcl.mutate({
cadence: `
import FungibleToken from 0xf233dcee88fe0abe
import FlowToken from 0x1654653399040a61
transaction(amount: UFix64, to: Address) {
let vault: @FlowToken.Vault
prepare(signer: auth(BorrowValue) &Account) {
self.vault <- signer.storage
.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!
.withdraw(amount: amount) as! @FlowToken.Vault
}
execute {
getAccount(to)
.capabilities
.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
.borrow()!
.deposit(from: <-self.vault)
}
}
`,
args: (arg, t) => [arg(100.0, t.UFix64), arg(addr, t.Address)],
payer: authorizationFunction,
proposer: authorizationFunction,
authorizations: [authorizationFunction],
limit: 999,
});
console.log({ transactionId });
fcl.tx(transactionId).subscribe((res) => {
console.log(res);
});
}
sendTx();
これはNode.jsのコードですが、Goでは少し違う形になります。
ここで書いている内容はここやここを参考にしてください。鍵のアルゴリズム、PRIVATE_KEY、KEY_IDの部分以外は同じ書き方になります。あくまで同じようなことが書いてあるな、と理解するだけで大丈夫です。大事なのはPrivate Keyを決して公開しない事です。
上記のコードをsendFlow.jsのようなファイルに保存してnode ./sendFlow.jsを実行すれば、Flowトークンを好きな時に他のアカウントに送ることができます。よく使用します。
