メインコンテンツまでスキップ

lesson-1_コントラクトの関数を呼び出せるようにしよう

🥃 コントラクトの関数を呼び出せるようにしよう!

ここからはフロントエンドを開発していきましょう!

本教材の主眼はコントラクトの作成なので、UI部分の解説は簡略化していることをご了承ください 😔

まずはフロントエンドの開発に必要なライブラリをインポートしましょう。次のコマンドをpackages/clientディレクトリへ移動した後、ターミナルで実行してください。

yarn add react-icons --save
yarn add --save react-modal @types/react-modal

次に環境変数として必要な値を登録しましょう。

ディレクトリ構造において一番上階層(pagesと同じ階層)に.envファイルを作成して、内容を下のように記述しましょう。

ここではコントラクトアドレスとまだプロフィールを作成していないユーザーのプロフィール画像のURLでそれぞれNEXT_PUBLIC_CONTRACT_ADDRESS, NEXT_PUBLIC_UNKNOWN_IMAGE_URLという名前で登録することになります。

取得したコントラクトアドレスをcontract_addressへ代入します(こちら動作確認のためにデプロイした際に行います)。

unknown_imageにはユーザーがデフォルトで設定するプロフィールの画像のURLを記述してください。

ここで注意点ですが、今回使用できるのはこちらにある画像のみです。また、NEXT_PUBLIC_UNKNOWN_IMAGE_URLに代入するURLは下のようにhttpsから全てのURLを記述しましょう!

NEXT_PUBLIC_UNKNOWN_IMAGE_URL="https://images.unsplash.com/..."

[.env]

NEXT_PUBLIC_CONTRACT_ADDRESS=contract_address
NEXT_PUBLIC_UNKNOWN_IMAGE_URL=unknown_image

下のように代入してみてください。

次にさきほどコントラクトを実装して後に更新したmetadata.jsonを一番上の階層(pagesと同じ階層)に加えましょう。

次にlinterの設定をしましょう。linterをきちんと設定しないと無駄な部分にエラーが発生してアプリのデプロイ時に問題があるように写りデプロイができないようになります。

一番上の階層(pagesと同じ階層)に.eslintrc.jsonファイルがあるので下のように編集しましょう。

[eslintrc.json]

