暗号資産の交換所や決済サービスを開発する際、避けて通れないのがカストディアル・ウォレット(預かり資産管理)の構築です。ユーザーの入金用アドレスを監視し、届いた資金を安全かつ効率的に回収(スイープ)する仕組みは、システムの信頼性に直結します。
本記事では、Node.jsとethers.jsを用いた堅牢なスイープ機構の設計と、運用の現場で求められるアーキテクチャについて解説します。
目次
1. オンチェーンの事実と元帳を同期させる「冪等性」の重要性
カストディアル・システムの核心は、ブロックチェーン上の「事実」を、自社のデータベースという「元帳」にいかに正確に反映させるかです。ここで重要なのが、同じ処理を何度行っても結果が変わらない「冪等性(べきとうせい)」の確保です。
私の経験上、ネットワークの遅延やAPIのエラーによる「リトライ」は必ず発生します。そのため、デポジットを監視するリスナーと資金を回収するスイーパーを分離し、DBのトランザクションを用いて「一度処理したTxHashは二度と受け付けない」ロジックを徹底する必要があります。
2. ガスコストを40%削減するバッチ処理と運用戦略
ユーザーが入金するたびに即座にスイープを実行すると、送金手数料(ガス代)が嵩み、ビジネスの収益を圧迫します。実際の運用現場では、以下の戦略を組み合わせてコストを最適化します。
- バッチスイープ:チェーンの混雑状況や蓄積額を見極め、適切なタイミングでまとめて回収する。
- Hot/Coldの論理分割:出金用の「Hotウォレット」と、資産運用・保管用の「Coldウォレット」を分け、余剰資産をAaveなどで利回り運用(Yield Deployment)に回す。
- フィーダープロセスの設計:ERC20トークンの回収には、入金アドレス側にガス代(ETHなど)が必要です。これらを自動で補充する仕組みも忘れずに設計しましょう。
3. 現場でハマる「チェーンの再編成(Reorg)」への対策
最も頻発するトラブルの一つが、チェーンの再編成による「確定したはずのトランザクションの消失」です。これを防ぐためには、十分な承認数を待つロジックが不可欠です。
- ファイナリティの待機:最低でも12ブロック、安全を期すならそれ以上の確認を待ってから元帳に反映させます。
- DBの整合性確保:残高の更新と処理済みログの作成を、必ずアトミックなトランザクション内で実行します。
async function processDeposit(txHash, userId) {
const receipt = await provider.getTransactionReceipt(txHash);
if (!receipt || receipt.confirmations < 12) return;
const session = await db.startSession();
session.startTransaction();
try {
const alreadyProcessed = await TxLog.findOne({ hash: txHash }).session(session);
if (!alreadyProcessed) {
// ユーザー残高の更新とログ記録をセットで行う
await UserBalance.updateOne({ userId }, { $inc: { balance: receipt.value } }).session(session);
await TxLog.create([{ hash: txHash, status: 'PROCESSED' }], { session });
}
await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
}
}まとめ:信頼されるインフラを目指して
カストディアル基盤の構築は、単に資金を移動させるだけでなく、「不確実なチェーンの状態」を「確実な内部データ」へと変換する高度な設計が求められます。
冪等性の担保とガス代の最適化を両立させ、スケーラブルで安全なプラットフォームを構築しましょう。

