lesson-3_ユーザーボードを作成しよう
📃 ユーザーボードを作成しよう
このレッスンでは、ユーザーが保有するトークンの情報を一覧表示するボードを作成していきます。
ユーザーボードは以下の機能を持ちます。
- ユーザーのPrincipalを表示する
- トークンの保有量が表示される
- 各トークンに対しての操作(Faucet / Deposit / Withdraw)が行えるボタンを表示する
まずは、必要なファイルを作成します。
touch ./src/icp_basic_dex_frontend/src/components/UserBoard.jsx
続いて、DEX上で扱うトークンの情報をまとめておくためのファイルを作成します。
mkdir ./src/icp_basic_dex_frontend/src/utils && touch ./src/icp_basic_dex_frontend/src/utils/token.js
ここまでで、icp_basic_dex_frontend/src
ディレクトリ下のフォルダ構成が以下のようになっているでしょう。
src/
├── App.css
├── App.jsx
├── components/
│ ├── Header.jsx
+│ └── UserBoard.jsx
+├── utils/
+│ └── token.js
├── index.html
└── index.js
それでは、実装をしていきます。まずはトークンの情報をまとめておく配列をutils/token.js
ファイルに実装します。
[tokens.js]
import {
canisterId as GoldDIP20canisterId,
createActor as GoldDIP20CreateActor,
GoldDIP20,
} from "../../../declarations/GoldDIP20";
import {
canisterId as SilverDIP20canisterId,
createActor as SilverDIP20CreateActor,
SilverDIP20,
} from "../../../declarations/SilverDIP20";
// DEX上で扱うトークンのデータを配列に格納
export const tokens = [
{
canisterName: "GoldDIP20",
canister: GoldDIP20,
tokenSymbol: "TGLD",
createActor: GoldDIP20CreateActor,
canisterId: GoldDIP20canisterId,
},
{
canisterName: "SilverDIP20",
canister: SilverDIP20,
tokenSymbol: "TSLV",
createActor: SilverDIP20CreateActor,
canisterId: SilverDIP20canisterId,
},
];
次に、ユーザーボードの機能と、トークン一覧を表示するための実装をします。components/UserBoard.jsx
ファイルに以下のコードを記述しましょう。
[UserBoard.jsx]
import {
canisterId as faucetCanisterId,
createActor as faucetCreateActor,
} from "../../../declarations/faucet";
import {
canisterId as DEXCanisterId,
createActor as DEXCreateActor,
icp_basic_dex_backend as DEX,
} from "../../../declarations/icp_basic_dex_backend";
import { tokens } from "../utils/token";
import { Principal } from "@dfinity/principal";
export const UserBoard = (props) => {
const { agent, userPrincipal, userTokens, setUserTokens } = props;
const TOKEN_AMOUNT = 500;
const options = {
agent,
};
// ユーザーボード上のトークンデータを更新する
const updateUserToken = async (updateIndex) => {
// ユーザーが保有するトークン量を取得
const balance = await tokens[updateIndex].canister.balanceOf(userPrincipal);
// ユーザーがDEXに預けたトークン量を取得
const dexBalance = await DEX.getBalance(
userPrincipal,
Principal.fromText(tokens[updateIndex].canisterId)
);
setUserTokens(
userTokens.map((userToken, index) =>
index === updateIndex
? {
symbol: userToken.symbol,
balance: balance.toString(),
dexBalance: dexBalance.toString(),
fee: userToken.fee,
}
: userToken
)
);
};
const handleDeposit = async (updateIndex) => {
try {
const DEXActor = DEXCreateActor(DEXCanisterId, options);
const tokenActor = tokens[updateIndex].createActor(
tokens[updateIndex].canisterId,
options
);
// ユーザーの代わりにDEXがトークンを転送することを承認する
const resultApprove = await tokenActor.approve(
Principal.fromText(DEXCanisterId),
TOKEN_AMOUNT
);
if (!resultApprove.Ok) {
alert(`Error: ${Object.keys(resultApprove.Err)[0]}`);
return;
}
// DEXにトークンを入金する
const resultDeposit = await DEXActor.deposit(
Principal.fromText(tokens[updateIndex].canisterId)
);
if (!resultDeposit.Ok) {
alert(`Error: ${Object.keys(resultDeposit.Err)[0]}`);
return;
}
console.log(`resultDeposit: ${resultDeposit.Ok}`);
updateUserToken(updateIndex);
} catch (error) {
console.log(`handleDeposit: ${error} `);
}
};
const handleWithdraw = async (updateIndex) => {
try {
const DEXActor = DEXCreateActor(DEXCanisterId, options);
// DEXからトークンを出金する
const resultWithdraw = await DEXActor.withdraw(
Principal.fromText(tokens[updateIndex].canisterId),
TOKEN_AMOUNT
);
if (!resultWithdraw.Ok) {
alert(`Error: ${Object.keys(resultWithdraw.Err)[0]}`);
return;
}
console.log(`resultWithdraw: ${resultWithdraw.Ok}`);
updateUserToken(updateIndex);
} catch (error) {
console.log(`handleWithdraw: ${error} `);
}
};
// Faucetからトークンを取得する
const handleFaucet = async (updateIndex) => {
try {
const faucetActor = faucetCreateActor(faucetCanisterId, options);
const resultFaucet = await faucetActor.getToken(
Principal.fromText(tokens[updateIndex].canisterId)
);
if (!resultFaucet.Ok) {
alert(`Error: ${Object.keys(resultFaucet.Err)[0]}`);
return;
}
console.log(`resultFaucet: ${resultFaucet.Ok}`);
updateUserToken(updateIndex);
} catch (error) {
console.log(`handleFaucet: ${error}`);
}
};
return (
<>
<div className="user-board">
<h2>User</h2>
<li>principal ID: {userPrincipal.toString()}</li>
<table>
<tbody>
<tr>
<th>Token</th>
<th>Balance</th>
<th>DEX Balance</th>
<th>Fee</th>
<th>Action</th>
</tr>
{/* トークンのデータを一覧表示する */}
{userTokens.map((token, index) => {
return (
<tr key={`${index} : ${token.symbol} `}>
<td data-th="Token">{token.symbol}</td>
<td data-th="Balance">{token.balance}</td>
<td data-th="DEX Balance">{token.dexBalance}</td>
<td data-th="Fee">{token.fee}</td>
<td data-th="Action">
<div>
{/* トークンに対して行う操作(Deposit / Withdraw / Faucet)のボタンを表示 */}
<button
className="btn-green"
onClick={() => handleDeposit(index)}
>
Deposit
</button>
<button
className="btn-red"
onClick={() => handleWithdraw(index)}
>
Withdraw
</button>
<button
className="btn-blue"
onClick={() => handleFaucet(index)}
>
Faucet
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</>
);
};
トークンを取得するためのFaucetボタンを押した際に実行される、handleFaucet
関数を見てみましょう。ユーザーのPrincipalが必要な関数をコールする際には、ログイン認証後に作成したagent
を 用いて、キャニスターの関数をコールする必要があります。ここでは、faucetキャニスターのgetToken
関数を実行する際にユーザー Principalが必要になります。入金を行うhandleDeposit
関数や、出金を行うhandleWithdraw
関数でも同様にagentを使用します。
続いて、ログインをした時にユーザーが保有するトークンの情報を取得できるようにしたいと思います。前回のレッスンで作成したHeader.jsx
ファイルを編集しましょう。
まずは、props
の部分を以下ように更新します。トークンの情報を更新するための関数や、ユーザーの情報を保存するための関数を追加で渡します。
[Header.jsx]
export const Header = (props) => {
const {
+ updateOrderList,
+ updateUserTokens,
+ setAgent,
setUserPrincipal,
} = props;