{
"extends": [
"plugin:@typescript-eslint/recommended",
"next",
"prettier",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"rules": {
"import/order": [
"error",
{
"alphabetize": {
"order": "asc"
}
}
]
}
}

その後コードのエディター(vscodeなど)を一度閉じて、再度開いてみましょう。きちんとlinterが動くようになります。

次に環境構築で作成したプロジェクトの一番上の階層にhookscomponentsというディレクトリを作成してください。

その後pageshookscomponentsを下のようなディレクトリ構造にしてください。

[pages]

pages/
├── _app.tsx
├── api/
│ └── hello.ts
├── home.tsx
├── index.tsx
├── message.tsx
└── profile.tsx

[hooks]

hooks/
├── connect.tsx
├── FT.tsx
├── messageFunction.tsx
├── postFunction.tsx
└── profileFunction.tsx

[components]

./
├── atoms/
│ ├── appLogo.tsx
│ ├── balance.tsx
│ ├── bigInput.tsx
│ ├── biggerProfileIcon.tsx
│ ├── bottomLogo.tsx
│ ├── closeButton.tsx
│ ├── disconnectButton.tsx
│ ├── inputBox.tsx
│ ├── postButton.tsx
│ ├── profileTitle.tsx
│ ├── sendButton.tsx
│ ├── smallInput.tsx
│ ├── smallerProfileIcon.tsx
│ ├── submitButton.tsx
│ └── walletAddressSelection.tsx
├── bottomNavigation.tsx
├── message.tsx
├── messageRoom.tsx
├── message_member.tsx
├── molecules/
│ ├── formBox.tsx
│ ├── headerProfile.tsx
│ └── profileList.tsx
├── organisms/
│ ├── footer.tsx
│ ├── header.tsx
│ ├── inputGroup.tsx
│ └── messageBar.tsx
├── post.tsx
├── postModal.tsx
├── profileSettingModal.tsx
├── profileSubTopBar.tsx
└── topBar.tsx

これらがUIを構成するパーツやフロントで使用する関数を含んだものになります。

では順番にそれぞれのディレクトリについて説明して行きます。

pagesにはそれぞれの画面全体を記述することになります。ファイルの名前(.tsxを除く)が直接URLの名前となるので、URLがそのファイル名変更されるとそのページが表示されることになります。

hooksにはコントラクトで作成した関数をフロントで使用できるようにしたものが記述されます。ウォレットと接続するための関数はconnect.tsxに記述することになり、他のファイルにはコントラクトの関数が記述することになります。

componentsにはUIで使用するパーツを記述して行きます。今回のプロジェクトではアトミックデザインという考え方を採用しており、完全ではありませんがこの考え方に沿って構成されています。

アトミックデザインとはアプリを構成するパーツの単位を5つの段階に分けるというものでAtoms(原子),Molecules(分子),Organism(生体),Template(テンプレート),Pages(ページ)に分けられAtoms(原子)に近いほど小さく、Pagesに近いほど大きなパーツになります。

今回はTemplate以外のものを採用して作成しています。詳しくはこちらの記事を参考にしてください。

こんなことをしたらファイルが多すぎて逆にわかりにいのでは? と思われる方もいるかもしれないんですが、特にAtoms(原子)などの小さい単位のものは使い回しができるので重複してコードを書く必要はありませんし、また何処かでエラーが発生した場合にどのパーツでエラーが発生しているか追っていけばいいだけなので対処しやすいというメリットがあります。

次に、フロントエンドで使用する画像をプロジェクトの中に入れておきましょう。

下の画像を添えられている名前の通りに、ディレクトリ構造の一番上の階層にあるpublicに保存してください。

[Astar_logo.png]

[cross_mark_2_logo-removebg.png]

[cross_star_2_logo-removebg.png]

[cross_star_6_logo-removebg.png]

[unchain_logo.png]

次にNext.jsではURLの画像を参照するためにはnext.config.jsに参照したいURLの頭の部分を登録する必要があるので下のように書き換えましょう。

[next.config.js]

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
images: {
domains: ["images.unsplash.com", "plus.unsplash.com"],
},
typescript: {
// !! WARN !!
// Dangerously allow production builds to successfully complete even if
// your project has type errors.
// !! WARN !!
ignoreBuildErrors: true,
},
};

module.exports = nextConfig;

これでunsplashの画像やインターネット上にある画像を参照できるようになりました!

それでは早速コントラクトの関数を呼び出す部分であるhooksディレクトリの中身を下のように編集して行きましょう。

[connect.tsx]

このファイルではコントラクトとの接続に関わるものに関する記述をしていきます。

import { ApiPromise, WsProvider } from "@polkadot/api";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import { Dispatch } from "react";

// コントラクトとの接続を行うために使用するtype
type Props = {
api: ApiPromise | undefined;
accountList: InjectedAccountWithMeta[];
actingAccount: InjectedAccountWithMeta;
isSetup: boolean;
setApi: Dispatch<React.SetStateAction<ApiPromise | undefined>>;
setAccountList: Dispatch<React.SetStateAction<InjectedAccountWithMeta[]>>;
setActingAccount: Dispatch<
React.SetStateAction<InjectedAccountWithMeta | undefined>
>;
setIsSetup: React.Dispatch<React.SetStateAction<boolean>>;
};

// コントラクトとの接続を行うための関数
export const connectToContract = async (props: Props) => {
// rpcのURL
const blockchainUrl = "ws://127.0.0.1:9944";

// この関数でアカウント情報を取得する
const extensionSetup = async () => {
const { web3Accounts, web3Enable } = await import(
"@polkadot/extension-dapp"
);
const extensions = await web3Enable("Polk4NET");
if (extensions.length === 0) {
return;
}
const accounts = await web3Accounts();
props.setAccountList(accounts);
props.setActingAccount(accounts[0]);
props.setIsSetup(true);
};

// この部分でコントラクトに接続
const wsProvider = new WsProvider(blockchainUrl);
const connectedApi = await ApiPromise.create({ provider: wsProvider });
props.setApi(connectedApi);
await extensionSetup();
};

