news 2026/9/15 19:28:59

WTF Solidity 第33講:AirdropコントラクトでERC20トークンを一括エアドロップする

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WTF Solidity 第33講:AirdropコントラクトでERC20トークンを一括エアドロップする

WTF Solidity 第33講:AirdropコントラクトでERC20トークンを一括エアドロップする

【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity

エアドロップ(空投)は仮想通貨プロジェクトの代表的なマーケティング戦略であり、プロジェクトチームが特定のユーザー群体へ無料でトークンを配布する仕組みです。本記事では、WTF-Solidity の第33講に基づき、Solidity でAirdropスマートコントラクトを実装し、1 回のトランザクションで複数アドレスへERC20トークンおよびETHを一括送信する方法を解説します。ソースコードの内部ロジック、approveによる承認フロー、Remix での実践手順まで、実際に動かせる知識を身につけられます。

エアドロップとは

エアドロップとは、プロジェクトチームが特定のユーザー群体に対してトークンを無料で配布するマーケティング手法です。ユーザーはエアドロップの資格を得るために、製品のテスト、ニュースの共有、友人紹介などの簡単なタスクを完了するのが一般的です。プロジェクトチームにとってはシードユーザーの獲得、ユーザーにとってはトークン(価値)の獲得という、双方に利益がある仕組みです。

受取ユーザーは数百人・数千人規模に及ぶこともあるため、プロジェクトチームがアドレスごとに送金トランザクションを発行することは現実的ではありません。そこでスマートコントラクトを利用し、1 回のトランザクションで複数アドレスへ一括転送することで、エアドロップの効率を大幅に向上させることができます。本講で扱うAirdropコントラクトはまさにこの「一括転送」を実現するためのものです。

Airdropコントラクトの全体像

Airdropコントラクトのロジックは非常にシンプルです。ループを使用して、1 回の取引で複数のアドレスにERC20トークンを送信します。完全なソースコードは Languages/ja/33_Airdrop_ja/Airdrop.sol にあります(中国語版は 33_Airdrop/Airdrop.sol)。

// SPDX-License-Identifier: MIT // By 0xAA pragma solidity ^0.8.34; import "./IERC20.sol"; //import IERC20 /// @notice 複数のアドレスに対してトークンをエアドロするコントラクト contract Airdrop { mapping(address => uint256) failTransferList; // ... 以下に関数を実装 }

このコントラクトは次の要素から構成されます。

要素役割
getSum()uint配列の合計を返す補助関数
multiTransferToken()ERC20トークンのエアドロップを一括送信
multiTransferETH()ETHのエアドロップを一括送信
failTransferListETH送金に失敗したアドレスと金額を記録するマッピング
withdrawFromFailList()送金失敗者に払い戻しの機会を提供

また、同じファイル内にはエアドロップ対象となる独自実装のERC20トークンコントラクトも同梱されており、実践手順でそのまま利用できます。

getSum()関数:空投総量の計算

