自分の備忘録としてDappの作り方の手順をまとめてみました。
最近やり始めたので間違いもあるかもしれません。ご承知ください。
環境構築
solidityのインストール
$ brew install solidity
truffleのインストールとプロジェクトの作成
$ npm install -g truffle
$ mkdir project && cd project
$ truffle init
$ vue create front
vue create
時にtypescriptを選択します。
*****現状の環境*****
$ node -v
v11.9.0
$ npm -v
6.5.0
$ vue -V
3.3.0
$ solc --version
solc, the solidity compiler commandline interface
Version: 0.5.3+commit.10d17f24.Darwin.appleclang
$ truffle version
Truffle v5.0.3 (core: 5.0.3)
Solidity v0.5.0 (solc-js)
Node v11.9.0
* macOS Mojave version10.14
truffleプロジェクトの構造
上記に従ってプロジェクトの作成を行うと、以下のような構造を持つディレクトリが作られます。
-----contracts
|---migrations
|---test
|---truffle-config.js
|---front
ディレクトリの構造を理解するために、Dapp開発の流れを説明します。
Dapp開発の手順
- contractディレクトリにsolファイルを作成してコントラクトコードを書く。
- migrationディレクトリにデプロイのためのjsファイルを作成。
- testディレクトリにコントラクトコードをテストするファイルを作成。
- ganacheの導入
- terminalでtruffle migrateを行い、プライベートチェーン上にコントラクトをデプロイ
- フロント側でweb3.jsの設定を行う
- metamaskの導入
- 動作確認
contractディレクトリにsolファイルを作成してコントラクトコードを書く。
solファイルはsolidityで書かれたファイルのことです。このファイルにコントラクトを書き込んでいきます。
コントラクトはブロックチェーンのネットワーク上で利用できるアカウントであり、このコントラクト上で実行されるコードをコントラクトコードと呼びます。
ここではブロックチェーンに関する解説は省きますが、ブロックチェーンのネットワークには送金などを行うアカウントとコードとして実行されるアカウントが存在し、Dappでは後者を利用されています。
// Dogs.sol
pragma solidity ^0.5.0;
contract Dogs {
// 犬のインスタンスを作っていきます
struct Dog {
uint id;
string name;
}
// mappingはいわゆる連想配列のこと。ここではuint(id)をDogインスタンスに割り当てている。
mapping(uint => Dog) public dogs; // Dogインスタンスを格納
uint public dogsCount; // インスタンス作成時のidとして利用(毎インスタンス生成時に+1していく)
constructor() public { // 初期インスタンス生成
createProgress("pochi");
}
function createProgress(string memory _name) private {
dogsCount++;
dogs[dogsCount] = Dog(dogsCount, _name);
}
}
migrationディレクトリにデプロイのためのjsファイルを作成。
migrationディレクトリには、deployのためのファイルを作成します。
// migration/2_deploy_contract.js
let Dogs = artifacts.require("./Dogs.sol");
module.exports = function(deployer) { deployer.deploy(Dogs); };
上記のようにファイルを作成し、以下のコマンドを実行すると、contractsディレクトリに作成されたコントラクトをブロックチェーン上にデプロイします。
$ truffle migrate
また、今後開発を行なっていく上で、一度デプロイしたコントラクトコードを修正したいと思うことがあると思います。
その時は以下のコマンドを実行して新しいコントラクトコードでデプロイし直します。
$ truffle migrate --reset
railsをやったことがある人であれば、db:migrate:resetと同じような感じで考えていただければ良いと思います。
testディレクトリにコントラクトコードをテストするファイルを作成。
migrate --resetでやり直せるにしても、ブロックチェーンにデプロイされたものは修正が効かず、それゆえに改竄不可能性を持つわけですが、そうなるとバグや予想しない動作を含むものをデプロイするわけにはいきません。
こういった不具合を未然に防ぐためにもコントラクトコードのテストを行う方が良いです。
テストはsolidityでもjavascriptでも書くことができます。
// 飽くまで例です
// jsの場合
const Dogs = artifacts.require("./Dogs.sol");
contract("Dogs", function(accounts) {
it("initializes with a dog", function() {
return Dogs.deployed().then(function(instance) {
return instance.dogsCount();
}).then(function(count) {
assert.equal(count, 1); // solファイルのconstructorで作成したデフォルトのインスタンス
});
});
});
ganacheの導入
ganacheはプライベートチェーン上に自動的に模擬アカウントを作成してくれるツールです。
以下のリンクからインストールできます。
https://truffleframework.com/ganache
ganacheを起動させてからコントラクトコードをプライベートチェーンにデプロイしてあげると、ganacheで生成されたアカウントがマイニングを行なってくれるので、すぐにコントラクトコードを実行できるようになります。
terminalでtruffle migrateを行い、プライベートチェーン上にコントラクトをデプロイ
ganacheを起動させた状態で、且つ、contractsディレクトリにsolidityファイル、migrationsディレクトリにdeploy用のjsファイル(testのファイルはここでの動作に影響はありませんが、soldityファイルにエラーがあればデプロイができなくなります)があることを確認してください。
その上で次のコマンドを実行してください。
$ truffle migrate
1_initial_migration.js
======================
Replacing 'Migrations'
----------------------
> transaction hash: 0xc3006c7464bb1e931d818226170e743976ca068ce15c92c0ad074f6a2685f71c
> Blocks: 0 Seconds: 0
> contract address: 0x8bBDf86104324ff2bA12d34F4D9D38eEe9e83748
> account: 0x0f85Ddf6555a85e7bfb107faD34Fc13DF63475A4
> balance: 99.92727632
> gas used: 284908
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00569816 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00569816 ETH
2_deploy_contract.js
====================
Replacing 'Dogs'
------------------
> transaction hash: 0xb2e38fc03105dc5089418a24ede9f54559ca78264ce2ab9753f521e2338ee50f
> Blocks: 0 Seconds: 0
> contract address: 0x1EEB22378Ae20b4c18179E46F9C89D11B1f8Aa57
> account: 0x0f85Ddf6555a85e7bfb107faD34Fc13DF63475A4
> balance: 99.90698292
> gas used: 972636
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.01945272 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.01945272 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.02515088 ETH
デプロイができれば上記のようになります。
他の方法でデプロイができていることを確認するには、
$ truffle console
でtruffleのコンソールに入って、以下のようにコントラクトコードを実行してみることができます。
$ truffle console
truffle(ganache)> Dogs.deployed().then(function(dogs) { app = dogs })
undefined
truffle(ganache)> app.dogs(1).then(function(d) { dog = d })
undefined
truggle(ganache)> dog[1]
'pochi'
Dogs.deployed().then(function(dogs) { app = dogs })
の部分で、appにdogsコントラクトを渡し、そのappを使って、app.dogs(1)
のように、コントラクトコード上に定義したmappingにidを渡して該当のインスタンスを抜き出します。
最後にdog = d
の部分で抜き出したインスタンスを変数に格納し、最後にそれの[1]番目の要素、つまりnameを参照しています。
このように名前を参照することができればデプロイできている証拠です。
次にフロント側の設定を行なっていきます。
フロント側でweb3.jsの設定を行う
それでは今度はフロント側を実装していきます。
ここではvue.jsとtypescriptを使っていきます。
まず、vue createでプロジェクトを作成してください。
作成時にtypescriptを利用する選択を行なってください。
cssもcssプリプロセッサを使うことで楽に記述できるようになるのでおすすめです。
$ vue create frontend
プロジェクトが作成されれば、中に入ってweb3とtruffle-contractのモジュールをインストールします。
$ npm install --save-dev web3@1.0.0-beta.37 truffle-contract
ここではweb3のバージョンを指定していますが、私の環境ではweb3だけでnpm installすると、このあと紹介するmetamaskというツールを利用した時にエラーが発生したので、これはその対処として行なっています。
vueとtypescriptの説明は行わないので、コンポーネントのファイルに書くブロックチェーンとやりとりするためのコードを解説します。
el-系のinputタグやbuttonタグはelementUIをインストールすると利用できるようになります。非常に使いやすいのでお勧めです。
https://element.eleme.io/#/en-US
*templateにはpugを利用しています。npm install --save-dev pug pug-plain-loader
で使えます。
<template lang="pug">
#Contract
.list(v-for="dog in dogs")
p {{ dog.name }}
.form
el-input(v-model="name")
el-button(@click="createDog") 作成
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import Web3 from 'web3'
import TruffleContract from 'truffle-contract'
import artifacts from '../../../build/contracts/Dogs.json' // デプロイするとbuild/contracts配下に作成されます。
import $ from 'jquery'
const Dogs = TruffleContract(artifacts)
@Component({ components: { Dogs } })
export default class ProgressChainIndex extends Vue {
private ownAddress: string = ""
private name: string = ""
private dogs: Array<object> = []
created() {
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider)
} else {
console.warn('No web3 detected.')
web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:7545'))
}
Dogs.setProvider(web3.currentProvider)
web3.eth.getCoinbase().then((account) => {
this.ownerAddress = account
Dogs.defaults({ from: account })
});
}
beforeMount() {
// ここでブロックチェーン上に記録されたデータを取得して画面上に表示する
let dogsInstance
let self = this
Dogs.deployed().then(function(instance) {
dogsInstance = instance
return dogsInstance.dogsCount()
}).then(function(dogsCount) {
for (let i = 1; i <= progressId; i++) {
// ブロックチェーン上のdogインスタンスを一つ一つ拾ってきて、そのidとnameをまとめたオブジェクトを作成、配列dogsに格納する
dogsInstance.progresses(i).then(function(dog) {
self.dogs.push({
id: dog[0],
name: dog[1]
})
})
}
}).catch(function(err) {
console.warn(err)
})
}
createDog() {
// ブロックチェーンのコントラクトコードにインスタンスを記録
let self = this
Dogs.deployed().then(function(instance) {
return instance.createProgress(
self.name,
{ from: self.ownerAddress })
}).catch(function(err) {
console.error(err)
})
}
}
</script>
このコンポーネントを好きなように親コンポーネントに組み込んであげてください。
metamaskの導入
metamaskはブラウザとブロックチェーンを繋げるツールです。
このツールが実行されていれば、先ほどのコード上でweb3を認識することができます。
https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=ja
アプリケーションの実行
上記のようにコードを作成し、vueのディレクトリでnpm run serve
を行います。
localhost:8080を開き、コンポーネントで配置したinputにdogの名前を入力して、作成ボタンを押します。
すると、ブロックチェーンへのアクセスを承認するか拒否するかの選択が表示されるので、承認ボタンを押しましょう。
承認後、作成したインスタンスをブラウザ上に反映させるためには一度リロードを行う必要があります。
また、ここでエラーが発生するようであれば、コードに何らかの原因があるか、ganacheやmetamaskの問題が考えられます。
ganacheの再起動やmetamaskでアカウントのリセットを行うと改善されることがあるので、その辺を確認してみましょう。
以下の動画は非常に参考になります。実際に動かしながら説明しているのでこのページを読むより楽かもしれません。
https://www.youtube.com/watch?v=3681ZYbDSSk