まずは必要なライブラリをインストールしましょう。

packages/clientディレクトリへ移動して下記のコマンドを実行しましょう。

yarn add @polkadot/api @polkadot/extension-inject @polkadot/extension-dapp

順番に見て行きましょう。

下の部分ではコントラクト接続に使用するための引数をPropsで受け取る値の型を指定しています。

apiはチェーンと接続した時に取得するもので、これはコントラクトから関数を呼び出す時にも使用します。

accountList,actingAccountについては、それぞれ存在が確認されているアカウント情報のリストと現在接続しているアカウントが入ることになります。

isSetupには接続がされているかを確認するためのbool型の値が入りまします。

それ以降の値はuseStateで取得するset○○のような関数で、値をセットするための関数が入ります。

// コントラクトとの接続を行うために使用するtype
type Props = {
api: ApiPromise | undefined;
accountList: InjectedAccountWithMeta[];
actingAccount: InjectedAccountWithMeta;
isSetup: boolean;
setApi: Dispatch<React.SetStateAction<ApiPromise | undefined>>;
setAccountList: Dispatch<React.SetStateAction<InjectedAccountWithMeta[]>>;
setActingAccount: Dispatch<
React.SetStateAction<InjectedAccountWithMeta | undefined>
>;
setIsSetup: React.Dispatch<React.SetStateAction<boolean>>;
};

ここではブロックチェーンとのやりとりを仲介してくれるRPCのURLを定義しています。

今回はテストネットではなくローカルのRPCを使用するので下のように記述しましょう。

// rpcのURL
const blockchainUrl = "ws://127.0.0.1:9944";

次の部分でアカウント情報を取得しています。それらの取得したアカウントを、この関数を使用するページにおいてuseStateによってセットします。

そして最後に接続が完了したことを伝えるためにsetIsSetuptrueを入れます。

// この関数でアカウント情報を取得する
const extensionSetup = async () => {
const { web3Accounts, web3Enable } = await import("@polkadot/extension-dapp");
const extensions = await web3Enable("Polk4NET");
if (extensions.length === 0) {
return;
}
const accounts = await web3Accounts();
props.setAccountList(accounts);
props.setActingAccount(accounts[0]);
props.setIsSetup(true);
};

上のextensionSetup関数の前にチェーンと接続します。

// この部分でコントラクトに接続
const wsProvider = new WsProvider(blockchainUrl);
const connectedApi = await ApiPromise.create({ provider: wsProvider });
props.setApi(connectedApi);
await extensionSetup();

では次にコントラクトから呼び出す関数の定義を行なっていきましょう!

[messageFunction.tsx]

import { ApiPromise } from "@polkadot/api";
import { ContractPromise } from "@polkadot/api-contract";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";

import abi from "../metadata.json";

// コントラクトの`Message`構造体の型
export type MessageType = {
message: string;
senderId: string;
createdTime: string;
};

// sendMessage関数用の型
type PropsSM = {
api: ApiPromise | undefined;
actingAccount: InjectedAccountWithMeta;
message: string;
id: string;
};

// getMessage関数用の型
type PropsGML = {
api: ApiPromise | undefined;
id: number;
};

// lastMessage関数用の型
type PropsGLM = {
api: ApiPromise | undefined;
id: number;
};

// コントラクトアドレスをenvファイルから抽出
const contractAddress: string = process.env
.NEXT_PUBLIC_CONTRACT_ADDRESS as string;

// メッセージ送信関数
export const sendMessage = async (props: PropsSM) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api!, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount.meta.source);
const date = new Date();
const add_likes = await contract.tx.sendMessage(
{
value: 0,
gasLimit: 18850000000,
},
props.message,
props.id,
[date.getMonth() + 1, date.getDate()].join("-") +
" " +
[
date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, "0"),
].join(":")
);
if (injector !== undefined) {
add_likes.signAndSend(
performingAccount.address,
{ signer: injector.signer },
(result) => {}
);
}
};

