Skip to main content

Command Palette

Search for a command to run...

ちょっと高度な Hello World !

Updated
5 min readView as Markdown
T
Founder & Architect of Blsqui. Building the cloud-native infrastructure layer that transforms standard video games into instant Web3 eSports platforms. Passionate about democratizing global revenue networks for the next generation of independent, AI-driven creators. Creating plug-and-play, no-code backend frameworks for Unity and Godot that integrate frictionless stablecoin microtransactions without client-side execution overhead.

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.pkeymainnet-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トークンを好きな時に他のアカウントに送ることができます。よく使用します。