Nuxt3とdev-kitでDApp開発を始めてみる

## はじめに

[Nuxt3](https://v3.nuxtjs.org/)がリリースされたので、[@devprotocol/dev-kit](https://www.npmjs.com/package/@devprotocol/dev-kit)と組み合わせて、DApp開発してみます。

## 環境

MacOSX 11.6 (Intel Mac)
Node.js v14.17.6
nuxt3 3.0.0-27324955.23397e6 （`latest`指定）
@devprotocol/dev-kit 5.9.0
ethers 5.5.2

## 開発

### インストール

Nuxt3や必要なパッケージをあらかじめインストールしておきます。

```shell-session
$ npx nuxi init nuxt3-app
$ cd nuxt3-app
$ yarn
$ yarn dev
```

http://localhost:3000 でアプリケーションが起動します。

TypeScript使うならこれ必要？(もしかしたら実行しなくてもいいかも)

```shell-session
$ yarn nuxi typecheck
```

その他必要なパッケージもインストール

```shell-session
$ yarn add ethers @devprotocol/dev-kit
```

### アプリケーションを書いてみる

[dev-kitのリポジトリにあるexamplesのget-property-info.js](https://github.com/dev-protocol/dev-kit-js/blob/71416344e471c891370020dae8c11cd3e5c791f4/examples/get-property-info.js)を組み込んでみます。

特定のプロパティアドレスを指定して、その合計ステーキング数と報酬数（どちらも単位はDEV）を表示する感じです。

その他の前提としてはEthereumのmainnetを使うことにします。

`app.vue` を以下のように変更します。（ソースコード内のコメントは後から追加しました。）

```vue
<template>
  <div>
    <NuxtWelcome />
  </div>
</template>

<script>
import { ethers } from "ethers";
import { contractFactory, addresses } from "@devprotocol/dev-kit";

export default {
  data() {
    const config = useRuntimeConfig();
    console.log(config, config.WEB3_PROVIDER_URL);
    return {
      config,
    };
  },
  async mounted() {
    // メインネットを使う
    const provider = new ethers.providers.JsonRpcProvider(
      this.config.WEB3_PROVIDER_URL
    );
    const network = await provider.ready;
    console.log(network);
    const contract = contractFactory(provider);

    // 対象のプロパティアドレス
    const propertyAddress = "0xac1AC9d00314aE7B4a7d6DbEE4860bECedF92309";

    // コントラクトの準備
    const registryContractAddress = addresses.eth.main.registry;
    const lockupContractAddress = await contract
      .registry(registryContractAddress)
      .lockup();

    // ステーキング数取得
    const propertyStakingAmount = await contract
      .lockup(lockupContractAddress)
      .getPropertyValue(propertyAddress);
    const stakingAmount = ethers.BigNumber.from(propertyStakingAmount).div(
      new ethers.BigNumber.from(10).pow(18)
    );
    console.log(
      `${propertyAddress}'s staking amount is ${stakingAmount.toBigInt()} DEV`
    );

    // 報酬数取得
    const propertyRewards = await contract
      .lockup(lockupContractAddress)
      .calculateRewardAmount(propertyAddress);
    const reward = ethers.BigNumber.from(propertyRewards[0]).div(
      new ethers.BigNumber.from(10).pow(36)
    );
    console.log(`${propertyAddress}'s rewards is ${reward.toBigInt()} DEV`);
  },
};
</script>
```

`nuxt.config.ts`は以下

```typescript
import { defineNuxtConfig } from "nuxt3";

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
    build: {
        transpile: ["@ethersproject", "ethers"],
    },
    publicRuntimeConfig: {
        WEB3_PROVIDER_URL:
            "https://eth-mainnet.alchemyapi.io/v2/XXXX",
    },
});
```

`https://eth-mainnet.alchemyapi.io/v2/XXXX`は自分で作成したAlchemyのキーとかローカルのEthereumノード使ってね。

で、起動するとエラー発生

```
Uncaught SyntaxError: import not found: default bignumber.js:9:7
```

`nuxt.config.ts`を以下のように`vite.optimizeDeps`にエラーが発生しているパッケージを追加していく.
色々対応して別のエラーまで進んだ.