// メッセージリストを取得する関数
export const getMessageList = async (props: PropsGML) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getMessageList(
"",
{
value: 0,
gasLimit: -1,
},
props.id,
1
);
if (output !== undefined && output !== null) {
return output;
}
return [];
};

// それぞれのメッセージリストの最後のメッセージを取得する関数
export const getLastMessage = async (props: PropsGLM) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getLastMessage(
"",
{
value: 0,
gasLimit: -1,
},
props.id
);
if (output !== undefined && output !== null) {
return output.toHuman()?.message.toString() ?? "";
}
};

まずは必要なライブラリを下のコマンドをターミナルで実行することによってインストールしましょう。

yarn add @polkadot/api-contract

次にmetadata.jsonについては先ほど作成したastar_sns_contractプロジェクトのmetadata.jsonをディレクトリ構造の一番上の階層にコピー&ペーストしましょう。

ではコードを順番に見て行きましょう。

まずは初めに定義しているtypeについてです。

MessageTypeはコントラクトで定義しているMessageという構造体のそれぞれの値の型を定義しています。

それ以降はそれぞれ関数の略称(ex: sendMessage->PropsSM)をPropsにつけて型を定義しています。

export type MessageType = {
message: string;
senderId: string;
createdTime: string;
};

// type for sendMessage function
type PropsSM = {
api: ApiPromise | undefined;
actingAccount: InjectedAccountWithMeta;
message: string;
id: string;
};

// type for getMessage function
type PropsGML = {
api: ApiPromise | undefined;
id: number;
};

// type for lastMessage function
type PropsGLM = {
api: ApiPromise | undefined;
id: number;
};

const contractAddress: string = process.env
.NEXT_PUBLIC_CONTRACT_ADDRESS as string;

次にコントラクトから呼び出す関数についてです。コントラクトには2種類あり、コントラクトに情報を書き込む関数コントラクトから情報を引き出す関数があります。

まず前者の関数では下のように記述します。ガス代と引数を指定する必要があります。

export const sendMessage = async (props: PropsSM) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api!, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount.meta.source);
const date = new Date();
const add_likes = await contract.tx.sendMessage(
{
value: 0,
gasLimit: 18850000000,
},
props.message,
props.id,
[date.getMonth() + 1, date.getDate()].join("-") +
" " +
[
date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, "0"),
].join(":")
);
if (injector !== undefined) {
add_likes.signAndSend(
performingAccount.address,
{ signer: injector.signer },
(result) => {}
);
}
};

後でも注意するのですが、ガス代が足りなかったり多すぎたりするときちんとしたコードが書けていてもきちんとトランザクションが行われないという不具合が起きます。

ではどれくらいのガス代に設定すればいいのかということですが方法は2つあります。

1つはプロジェクト内で計算するというものでもう1つはPolkadot.jsサイトで確かめるというものです。

今回は後者で行います。下のように情報を書き込む関数にはexecボタンがあるのでそれを押せばガス代の最大値が出てくるので、それと同じ値を入れましょう。

次の2つはコントラクトから情報を引き出すタイプの関数です。こちらはガス代を消費することはないのでガス代の欄は-1に設定しておけばOKです。

最終的な結果はoutputという変数に入るのでそれをフロントに反映させることになります。

export const getMessageList = async (props: PropsGML) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getMessageList(
"",
{
value: 0,
gasLimit: -1,
},
props.id,
1
);
if (output !== undefined && output !== null) {
return output;
}
return [];
};

// それぞれのメッセージリストの最後のメッセージを取得する関数
export const getLastMessage = async (props: PropsGLM) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getLastMessage(
"",
{
value: 0,
gasLimit: -1,
},
props.id
);
if (output !== undefined && output !== null) {
return output.toHuman()?.message.toString() ?? "";
}
};

次にFT(Fungible Token)機能に関するファイルFT.tsx,投稿機能に関するファイルpostFunction.tsx,プロフィールに関するファイルprofileFunction.tsxも同様に記述していきましょう。

