XRP Ledger XRPの簡易的なWalletを作成
今回はWalletの仕組みを知りたいという好奇心で作成しました。
概要
XRP Ledger Test Netに接続してアカウントの作成とXRPの送金を体験できるアプリケーションです。
このアプリケーションはNext.js,TypeScript,axios,xrpl,Tailwindcssを使用しています。
Next.jsのAPIルートを利用して、XRP Ledgerに接続し処理を行うAPIを作成。
そのAPIにaxiosでリクエストを送信して、アカウントの作成やXRPの送金を行います。
XRP Ledgerとは
分散型のパブリックブロックチェーンでリップル社の決済ネットワークです。
今後はスマートコントラクトを実装する案もあります。
トランザクションの処理速度も速く、手数料も安い。
Next.js,TypeScript開発環境の構築
## Next.jsのプロジェクトを作成
yarn create next-app --typescript
## 作成したプロジェクトへ移動
cd [プロジェクト名]
## ローカルサーバー起動
yarn dev
Tailwindcssのインストール
下記を参照してください。
Axiosをインストール
APIにリクエストはAxiosを使います。
yarn add axios
xrpl.jsをインストール
XRP Ledgerへの接続を行うため、クライアントライブラリをインストールします。
yarn add xrpl
APIの実装
始めに下記のAPIを実装します。
- アカウントを作成
- シードフレーズを使ってアカウントの情報を取得
- XRPを送金
今回はNext.sjのAPIルートを使用してAPIを実装します。
pages/apiディレクトリの中に新しく各ファイルを作成して記述しました。
アカウントを作成するAPI
import type { NextApiRequest, NextApiResponse } from "next";
import { Client } from "xrpl";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// XRP Ledger Test Net に接続
const client = new Client("wss://s.altnet.rippletest.net:51233");
await client.connect();
// 新しいウォレットを作成
const wallet = (await client.fundWallet()).wallet;
// 残高を取得
const balance = await client.getXrpBalance(wallet.address);
// 表示するウォレットのデータを定義
const newWallet = {
address: wallet.address,
publicKey: wallet.publicKey,
privateKey: wallet.privateKey,
seed: wallet.seed,
balance,
};
// XRP Ledger Test Net との接続を解除
await client.disconnect();
// レスポンスでデータを返す
res.status(200).json(newWallet);
};
export default handler;
シードフレーズを使ってアカウントの情報を取得するAPI
import type { NextApiRequest, NextApiResponse } from "next";
import { Client, Wallet } from "xrpl";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// XRP Ledger Test Net に接続
const client = new Client("wss://s.altnet.rippletest.net:51233");
await client.connect();
// seedで自身のアカウントを取得
const wallet = Wallet.fromSeed(req.body.seed);
// 残高を取得
const balance = await client.getXrpBalance(wallet.address);
// 表示するウォレットのデータを定義
const currentWallet = {
address: wallet.address,
publicKey: wallet.publicKey,
privateKey: wallet.privateKey,
seed: req.body.seed,
balance,
};
// XRP Ledger Test Net との接続を解除
await client.disconnect();
// レスポンスでデータを返す
res.status(200).json(currentWallet);
};
export default handler;
XRPを送金するAPI
XRPを送金するAPIの作成を実装
pages/apiディレクトリの中に新しくファイルを作成
import type { NextApiRequest, NextApiResponse } from "next";
import { Client, Wallet, xrpToDrops, getBalanceChanges } from "xrpl";
type transferData = {
seed: string;
amount: number;
destination: string;
};
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// 受け取った値を定義
const transferData: transferData = req.body;
// XRP Ledger Test Net に接続
const client = new Client("wss://s.altnet.rippletest.net:51233");
await client.connect();
// seedで自身のアカウントを取得
const wallet = Wallet.fromSeed(transferData.seed);
// トランザクションを準備
const prepared = await client.autofill({
TransactionType: "Payment",
Account: wallet.address, // 送金するの自身のウォレットアドレス
Amount: xrpToDrops(transferData.amount), // 送金金額
Destination: transferData.destination, // 送信先のウォレットアドレス
});
// 準備されたトランザクションに署名。
const signed = wallet.sign(prepared);
//トランザクションを送信し、結果を待ちます。
const tx = await client.submitAndWait(signed.tx_blob);
// トランザクションによって生じた変更を取得
let mateData;
if (tx.result.meta !== undefined && typeof tx.result.meta !== "string") {
mateData = JSON.stringify(getBalanceChanges(tx.result.meta), null, 2);
}
// 送金後残高
const balance = await client.getXrpBalance(wallet.address);
// XRP Ledger Test Net との接続を解除
await client.disconnect();
// レスポンスでデータを返す
res.status(200).json({ balance, mateData });
};
export default handler;
データを取得して表示
次にクライアント側を実装します。
APIで取得した情報をReact HookのuseStateで変更して表示するように実装しています。
import type { NextPage } from "next";
import { useState } from "react";
type Wallet = {
address: string;
publicKey: string;
privateKey: string;
seed: string;
balance: string;
};
const Home: NextPage = () => {
// 初期値
const initialWallet = {
address: "",
publicKey: "",
privateKey: "",
seed: "",
balance: "",
};
// ウォレットのデータを入れるstateを作成
const [Wallet, setWallet] = useState<Wallet>(initialWallet);
return (
<main>
表示内容を記述
</main>
);
};
export default Home;
各機能の作成
下記の機能を実装します。
- アカウントを作成
- シードフレーズを使ってアカウントの情報を取得
- XRPを送金
アカウントを作成
const createAccount: ComponentProps<"form">["onSubmit"] = async (event) => {
event.preventDefault();
// リクエストの定義
const options: AxiosRequestConfig = {
url: "/api/account",
method: "POST",
};
// 新しいウォレットのデータを取得しstateを変更
const { data } = await axios(options);
setWallet(data);
};
シードフレーズを使ってアカウントの情報を取得
// seedをを使い既存のウォレットを取得する関数
const getWallet: ComponentProps<"form">["onSubmit"] = async (event) => {
event.preventDefault();
// 入力されたseedの文字列を使ってリクエストを送る
const seed: string = event.currentTarget.seed.value;
const options: AxiosRequestConfig = {
url: "/api/getWallet",
method: "POST",
data: {
seed,
},
};
// 取得したウォレットのデータでstateを変更
const { data } = await axios(options);
setWallet(data);
};
XRPを送金
// XRPを送金する関数
const transfer: ComponentProps<"form">["onSubmit"] = async (event) => {
event.preventDefault();
// 送金金額を定義
const amount = event.currentTarget.amount.value;
// 送金先のアドレスを定義
const destination = event.currentTarget.destination.value;
// 自身のseed,金額,送金先のアドレスをリクエストで送信
const options: AxiosRequestConfig = {
url: "/api/sendXRP",
method: "POST",
data: {
seed: Wallet?.seed,
destination,
amount,
},
};
const { data } = await axios(options);
// 送金後の残高を取得してstateを更新
setWallet({ ...Wallet, balance: data.balance });
};
取得したデータを表示する
return (
<main className="text-gray-600 bg-gray-100">
<div className="container px-5 py-24 mx-auto">
{/* // アカウントを作成 /// */}
<div className="p-4 lg:w-1/2 md:w-full mx-auto ">
<div className="border p-3 ">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
アカウントの作成
</h2>
<form onSubmit={createAccount} className="mt-5">
<button className="mt-5 block rounded border-0 bg-sky-500 py-2 px-6 text-lg text-white hover:bg-sky-600 focus:outline-none">
作成
</button>
</form>
</div>
</div>
<div className="p-4 lg:w-1/2 md:w-full mx-auto">
<div className="border p-3">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
アカウント
</h2>
<p className="mt-5 leading-relaxed">
seedを入力して既存のアカウントデータを取得する
</p>
<form onSubmit={getWallet} className="mt-5">
<label
htmlFor="seed"
className="text-sm leading-7 text-gray-600"
>
seed
</label>
<input
type="text"
name="seed"
id="seed"
className=" w-full rounded border border-gray-300 bg-white py-1 px-3 text-base leading-8 text-gray-700 outline-none transition-colors duration-200 ease-in-out focus:border-sky-500 focus:ring-2 focus:ring-sky-200"
/>
<button className="mt-5 block rounded border-0 bg-sky-500 py-2 px-6 text-lg text-white hover:bg-sky-600 focus:outline-none">
取得
</button>
</form>
</div>
</div>
{/* // アカウントを取得 // */}
<div className="p-4 lg:w-1/2 md:w-full mx-auto">
<div className="border p-3 ">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
アカウント情報
</h2>
<dl>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
address
</dt>
<dd className="leading-relaxed"> {Wallet?.address}</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
publicKey
</dt>
<dd className="leading-relaxed">
<p>{Wallet?.publicKey}</p>{" "}
</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
privateKey
</dt>
<dd className="leading-relaxed"> {Wallet?.privateKey}</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
seed
</dt>
<dd className="leading-relaxed"> {Wallet?.seed}</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
balance
</dt>
<dd className="leading-relaxed"> {Wallet?.balance}</dd>
</div>
</dl>
</div>
</div>
{/* // XRPを送金 // */}
<div className="p-4 lg:w-1/2 md:w-full mx-auto">
<div className="border p-3 ">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
XRPの送金
</h2>
{Wallet.address !== "" ? (
<form onSubmit={transfer}>
<div>
<label
htmlFor="destination"
className="text-sm leading-7 text-gray-600"
>
送信先アドレス
</label>
<input
type="text"
name="destination"
id="destination"
className="w-full rounded border border-gray-300 bg-white py-1 px-3 text-base leading-8 text-gray-700 outline-none transition-colors duration-200 ease-in-out focus:border-sky-500 focus:ring-2 focus:ring-sky-200"
/>
</div>
<div>
<label
htmlFor="amount"
className="text-sm leading-7 text-gray-600"
>
送金金額
</label>
<input
type="number"
name="amount"
id="amount"
className="w-full rounded border border-gray-300 bg-white py-1 px-3 text-base leading-8 text-gray-700 outline-none transition-colors duration-200 ease-in-out focus:border-sky-500 focus:ring-2 focus:ring-sky-200"
/>
</div>
<button className="mt-5 block rounded border-0 bg-sky-500 py-2 px-6 text-lg text-white hover:bg-sky-600 focus:outline-none">
送金
</button>
</form>
) : (
<p>アカウントを取得してください</p>
)}
</div>
</div>
</div>
</main>
);
完成したpages/index.tsx
import type { NextPage } from "next";
import { ComponentProps, useState } from "react";
import axios, { AxiosRequestConfig } from "axios";
type Wallet = {
address: string;
publicKey: string;
privateKey: string;
seed: string;
balance: string;
};
const Home: NextPage = () => {
// 初期値
const initialWallet = {
address: "",
publicKey: "",
privateKey: "",
seed: "",
balance: "",
};
// ウォレットのデータを入れるstateを作成
const [Wallet, setWallet] = useState<Wallet>(initialWallet);
// アカウントを作成する関数
const createAccount: ComponentProps<"form">["onSubmit"] = async (event) => {
event.preventDefault();
// リクエストの定義
const options: AxiosRequestConfig = {
url: "/api/account",
method: "POST",
};
// 新しいウォレットのデータを取得しstateを変更
const { data } = await axios(options);
setWallet(data);
};
// seedをを使い既存のウォレットを取得する関数
const getWallet: ComponentProps<"form">["onSubmit"] = async (event) => {
event.preventDefault();
// 入力されたseedの文字列を使ってリクエストを送る
const seed: string = event.currentTarget.seed.value;
const options: AxiosRequestConfig = {
url: "/api/getWallet",
method: "POST",
data: {
seed,
},
};
// 取得したウォレットのデータでstateを変更
const { data } = await axios(options);
setWallet(data);
};
// XRPを送金する関数
const transfer: ComponentProps<"form">["onSubmit"] = async (event) => {
event.preventDefault();
// 送金金額を定義
const amount = event.currentTarget.amount.value;
// 送金先のアドレスを定義
const destination = event.currentTarget.destination.value;
// 自身のseed,金額,送金先のアドレスをリクエストで送信
const options: AxiosRequestConfig = {
url: "/api/sendXRP",
method: "POST",
data: {
seed: Wallet?.seed,
destination,
amount,
},
};
const { data } = await axios(options);
// 送金後の残高を取得してstateを更新
setWallet({ ...Wallet, balance: data.balance });
};
return (
<main className="text-gray-600 bg-gray-100">
<div className="container px-5 py-24 mx-auto">
{/* // アカウントを作成 /// */}
<div className="p-4 lg:w-1/2 md:w-full mx-auto ">
<div className="border p-3 ">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
アカウントの作成
</h2>
<form onSubmit={createAccount} className="mt-5">
<button className="mt-5 block rounded border-0 bg-sky-500 py-2 px-6 text-lg text-white hover:bg-sky-600 focus:outline-none">
作成
</button>
</form>
</div>
</div>
<div className="p-4 lg:w-1/2 md:w-full mx-auto">
<div className="border p-3">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
アカウント
</h2>
<p className="mt-5 leading-relaxed">
seedを入力して既存のアカウントデータを取得する
</p>
<form onSubmit={getWallet} className="mt-5">
<label
htmlFor="seed"
className="text-sm leading-7 text-gray-600"
>
seed
</label>
<input
type="text"
name="seed"
id="seed"
className=" w-full rounded border border-gray-300 bg-white py-1 px-3 text-base leading-8 text-gray-700 outline-none transition-colors duration-200 ease-in-out focus:border-sky-500 focus:ring-2 focus:ring-sky-200"
/>
<button className="mt-5 block rounded border-0 bg-sky-500 py-2 px-6 text-lg text-white hover:bg-sky-600 focus:outline-none">
取得
</button>
</form>
</div>
</div>
{/* // アカウントを取得 // */}
<div className="p-4 lg:w-1/2 md:w-full mx-auto">
<div className="border p-3 ">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
アカウント情報
</h2>
<dl>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
address
</dt>
<dd className="leading-relaxed"> {Wallet?.address}</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
publicKey
</dt>
<dd className="leading-relaxed">
<p>{Wallet?.publicKey}</p>{" "}
</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
privateKey
</dt>
<dd className="leading-relaxed"> {Wallet?.privateKey}</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
seed
</dt>
<dd className="leading-relaxed"> {Wallet?.seed}</dd>
</div>
<div className="mt-6 flex-grow sm:mt-0">
<dt className="title-font mb-1 text-xl font-medium text-gray-900">
balance
</dt>
<dd className="leading-relaxed"> {Wallet?.balance}</dd>
</div>
</dl>
</div>
</div>
{/* // XRPを送金 // */}
<div className="p-4 lg:w-1/2 md:w-full mx-auto">
<div className="border p-3 ">
<h2 className="title-font mb-4 text-3xl font-medium text-gray-900 sm:text-4xl">
XRPの送金
</h2>
{Wallet.address !== "" ? (
<form onSubmit={transfer}>
<div>
<label
htmlFor="destination"
className="text-sm leading-7 text-gray-600"
>
送信先アドレス
</label>
<input
type="text"
name="destination"
id="destination"
className="w-full rounded border border-gray-300 bg-white py-1 px-3 text-base leading-8 text-gray-700 outline-none transition-colors duration-200 ease-in-out focus:border-sky-500 focus:ring-2 focus:ring-sky-200"
/>
</div>
<div>
<label
htmlFor="amount"
className="text-sm leading-7 text-gray-600"
>
送金金額
</label>
<input
type="number"
name="amount"
id="amount"
className="w-full rounded border border-gray-300 bg-white py-1 px-3 text-base leading-8 text-gray-700 outline-none transition-colors duration-200 ease-in-out focus:border-sky-500 focus:ring-2 focus:ring-sky-200"
/>
</div>
<button className="mt-5 block rounded border-0 bg-sky-500 py-2 px-6 text-lg text-white hover:bg-sky-600 focus:outline-none">
送金
</button>
</form>
) : (
<p>アカウントを取得してください</p>
)}
</div>
</div>
</div>
</main>
);
};
export default Home;
アプリケーションの使い方
1.下記のコマンドでローカルサーバーを起動します
yarn dev
- 作成ボタンを押してアカウントを作成します。
作成したアカウントはseedを使って復元できます。
使い回しが可能なのでseedをメモして使用してください。
(送金を体験するためは2つアカウントを作成する必要があります。)
3.XRP送金
送金先のアドレスと送金金額を入力して送金できます。
作成したアカウントで試してみてください。
まとめ
暗号資産のウォレットでは、どのようなデータをやり取りするのか理解することができました。
XRPは送金速度が早くSWIFTの代替になり得る可能性は0ではないと思って情報を追っていく予定です。
今回は簡易的なテストネットでのウォレットの作成でしたが、今後はセキュリティについて多くのことを学習して、本番環境で使用できる安全なウォレットを作成したいと思います。
ご一読いただきありがとうございました。
Discussion