エアドロップの総量を計算するため、Airdropコントラクトはuint配列の合計を返す補助関数getSum()を持ちます(Languages/ja/33_Airdrop_ja/Airdrop.sol#L58-L63)。

// 配列の合計を計算する関数 function getSum(uint256[] calldata _arr) public pure returns (uint256 sum) { for (uint256 i = 0; i < _arr.length; i++) { sum = sum + _arr[i]; } }

ポイントは以下の通りです。

  • calldataキーワードで外部引数の配列を読み取り専用として受け取り、ガスコストを削減します。
  • pure指定のため、状態変数の読み書きを行わず、ブロックチェーンの状態を変更しません。
  • 戻り値は名前付き戻り値sumとして宣言されているため、関数末尾のreturn文を省略できます。

multiTransferToken()関数:ERC20トークンの一括エアドロップ

multiTransferToken()ERC20トークンのエアドロップを送信するコア関数です(Languages/ja/33_Airdrop_ja/Airdrop.sol#L16-L28)。3 つのパラメータを受け取ります。

パラメータ説明
_tokenaddress送信対象のERC20トークンコントラクトのアドレス
_addressesaddress[]エアドロップを受け取るユーザーアドレスの配列
_amountsuint256[]エアドロップ量の配列。_addressesの各アドレスに対応する数量
/// @notice 複数のアドレスに対してERC20トークンをエアドロする。使用前に承認が必要 /// /// @param _token エアドロップするERC20トークンのアドレス /// @param _addresses エアドロップするアドレスの配列 /// @param _amounts トークンの数量の配列(各アドレスにエアドロップするトークンの数量) function multiTransferToken(address _token, address[] calldata _addresses, uint256[] calldata _amounts) external { // チェック:_addressesと_amounts配列の長さが等しいこと require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL"); IERC20 token = IERC20(_token); // IERC20コントラクトインスタンスを宣言 uint256 _amountSum = getSum(_amounts); // エアドロップするトークンの総量を計算 // チェック:承認されたトークン数 > エアドロップするトークンの総量 require(token.allowance(msg.sender, address(this)) > _amountSum, "Need Approve ERC20 token"); // forループを使用し、transferFrom関数でエアドロップを送信 for (uint256 i; i < _addresses.length; i++) { token.transferFrom(msg.sender, _addresses[i], _amounts[i]); } }

この関数には 2 つの重要なチェック(require)があります。

  1. 配列長の一致チェック_addresses_amountsの 2 つの配列の長さが等しいことを検証します。等しくない場合、アドレスと金額の対応関係が崩れ、誤送金や金額不足が発生するため、最初に必ず検証します。
  2. 承認額チェック:エアドロップコントラクトへの承認額(allowance)が、エアドロップするトークン総量(getSum()の結果)より大きいことを検証します。承認が不足している場合は"Need Approve ERC20 token"でリバートします。

チェックを通過すると、forループでIERC20インターフェースのtransferFrom()を呼び出し、msg.sender(エアドロップの主催者)の残高から各受取アドレスへトークンを送信します。

IERC20インターフェースの役割

multiTransferToken()はトークンの実装に依存せず、Languages/ja/33_Airdrop_ja/IERC20.sol に定義された標準インターフェースIERC20を通じてトークンを操作します。インターフェースにはtotalSupply()balanceOf()transfer()allowance()approve()transferFrom()が宣言されており、エアドロップで実際に使うのは主に次の 2 つです。

  • allowance(owner, spender)ownerspenderに対して承認した残高を返します(view関数)。
  • transferFrom(from, to, amount):承認メカニズムを通じてfromからtoamountを転送し、転送分は呼び出し元のallowanceから差し引かれます。

IERC20インターフェースを介することで、Airdropコントラクトは特定のトークン実装に結合せず、インターフェースを満たす任意のERC20トークンをエアドロップできます。本講に同梱されている Languages/ja/33_Airdrop_ja/Airdrop.sol#L67-L120 のERC20コントラクトはこのインターフェースを実装しており、allowance[sender][msg.sender] -= amountの形でtransferFromの度に承認残高を減算する標準的なロジックを持ちます。エアドロップ前に主催者がapprove()で承認しておく必要があるのは、この仕組みのためです。

multiTransferETH()関数:ETHの一括エアドロップ

ERC20に加えて、AirdropコントラクトはETHの一括エアドロップにも対応しています(Languages/ja/33_Airdrop_ja/Airdrop.sol#L31-L47)。パラメータは 2 つです。

パラメータ説明
_addressesaddress payable[]エアドロップを受け取るユーザーアドレスの配列
_amountsuint256[]エアドロップ量の配列。_addressesの各アドレスに対応する数量
/// 複数のアドレスにETHを送金する function multiTransferETH(address payable[] calldata _addresses, uint256[] calldata _amounts) public payable { // チェック:_addressesと_amounts配列の長さが等しいこと require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL"); uint256 _amountSum = getSum(_amounts); // エアドロップするETHの総量を計算 // 送金されたETHがエアドロップの総量と等しいことをチェック require(msg.value == _amountSum, "Transfer amount error"); // forループを使用し、call関数でETHを送信 for (uint256 i = 0; i < _addresses.length; i++) { // コメントアウトされたコードにはDoS攻撃のリスクがあり、transferも推奨されない書き方です // DoS攻撃については WTF-Solidity の S09_DoS 講を参照 // _addresses[i].transfer(_amounts[i]); (bool success,) = _addresses[i].call{value: _amounts[i]}(""); if (!success) { failTransferList[_addresses[i]] = _amounts[i]; } } }

ETH版の特徴は次の通りです。

  • payable修飾子により、関数はトランザクションとともにETHを受け取ります。
  • 送金総額の一致チェックrequire(msg.value == _amountSum)で、トランザクションで送金されたETHがエアドロップ総量(getSum()の結果)と正確に一致することを検証します。過不足があればリバートします。
  • callによる送金_addresses[i].call{value: _amounts[i]}("")で各アドレスへETHを送信します。コメントアウトされたtransfer()は非推奨の送金方法です。

なぜtransferではなくcallを使うのか:DoS攻撃への対策

コード内でコメントアウトされている_addresses[i].transfer(_amounts[i])は、かつて一般的だったETH送金方法ですが、現在は推奨されません。理由は次の 2 点です。

  1. DoS 攻撃のリスクtransfer()は 2300 gas しか受け取り先へ渡せず、受け取り先がコントラクトの場合、この gas 不足で送金が常に失敗します。1 つでも失敗するとtransfer()は例外を投げてトランザクション全体がリバートするため、悪意のある(あるいは偶発的な)コントラクトアドレスが 1 つ含まれるだけで、エアドロップ全体が実行不可能になります。これは DoS 攻撃の典型例であり、WTF-Solidity では S09_DoS/readme.md として別講で詳しく解説されています。
  2. ガス上限の固定transfer()は送金先に渡す gas が 2300 に固定されており、ERC20の受信処理などを含むコントラクトではガス不足で失敗しやすくなります。

そこで本コントラクトは低レベルcallを採用し、失敗した場合は例外を投げずにsuccessフラグを受け取って処理を継続します。

failTransferListとwithdrawFromFailList

callによる送金が失敗した場合、そのアドレスと金額はfailTransferListマッピングに記録されます。

if (!success) { failTransferList[_addresses[i]] = _amounts[i]; }

これは「失敗を無視してループを継続する」ための記録であり、あわせて Languages/ja/33_Airdrop_ja/Airdrop.sol#L50-L56 のwithdrawFromFailList()が失敗者への払い戻し機会を提供します。

// エアドロップ失敗に対して能動的な操作の機会を提供 function withdrawFromFailList(address _to) public { uint256 failAmount = failTransferList[msg.sender]; require(failAmount > 0, "You are not in failed list"); failTransferList[msg.sender] = 0; (bool success,) = _to.call{value: failAmount}(""); require(success, "Fail withdraw"); }

この関数により、送金に失敗したアドレスの保有者が自らETHを取り戻せます。failTransferList[msg.sender]が 0 の場合は"You are not in failed list"でリバートし、払い戻し後は記録を 0 にリセットして二重払い戻しを防ぎます。

エアドロップの実践:Remixで一括送信を試す

ここからは Remix IDE(JavaScript VM 環境)で実際にエアドロップを実行する手順を解説します。日本語版のチュートリアル(Languages/ja/33_Airdrop_ja/readme.md)および中文版(33_Airdrop/readme.md)の手順に沿っています。

ステップ1:ERC20トークンコントラクトをデプロイし、10000トークンをmint

まず Languages/ja/33_Airdrop_ja/Airdrop.sol を Remix で開き、ファイル末尾に定義されているERC20コントラクトを選択してデプロイします。コンストラクタに代幣名とシンボル(例:WTF/WTF)を渡します。

デプロイ後、ERC20コントラクトのmint関数を呼び出し、自分(デプロイアドレス)に 10000 単位のトークンをミントします。mint0アドレスから呼び出し元へトークンを発行し、totalSupplyを増加させます。

ステップ2:Airdropエアドロップコントラクトをデプロイ

次にAirdropコントラクトをデプロイします。コンストラクタ引数は不要で、そのままtransactでデプロイできます。

ステップ3:ERC20のapprove()でAirdropコントラクトを承認

ERC20トークンコントラクトのapprove()関数を呼び出し、Airdropエアドロップコントラクトに対して 10000 単位のトークンを承認します。ここで指定するspenderAirdropコントラクトのアドレスです。

spender : Airdropコントラクトのアドレス amount : 10000

この承認がmultiTransferToken()の 2 つ目のrequire(承認額 ≥ 空投総量)を満たすための前提となります。

ステップ4:multiTransferToken()を実行してエアドロップ

AirdropコントラクトのmultiTransferToken()関数を実行します。_tokenにはERC20トークンコントラクトのアドレスを、_addresses_amountsには以下のように配列を入力します(中文版チュートリアル記載のテスト用アドレス例)。

// _addresses に入力 ["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"] // _amounts に入力 [100, 200]

トランザクション実行後、ログにTransferイベントが 2 件記録され、それぞれfrom(主催者)、to(受取アドレス)、value(送金額 100 / 200)が確認できます。

ステップ5:balanceOf()でエアドロップ結果を確認

ERC20トークンコントラクトのbalanceOf()関数を使用して、エアドロップ先アドレスのトークン残高を確認します。上の例では 2 つのアドレスの残高がそれぞれ100200に増加していれば、エアドロップ成功です。

まとめ

本講では、Solidityを使ってERC20トークンのエアドロップを送信する方法を学びました。

  • AirdropコントラクトはforループとIERC20.transferFrom()を組み合わせ、1 回のトランザクションで複数アドレスへERC20トークンを一括送信します。
  • 配列長の一致チェックと承認額チェックという 2 つのrequireにより、誤送金と承認不足を事前に防ぎます。
  • ETHエアドロップではcallを使用し、送金失敗をfailTransferListに記録した上でwithdrawFromFailList()による払い戻し機会を提供します。transfer()は DoS 攻撃のリスクがあるため推奨されません。
  • 実際の運用では、主催者は事前にapprove()でエアドロップコントラストへの承認を行う必要があり、承認額はエアドロップ総量以上でなければなりません。

これにより、個別送金に比べてエアドロップの効率を大幅に向上させることができます。実際のエアドロップでは、この基本形に加えてマークルツリーによるオフチェーン白名单検証(36_MerkleTree)、署名による権限確認(37_Signature)などを組み合わせることで、より安全でスケーラブルな運用が可能になります。

【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 19:21:35

Claude Code+微信小程序TDD云开发实战指南

1. 项目概述&#xff1a;这不是“用AI写代码”&#xff0c;而是重构小程序开发工作流“Claude Code 开发微信小程序实战&#xff1a;6 天做完 6 个里程碑”——这个标题乍看像营销话术&#xff0c;但在我带过三轮小程序团队、亲手交付过27个上线项目后&#xff0c;它背后的真实…

作者头像 李华