基本はmessageFunction.tsxに記述したものと書き方は同じなので説明は省略します。

[FT.tsx]

import { ApiPromise } from "@polkadot/api";
import { ContractPromise } from "@polkadot/api-contract";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import { Dispatch } from "react";

import abi from "../metadata.json";

type PropsBO = {
api: ApiPromise | undefined;
actingAccount: InjectedAccountWithMeta;
setBalance: Dispatch<React.SetStateAction<string>>;
};

type PropsTF = {
api: ApiPromise | undefined;
actingAccount: InjectedAccountWithMeta;
amount: number;
};

type PropsDRL = {
api: ApiPromise | undefined;
actingAccount: InjectedAccountWithMeta;
};

const contractAddress: string = process.env
.NEXT_PUBLIC_CONTRACT_ADDRESS as string;

export const balanceOf = async (props: PropsBO) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.balanceOf(
"",
{
value: 0,
gasLimit: -1,
},
props.actingAccount.address
);
if (output !== undefined && output !== null) {
props.setBalance(output.toHuman()?.toString());
}
};

export const transfer = async (props: PropsTF) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api!, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount.meta.source);
const transfer = await contract.tx.transfer(
{
value: 0,
gasLimit: 31518000000,
},
props.amount
);
if (injector !== undefined) {
transfer.signAndSend(
performingAccount.address,
{ signer: injector.signer },
(result) => {}
);
}
};

export const distributeReferLikes = async (props: PropsDRL) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api!, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount.meta.source);
const transfer = await contract.tx.distributeReferLikes({
value: 0,
gasLimit: 31518000000,
});
if (injector !== undefined) {
transfer.signAndSend(
performingAccount.address,
{ signer: injector.signer },
(result) => {}
);
}
};

[postFunction.tsx]

import { ApiPromise } from "@polkadot/api";
import { ContractPromise } from "@polkadot/api-contract";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import { Dispatch } from "react";

import abi from "../metadata.json";

// type for post in contract
export type PostType = {
name: string;
userId: string;
createdTime: string;
imgUrl: string;
userImgUrl: string;
description: string;
numOfLikes: number;
postId: number;
};

// type for releasePost function
type PropsRP = {
api: ApiPromise;
actingAccount: InjectedAccountWithMeta;
description: string;
imgUrl: string;
};

// type for getGeneralPost function
type PropsGGP = {
api: ApiPromise;
setGeneralPostList: Dispatch<React.SetStateAction<PostType[]>>;
};

// type for addLikes function
type PropsAL = {
api: ApiPromise;
actingAccount: InjectedAccountWithMeta;
postId: number;
};

// type for getIndividualPost function
type PropsGIP = {
api: ApiPromise | undefined;
actingAccount: InjectedAccountWithMeta | undefined;
setIndividualPostList: Dispatch<React.SetStateAction<PostType[]>>;
};

const contractAddress: string = process.env
.NEXT_PUBLIC_CONTRACT_ADDRESS as string;

// release post function
export const releasePost = async (props: PropsRP) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount.meta.source);
const date = new Date();
const release_post = await contract.tx.releasePost(
{
value: 0,
gasLimit: 50000000000,
},
props.description,
[date.getFullYear(), date.getMonth() + 1, date.getDate()].join("-") +
" " +
[
date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, "0"),
].join(":"),
props.imgUrl
);
if (injector !== undefined) {
release_post.signAndSend(
performingAccount.address,
{ signer: injector.signer },
(result) => {}
);
}
};

// get general post function
export const getGeneralPost = async (props: PropsGGP) => {
const contract = new ContractPromise(props.api, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getGeneralPost(
"",
{
value: 0,
gasLimit: -1,
},
1
);
if (output !== undefined && output !== null) {
props.setGeneralPostList(output.toHuman() == null ? [] : output.toHuman());
}
};

// add like to post function
export const addLikes = async (props: PropsAL) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount!.meta.source);
const add_likes = await contract.tx.addLikes(
{
value: 0,
gasLimit: 18850000000,
},
props.postId
);
if (injector !== undefined) {
add_likes.signAndSend(
performingAccount!.address,
{ signer: injector.signer },
(result) => {}
);
}
};

