【Web3 / Blockchain/React/TypeScript】Solanaでのトランザクション処理をコードから学ぶ
はじめに
仮想通貨やブロックチェーン上での取引は、実際にどのようにプログラムコードで行われているのでしょうか?
この記事では Solana を例に、Web3 ならではのネットワーク構造・RPC ノード・トランザクションの仕組みを、シンプルな入金(Deposit)コードをとおして解説します。
1. Web3 / Blockchain とは?
1-1. 従来のWeb2との違い
- Web2: 中央サーバー or クラウド (例: AWS, GCP) がメイン。
- Web3: 分散型ネットワークを使い、誰でもトランザクション(取引)を検証できる。
Solanaのようなブロックチェーンでは、各ノードがトランザクションを検証してブロックに取り込み、一貫した状態を保ちます。
2. RPC ノードとネットワーク (Devnet / Mainnet / Testnet)
2-1. RPCノードとは
- ブロックチェーンとのやりとりをする「ゲートウェイサーバー」的存在。
- Solana では、
Connection(RPC_URL)
オブジェクトを介してブロックチェーンにアクセス。 - 公式RPC (
https://api.mainnet-beta.solana.com
) だけでなく、Helius や QuickNode など、外部のインフラプロバイダが提供するRPCノードもある。
2-2. Devnet / Mainnet / Testnet の違い
- Mainnet: 本番環境。実際の SOL やトークンに価値がある。
- Devnet: 開発用ネットワーク。試験用に使われる。
- Testnet: 主にアップグレードや新機能テストに使われる公式ネットワーク。
多くの開発者は Mainnet の貴重な資産をリスクにさらさないため、Devnet で最初に機能を試し、問題がなければ Mainnet にデプロイします。
3. コード全体像 – Solana の Depositトランザクション
以下は React + TypeScript で書かれたサンプルコードです。
Wallet(Solflare) と接続して、ユーザーが入力した金額(USDC)をVaultアドレスへ送金(Deposit)する流れを示します。
import React, { useState, useEffect, useRef } from 'react';
import Solflare from '@solflare-wallet/sdk';
import {
Connection,
PublicKey,
SendTransactionError,
Transaction,
} from '@solana/web3.js';
import { useNavigate } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { Line } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import {
getAssociatedTokenAddress,
createAssociatedTokenAccountInstruction,
createTransferInstruction,
TOKEN_PROGRAM_ID,
} from '@solana/spl-token';
// ChartJS init
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
// Env variables
const IS_PRODUCTION = process.env.REACT_APP_ENV === 'production';
const RPC_URL = IS_PRODUCTION
? process.env.REACT_APP_RPC_URL_PROD!
: process.env.REACT_APP_RPC_URL_DEV!;
const USDC_MINT_ADDRESS = IS_PRODUCTION
? process.env.REACT_APP_USDC_MINT_PROD!
: process.env.REACT_APP_USDC_MINT_DEV!;
const VAULT_PUBLIC_KEY = IS_PRODUCTION
? process.env.REACT_APP_VAULT_ADDRESS_PROD!
: process.env.REACT_APP_VAULT_ADDRESS_DEV!;
// PostDepositModal ...
interface PostDepositModalProps {
depositSentAmount: number;
preDepositBalance: number;
postBalance: number;
onClose: () => void;
onAutoDisconnect: () => void;
}
const PostDepositModal: React.FC<PostDepositModalProps> = (...) => {
// 省略
};
const SuperchargerVault: React.FC = () => {
// React state ...
const walletRef = useRef<Solflare>(new Solflare());
const connection = useRef<Connection>(new Connection(RPC_URL)).current;
// ...
const handleDeposit = async () => {
console.log('[handleDeposit] Deposit button clicked!');
if (isDepositing) return; // 連打防止
if (depositInProgressOtherTab) {
// 他タブ実行中のフラグ
return;
}
setIsDepositing(true);
localStorage.setItem('SUPERCHARGER_DEPOSIT_IN_PROGRESS', 'true');
try {
// 金額チェック
const val = parseFloat(depositAmount);
if (val > (usdcBalance || 0)) {
// 残高超過エラー
}
// Transaction構築
const wallet = walletRef.current;
if (!wallet.publicKey) throw new Error('No wallet');
const { blockhash } = await connection.getLatestBlockhash('finalized');
const depositLamports = Math.round(val * 1_000_000);
const usdcMint = new PublicKey(USDC_MINT_ADDRESS);
const vaultPubkey = new PublicKey(VAULT_PUBLIC_KEY);
const userTokenAccount = await getAssociatedTokenAddress(usdcMint, wallet.publicKey);
const vaultTokenAccount = await getAssociatedTokenAddress(usdcMint, vaultPubkey);
const transferIx = createTransferInstruction(
userTokenAccount,
vaultTokenAccount,
wallet.publicKey,
depositLamports,
[],
TOKEN_PROGRAM_ID
);
const transaction = new Transaction().add(transferIx);
transaction.feePayer = wallet.publicKey;
transaction.recentBlockhash = blockhash;
// 署名
const signedTx = await wallet.signTransaction(transaction);
// 送信
const signature = await connection.sendRawTransaction(signedTx.serialize(), {
skipPreflight: false,
preflightCommitment: 'processed',
});
console.log('[handleDeposit] signature:', signature);
// 確認
const confirm = await connection.confirmTransaction(signature, 'finalized');
if (confirm.value.err) {
throw new Error('トランザクションの確認に失敗しました');
}
// 成功処理 ...
} catch (error) {
// エラー処理 ...
} finally {
setIsDepositing(false);
localStorage.setItem('SUPERCHARGER_DEPOSIT_IN_PROGRESS', 'false');
}
};
// ReactのJSXレンダリング ...
return (
<div>
{/* Vault UI / Wallet Connect UI */}
</div>
);
};
export default SuperchargerVault;
</details>
4. Deposit処理の流れを図解 (mermaid)
Solana の トランザクション送信 と ウォレットの署名 の一連のやりとりを、簡単なシーケンス図で示します。
このように、ウォレット拡張が秘密鍵で署名 し、アプリは署名済みトランザクションをネットワークに送る、という流れがポイントです。
5. Signature(署名)の仕組み
- Solana上のトランザクションは、秘密鍵 で署名されることで「このユーザーが正当に送金を発行した」ことを証明します。
- 公開鍵(=アドレス) に対応する秘密鍵で署名すると、ブロックチェーン上のバリデータは署名の正当性を検証し、本物の所有者が送金要求した と判断します。
- コードでは、ウォレット拡張(Solflare) などが代わりに署名を行い、その結果(署名済みトランザクション)を受け取って
connection.sendRawTransaction()
で送信します。
6. Devnet / Testnet / Mainnet の使い分け
-
Mainnet
- 本番。SOL やトークンに実際の価値がある。
- 間違いが許されにくい。
-
Devnet
- 開発用ネットワーク。トークンはテスト用で、本物の価値はない。
-
requestAirdrop()
などで自由にSOLを入手し、トランザクションの挙動を試せる。
-
Testnet
- Mainnet のアップグレード前テストなどに使われることが多い。
- Devnet に比べると、さらに公式的・安定的に試験されるが、一般ユーザはあまり使わない場合も。
このサンプルコードでは .env
に REACT_APP_ENV=development
を設定し、RPC_URL
も Devnet 用に切り替えると、Devnet で同様のフローを実行できます。
7. コードのポイント
-
多重送信の防止
-
isDepositing
ステートと、localStorage
フラグSUPERCHARGER_DEPOSIT_IN_PROGRESS
を併用して、同タブでの連打や別タブとの競合を防ぐ。
-
-
Legacy Transaction
- Solana にはVersioned Transaction等ありますが、このサンプルは
Legacy Transaction
でシンプルに書いています。
- Solana にはVersioned Transaction等ありますが、このサンプルは
-
Token Transfer
-
createTransferInstruction(userTokenAccount, vaultTokenAccount, ...)
で USDC を送金。 - Solanaの SPL Token プログラム (
TOKEN_PROGRAM_ID
) を指定してトランスファーを行う。
-
8. 安全性について
-
ウォレット拡張(Phantom, Solflareなど) が秘密鍵を管理
- アプリ本体には秘密鍵を渡さないため、ユーザー側がセキュリティを確保できる。
-
Transaction Signature
- ブロックチェーン上のノードは、公開鍵とトランザクションの署名を突き合わせて正当性を検証。
-
Devnetで試験
- Mainnet での大きな資産移動をいきなり行うのではなく、Devnet でテスト → 問題なければ Mainnet に移行するのが一般的。
9. まとめ
- このコードからわかるように、ウォレット署名 → RPCノードに送信 → ブロックチェーンで検証 という三段階で Deposit が完了します。
- Devnet / Mainnet / Testnet は同じコードでもRPCエンドポイントとMintアドレスを変えるだけで接続先を切り替えられます。
- Signature(署名) は秘密鍵を使った暗号技術で、ユーザー本人の取引を証明する重要な仕組みです。
これが Web3 / Blockchain 流の「トランザクションをコードで見る」アプローチの一例でした。
これから Web3 アプリケーション開発を始める方は、まずはDevnet でトランザクションを送るコードを動かして、ウォレット署名 や RPCノード の動きを体感してみるのをおすすめします!
Discussion