```typescript
import { defineNuxtConfig } from "nuxt3";

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
    build: {
        transpile: ["@ethersproject", "ethers"],
    },
    vite: {
        optimizeDeps: {
            include: ["bn.js", "js-sha3", "hash.js", "aes-js", "scrypt-js", "bech32"],
        },
    },
    publicRuntimeConfig: {
        WEB3_PROVIDER_URL:
            "https://eth-mainnet.alchemyapi.io/v2/XXXX",
    },
});
```

ここまでで一旦 `git init && git add XXX && git commit` しておく。

### エラー解析からのさらに開発進める

もう一度起動してエラーの内容をみる.
```
Uncaught (in promise) TypeError: (intermediate value).BigNumber.from is not a constructor
```

なんと! `new` 不要だったのか!!このexamples私が追加したのですが、`new`不要だったようです。
修正。

```diff
diff --git a/app.vue b/app.vue
index baf5fbd..2ecdda9 100644
--- a/app.vue
+++ b/app.vue
@@ -34,7 +34,7 @@ export default {
       .lockup(lockupContractAddress)
       .getPropertyValue(propertyAddress);
     const stakingAmount = ethers.BigNumber.from(propertyStakingAmount).div(
-      new ethers.BigNumber.from(10).pow(18)
+      ethers.BigNumber.from(10).pow(18)
     );
     console.log(
       `${propertyAddress}'s staking amount is ${stakingAmount.toBigInt()} DEV`
@@ -44,7 +44,7 @@ export default {
       .lockup(lockupContractAddress)
       .calculateRewardAmount(propertyAddress);
     const reward = ethers.BigNumber.from(propertyRewards[0]).div(
-      new ethers.BigNumber.from(10).pow(36)
+      ethers.BigNumber.from(10).pow(36)
     );
     console.log(`${propertyAddress}'s rewards is ${reward.toBigInt()} DEV`);
   },
```

うまく動いたっぽい!!
コンソールに出力されてる。

```
0xac1AC9d00314aE7B4a7d6DbEE4860bECedF92309's staking amount is 28645 DEV app.vue:40:13
0xac1AC9d00314aE7B4a7d6DbEE4860bECedF92309's rewards is 16377 DEV app.vue:50:3
```

## 最終調整

最後に表示を整える。最終的に以下のようなコードになった。

```vue
<template>
  <div>
    <p>property address: {{ propertyAddress }}</p>
    <p>staking: {{ staking?.toBigInt() }} DEV</p>
    <p>rewards: {{ reward?.toBigInt() }} DEV</p>
  </div>
</template>

<script>
import { ethers } from "ethers";
import { contractFactory, addresses } from "@devprotocol/dev-kit";

export default {
  data() {
    const config = useRuntimeConfig();
    console.log(config, config.WEB3_PROVIDER_URL);
    return {
      config,
      propertyAddress: "0xac1AC9d00314aE7B4a7d6DbEE4860bECedF92309",
      staking: undefined,
      reward: undefined,
    };
  },
  async mounted() {
    // use main net
    const registryContractAddress = addresses.eth.main.registry;
    const provider = new ethers.providers.JsonRpcProvider(
      this.config.WEB3_PROVIDER_URL
    );
    const network = await provider.ready;
    console.log(network);
    const contract = contractFactory(provider);
    const lockupContractAddress = await contract
      .registry(registryContractAddress)
      .lockup();

    const propertyStakingAmount = await contract
      .lockup(lockupContractAddress)
      .getPropertyValue(this.propertyAddress);
    const stakingAmount = ethers.BigNumber.from(propertyStakingAmount).div(
      ethers.BigNumber.from(10).pow(18)
    );
    console.log(
      `${
        this.propertyAddress
      }'s staking amount is ${stakingAmount.toBigInt()} DEV`
    );

    const propertyRewards = await contract
      .lockup(lockupContractAddress)
      .calculateRewardAmount(this.propertyAddress);
    const reward = ethers.BigNumber.from(propertyRewards[0]).div(
      ethers.BigNumber.from(10).pow(36)
    );
    console.log(
      `${this.propertyAddress}'s rewards is ${reward.toBigInt()} DEV`
    );

    this.staking = stakingAmount;
    this.reward = reward;
  },
};
</script>
```

でブラウザから見るとこんな感じ。

![スクリーンショット 2021-12-16 23.32.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1775/3c8f7257-fa43-f694-a243-e31af0b634db.png)

## おわりに

Nuxt3でDApp開発できそうです。いい感じです。

以下リポジトリにソースコードがありますので、参考まで。

https://github.com/hhatto/nuxt3-devkit-example