// get individual post function
export const getIndividualPost = async (props: PropsGIP) => {
const contract = new ContractPromise(props.api!, abi, contractAddress!);
const { gasConsumed, result, output } =
await contract.query.getIndividualPost(
"",
{
value: 0,
gasLimit: -1,
},
1,
props.actingAccount?.address
);
if (output !== undefined && output !== null) {
props.setIndividualPostList(
output.toHuman() == null ? [] : output.toHuman()
);
}
};

[profileFunction.tsx]

import { ApiPromise } from "@polkadot/api";
import { ContractPromise } from "@polkadot/api-contract";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import { Dispatch } from "react";

import abi from "../metadata.json";

// type of profile in contract
export type ProfileType = {
followingList: Array<string>;
followerList: Array<string>;
friendList: Array<string>;
userId: string;
name: string;
imgUrl: string;
messageListIdList: Array<number>;
postIdList: Array<number>;
};

// type for createCheckInfo function
type PropsCCI = {
api: ApiPromise | undefined;
userId: string | undefined;
setIsCreatedProfile: Dispatch<React.SetStateAction<boolean>>;
};

// type for createProject function
type PropsCP = {
api: ApiPromise | undefined;
actingAccount: InjectedAccountWithMeta;
};

// type for getProfileForHome function
type PropsGPFH = {
api: ApiPromise;
userId: string;
setImgUrl: Dispatch<React.SetStateAction<string>>;
};

// type for getProfileForProfile function
type PropsGPFP = {
api: ApiPromise | undefined;
userId: string | undefined;
setImgUrl: Dispatch<React.SetStateAction<string>>;
setName: Dispatch<React.SetStateAction<string>>;
};

// type for getProfileForMessage function
type PropsGPFM = {
api: ApiPromise | undefined;
userId: string | undefined;
setImgUrl: Dispatch<React.SetStateAction<string>>;
setMyImgUrl: Dispatch<React.SetStateAction<string>>;
setFriendList: Dispatch<React.SetStateAction<never[]>>;
setProfile: Dispatch<React.SetStateAction<ProfileType | undefined>>;
};

// type for getSimpleProfileForMessage function
type PropsGSPFM = {
api: ApiPromise | undefined;
userId: string | undefined;
};

// type for follow function
type PropsF = {
api: ApiPromise;
actingAccount: InjectedAccountWithMeta;
followedId: string;
};

// type for setProfileInfo function
type PropSPI = {
api: ApiPromise;
actingAccount: InjectedAccountWithMeta;
name: string;
imgUrl: string;
};

// type for getFollowingList function
type PropsGFIL = {
api: ApiPromise | undefined;
userId: string | undefined;
setFollowingList: Dispatch<React.SetStateAction<string[]>>;
};

// type for getFollowedList function
type PropsGFEL = {
api: ApiPromise | undefined;
userId: string | undefined;
setFollowerList: Dispatch<React.SetStateAction<string[]>>;
};

const contractAddress: string = process.env
.NEXT_PUBLIC_CONTRACT_ADDRESS as string;
const imageUrlForUnknown = process.env.NEXT_PUBLIC_UNKNOWN_IMAGE_URL as string;

// check if already create profile in contract function
export const checkCreatedInfo = async (props: PropsCCI) => {
const contract = new ContractPromise(props.api, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.checkCreatedInfo(
"",
{
value: 0,
gasLimit: -1,
},
props.userId
);
if (output !== undefined && output !== null) {
props.setIsCreatedProfile(output.toHuman());
}
};

// create profile function
export const createProfile = async (props: PropsCP) => {
console.log(props.actingAccount);
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api!, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount.meta.source);
const create_profile = await contract.tx.createProfile({
value: 0,
gasLimit: 18750000000,
});
if (injector !== undefined) {
create_profile.signAndSend(
performingAccount.address,
{ signer: injector.signer },
(result) => {}
);
}
};

// get profile for home screen function
export const getProfileForHome = async (props: PropsGPFH) => {
const contract = new ContractPromise(props.api, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getProfileInfo(
"",
{
value: 0,
gasLimit: -1,
},
props.userId
);
if (output !== undefined && output !== null) {
props.setImgUrl(
output.toHuman()?.imgUrl == null
? imageUrlForUnknown
: output.toHuman()?.imgUrl.toString()
);
}
};

// get profile for profile screen function
export const getProfileForProfile = async (props: PropsGPFP) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getProfileInfo(
"",
{
value: 0,
gasLimit: -1,
},
props.userId
);
if (output !== undefined && output !== null) {
props.setImgUrl(
output.toHuman()?.imgUrl == null
? imageUrlForUnknown
: output.toHuman()?.imgUrl.toString()
);
props.setName(
output.toHuman()?.name == null
? "unknown"
: output.toHuman()?.name.toString()
);
}
};

// get profile for message screen function
export const getProfileForMessage = async (props: PropsGPFM) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getProfileInfo(
"",
{
value: 0,
gasLimit: -1,
},
props.userId
);
if (output !== undefined && output !== null) {
props.setMyImgUrl(
output.toHuman()?.imgUrl == null
? imageUrlForUnknown
: output.toHuman()?.imgUrl.toString()
);
props.setImgUrl(
output.toHuman()?.imgUrl == null
? imageUrlForUnknown
: output.toHuman()?.imgUrl.toString()
);
props.setFriendList(
output.toHuman()?.friendList == null ? [] : output.toHuman()?.friendList
);
props.setProfile(output.toHuman());
}
};

// get simple profile for message screen function
export const getSimpleProfileForMessage = async (props: PropsGSPFM) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getProfileInfo(
"",
{
value: 0,
gasLimit: -1,
},
props.userId
);
if (output !== undefined && output !== null) {
return output.toHuman();
}
return;
};

// follow another account function
export const follow = async (props: PropsF) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api, abi, contractAddress);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount!.meta.source);
const follow = await contract.tx.follow(
{
value: 0,
gasLimit: 200000000000,
},
props.followedId
);
if (injector !== undefined) {
follow.signAndSend(
performingAccount!.address,
{ signer: injector.signer },
(result) => {}
);
}
};

export const setProfileInfo = async (props: PropSPI) => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const contract = new ContractPromise(props.api!, abi, contractAddress!);
const performingAccount = props.actingAccount;
const injector = await web3FromSource(performingAccount!.meta.source);
const set_profile_info = await contract.tx.setProfileInfo(
{
value: 0,
gasLimit: 187500000000,
},
props.name,
props.imgUrl
);
if (injector !== undefined) {
set_profile_info.signAndSend(
performingAccount!.address,
{ signer: injector.signer },
(result) => {}
);
}
};

// get following list function
export const getFollowingList = async (props: PropsGFIL) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getFollowingList(
"",
{
value: 0,
gasLimit: -1,
},
props.userId
);
if (output !== undefined && output !== null) {
props.setFollowingList(output.toHuman());
console.log(output.toHuman());
}
return;
};

// get follower list function
export const getFollowerList = async (props: PropsGFEL) => {
const contract = new ContractPromise(props.api!, abi, contractAddress);
const { gasConsumed, result, output } = await contract.query.getFollowerList(
"",
{
value: 0,
gasLimit: -1,
},
props.userId
);
if (output !== undefined && output !== null) {
props.setFollowerList(output.toHuman());
}
return;
};

profile.tsxFunctionimageUrlForUnknownには初めてのユーザーの画像のURLが入るのでご自分の好きな画像URLを入れてみてください。

ここではURLが長いのでimageUrlForUnknownに置き換えておきます。こちらの変数は環境変数から取得することになります。

これでコントラクト上の関数を呼び出せるようになりました。

🙋‍♂️ 質問する

わからないことがあれば、Discordの#astarでsection・Lesson名とともに質問をしてください 👋


次のセクションではUIで使用するパーツを記述して行きましょう! 🎉