diff --git a/Languages/en/README.md b/Languages/en/README.md
index fa04abb57..49c9ff35e 100644
--- a/Languages/en/README.md
+++ b/Languages/en/README.md
@@ -1,6 +1,6 @@

-**[中文](https://github.com/AmazingAng/WTF-Solidity) / [Español](../es/README.md) / [Português Brasileiro](../pt-br/README.md)**
+**[中文](https://github.com/AmazingAng/WTF-Solidity) / [Español](../es/README.md) / [Português Brasileiro](../pt-br/README.md) / [日本語](../ja/README.md)**
# WTF Solidity
diff --git a/Languages/es/README.md b/Languages/es/README.md
index 3e61e38a9..f1803c435 100644
--- a/Languages/es/README.md
+++ b/Languages/es/README.md
@@ -1,6 +1,6 @@

-**[中文版本](https://github.com/AmazingAng/WTF-Solidity) / [English Version](../en/README.md) / [Português Brasileiro](../pt-br/README.md)**
+**[中文版本](https://github.com/AmazingAng/WTF-Solidity) / [English Version](../en/README.md) / [Português Brasileiro](../pt-br/README.md) / [日本語版](../ja/README.md)**
# WTF Solidity
diff --git a/Languages/ja/34_ERC721_ja/ERC721.sol b/Languages/ja/34_ERC721_ja/ERC721.sol
new file mode 100644
index 000000000..aecab1be6
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/ERC721.sol
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: MIT
+// by 0xAA
+pragma solidity ^0.8.21;
+
+import "./IERC165.sol";
+import "./IERC721.sol";
+import "./IERC721Receiver.sol";
+import "./IERC721Metadata.sol";
+import "./String.sol";
+
+contract ERC721 is IERC721, IERC721Metadata{
+ using Strings for uint256; // Stringsライブラリを使用
+
+ // トークン名
+ string public override name;
+ // トークンシンボル
+ string public override symbol;
+ // tokenId から owner address への所有者マッピング
+ mapping(uint => address) private _owners;
+ // address から保有数量への保有量マッピング
+ mapping(address => uint) private _balances;
+ // tokenID から承認アドレスへの承認マッピング
+ mapping(uint => address) private _tokenApprovals;
+ // ownerアドレス から operatorアドレスへの一括承認マッピング
+ mapping(address => mapping(address => bool)) private _operatorApprovals;
+
+ // エラー 無効な受信者
+ error ERC721InvalidReceiver(address receiver);
+
+ /**
+ * コンストラクタ、`name` と`symbol` を初期化 .
+ */
+ constructor(string memory name_, string memory symbol_) {
+ name = name_;
+ symbol = symbol_;
+ }
+
+ // IERC165インターフェースsupportsInterfaceを実装
+ function supportsInterface(bytes4 interfaceId)
+ external
+ pure
+ override
+ returns (bool)
+ {
+ return
+ interfaceId == type(IERC721).interfaceId ||
+ interfaceId == type(IERC165).interfaceId ||
+ interfaceId == type(IERC721Metadata).interfaceId;
+ }
+
+ // IERC721のbalanceOfを実装、_balances変数を使用してownerアドレスのbalanceをクエリ。
+ function balanceOf(address owner) external view override returns (uint) {
+ require(owner != address(0), "owner = zero address");
+ return _balances[owner];
+ }
+
+ // IERC721のownerOfを実装、_owners変数を使用してtokenIdのownerをクエリ。
+ function ownerOf(uint tokenId) public view override returns (address owner) {
+ owner = _owners[tokenId];
+ require(owner != address(0), "token doesn't exist");
+ }
+
+ // IERC721のisApprovedForAllを実装、_operatorApprovals変数を使用してownerアドレスが保有するNFTをoperatorアドレスに一括承認しているかをクエリ。
+ function isApprovedForAll(address owner, address operator)
+ external
+ view
+ override
+ returns (bool)
+ {
+ return _operatorApprovals[owner][operator];
+ }
+
+ // IERC721のsetApprovalForAllを実装、保有トークンを全てoperatorアドレスに承認。_setApprovalForAll関数を呼び出し。
+ function setApprovalForAll(address operator, bool approved) external override {
+ _operatorApprovals[msg.sender][operator] = approved;
+ emit ApprovalForAll(msg.sender, operator, approved);
+ }
+
+ // IERC721のgetApprovedを実装、_tokenApprovals変数を使用してtokenIdの承認アドレスをクエリ。
+ function getApproved(uint tokenId) external view override returns (address) {
+ require(_owners[tokenId] != address(0), "token doesn't exist");
+ return _tokenApprovals[tokenId];
+ }
+
+ // 承認関数。_tokenApprovalsを調整して、to アドレスに tokenId の操作を承認し、同時にApprovalイベントを発行。
+ function _approve(
+ address owner,
+ address to,
+ uint tokenId
+ ) private {
+ _tokenApprovals[tokenId] = to;
+ emit Approval(owner, to, tokenId);
+ }
+
+ // IERC721のapproveを実装、tokenIdを to アドレスに承認。条件:toはownerではなく、msg.senderはownerまたは承認アドレス。_approve関数を呼び出し。
+ function approve(address to, uint tokenId) external override {
+ address owner = _owners[tokenId];
+ require(
+ msg.sender == owner || _operatorApprovals[owner][msg.sender],
+ "not owner nor approved for all"
+ );
+ _approve(owner, to, tokenId);
+ }
+
+ // spenderアドレスがtokenIdを使用できるかをクエリ(ownerまたは承認アドレスである必要がある)
+ function _isApprovedOrOwner(
+ address owner,
+ address spender,
+ uint tokenId
+ ) private view returns (bool) {
+ return (spender == owner ||
+ _tokenApprovals[tokenId] == spender ||
+ _operatorApprovals[owner][spender]);
+ }
+
+ /*
+ * 転送関数。_balancesと_owner変数を調整して tokenId を from から to に転送し、同時にTransferイベントを発行。
+ * 条件:
+ * 1. tokenId が from によって所有されている
+ * 2. to が0アドレスではない
+ */
+ function _transfer(
+ address owner,
+ address from,
+ address to,
+ uint tokenId
+ ) private {
+ require(from == owner, "not owner");
+ require(to != address(0), "transfer to the zero address");
+
+ _approve(owner, address(0), tokenId);
+
+ _balances[from] -= 1;
+ _balances[to] += 1;
+ _owners[tokenId] = to;
+
+ emit Transfer(from, to, tokenId);
+ }
+
+ // IERC721のtransferFromを実装、非安全転送、推奨されません。_transfer関数を呼び出し
+ function transferFrom(
+ address from,
+ address to,
+ uint tokenId
+ ) external override {
+ address owner = ownerOf(tokenId);
+ require(
+ _isApprovedOrOwner(owner, msg.sender, tokenId),
+ "not owner nor approved"
+ );
+ _transfer(owner, from, to, tokenId);
+ }
+
+ /**
+ * 安全転送、tokenId トークンを from から to に安全に転送し、コントラクト受信者がERC721プロトコルを理解しているかをチェックしてトークンが永続的にロックされることを防止。_transfer関数と_checkOnERC721Received関数を呼び出し。条件:
+ * from は0アドレスではない.
+ * to は0アドレスではない.
+ * tokenId トークンが存在し、from によって所有されている.
+ * to がスマートコントラクトの場合、IERC721Receiver-onERC721Receivedをサポートする必要がある.
+ */
+ function _safeTransfer(
+ address owner,
+ address from,
+ address to,
+ uint tokenId,
+ bytes memory _data
+ ) private {
+ _transfer(owner, from, to, tokenId);
+ _checkOnERC721Received(from, to, tokenId, _data);
+ }
+
+ /**
+ * IERC721のsafeTransferFromを実装、安全転送、_safeTransfer関数を呼び出し。
+ */
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint tokenId,
+ bytes memory _data
+ ) public override {
+ address owner = ownerOf(tokenId);
+ require(
+ _isApprovedOrOwner(owner, msg.sender, tokenId),
+ "not owner nor approved"
+ );
+ _safeTransfer(owner, from, to, tokenId, _data);
+ }
+
+ // safeTransferFromオーバーロード関数
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint tokenId
+ ) external override {
+ safeTransferFrom(from, to, tokenId, "");
+ }
+
+ /**
+ * ミント関数。_balancesと_owners変数を調整してtokenIdをミントし、to に転送、同時にTransferイベントを発行。ミント関数。_balancesと_owners変数を調整してtokenIdをミントし、to に転送、同時にTransferイベントを発行。
+ * このmint関数は誰でも呼び出すことができ、実際の使用では開発者が書き直して条件を追加する必要があります。
+ * 条件:
+ * 1. tokenIdがまだ存在しない。
+ * 2. toが0アドレスではない.
+ */
+ function _mint(address to, uint tokenId) internal virtual {
+ require(to != address(0), "mint to zero address");
+ require(_owners[tokenId] == address(0), "token already minted");
+
+ _balances[to] += 1;
+ _owners[tokenId] = to;
+
+ emit Transfer(address(0), to, tokenId);
+ }
+
+ // バーン関数、_balancesと_owners変数を調整してtokenIdを破棄し、同時にTransferイベントを発行。条件:tokenIdが存在する。
+ function _burn(uint tokenId) internal virtual {
+ address owner = ownerOf(tokenId);
+ require(msg.sender == owner, "not owner of token");
+
+ _approve(owner, address(0), tokenId);
+
+ _balances[owner] -= 1;
+ delete _owners[tokenId];
+
+ emit Transfer(owner, address(0), tokenId);
+ }
+
+ // _checkOnERC721Received:関数、to がコントラクトの場合にIERC721Receiver-onERC721Receivedを呼び出し、tokenId が誤ってブラックホールに転送されることを防ぐ。
+ function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
+ if (to.code.length > 0) {
+ try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
+ if (retval != IERC721Receiver.onERC721Received.selector) {
+ revert ERC721InvalidReceiver(to);
+ }
+ } catch (bytes memory reason) {
+ if (reason.length == 0) {
+ revert ERC721InvalidReceiver(to);
+ } else {
+ /// @solidity memory-safe-assembly
+ assembly {
+ revert(add(32, reason), mload(reason))
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * IERC721MetadataのtokenURI関数を実装、metadataをクエリ。
+ */
+ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
+ require(_owners[tokenId] != address(0), "Token Not Exist");
+
+ string memory baseURI = _baseURI();
+ return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
+ }
+
+ /**
+ * {tokenURI}のBaseURIを計算、tokenURIはbaseURIとtokenIdを連結したもので、開発者が書き直す必要がある。
+ * BAYCのbaseURIは ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+ */
+ function _baseURI() internal view virtual returns (string memory) {
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/34_ERC721_ja/IERC165.sol b/Languages/ja/34_ERC721_ja/IERC165.sol
new file mode 100644
index 000000000..5f223e82a
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/IERC165.sol
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.0;
+
+/**
+ * @dev ERC165標準インターフェース, 詳細は
+ * https://eips.ethereum.org/EIPS/eip-165[EIP].
+ *
+ * コントラクトはサポートするインターフェースを宣言し、他のコントラクトが確認できます
+ *
+ */
+interface IERC165 {
+ /**
+ * @dev コントラクトがクエリされた`interfaceId`を実装している場合はtrueを返します
+ * ルールの詳細:https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
+ *
+ */
+ function supportsInterface(bytes4 interfaceId) external view returns (bool);
+}
\ No newline at end of file
diff --git a/Languages/ja/34_ERC721_ja/IERC721.sol b/Languages/ja/34_ERC721_ja/IERC721.sol
new file mode 100644
index 000000000..b6502049c
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/IERC721.sol
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.0;
+
+import "./IERC165.sol";
+
+/**
+ * @dev ERC721標準インターフェース.
+ */
+interface IERC721 is IERC165 {
+ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
+ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
+ event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
+
+ function balanceOf(address owner) external view returns (uint256 balance);
+
+ function ownerOf(uint256 tokenId) external view returns (address owner);
+
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes calldata data
+ ) external;
+
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 tokenId
+ ) external;
+
+ function transferFrom(
+ address from,
+ address to,
+ uint256 tokenId
+ ) external;
+
+ function approve(address to, uint256 tokenId) external;
+
+ function setApprovalForAll(address operator, bool _approved) external;
+
+ function getApproved(uint256 tokenId) external view returns (address operator);
+
+ function isApprovedForAll(address owner, address operator) external view returns (bool);
+}
\ No newline at end of file
diff --git a/Languages/ja/34_ERC721_ja/IERC721Metadata.sol b/Languages/ja/34_ERC721_ja/IERC721Metadata.sol
new file mode 100644
index 000000000..7745bc42c
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/IERC721Metadata.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+interface IERC721Metadata {
+ function name() external view returns (string memory);
+
+ function symbol() external view returns (string memory);
+
+ function tokenURI(uint256 tokenId) external view returns (string memory);
+}
\ No newline at end of file
diff --git a/Languages/ja/34_ERC721_ja/IERC721Receiver.sol b/Languages/ja/34_ERC721_ja/IERC721Receiver.sol
new file mode 100644
index 000000000..dba1960cd
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/IERC721Receiver.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+// ERC721受信者インターフェース:コントラクトはこのインターフェースを実装して安全転送でERC721を受信する必要があります
+interface IERC721Receiver {
+ function onERC721Received(
+ address operator,
+ address from,
+ uint tokenId,
+ bytes calldata data
+ ) external returns (bytes4);
+}
\ No newline at end of file
diff --git a/Languages/ja/34_ERC721_ja/String.sol b/Languages/ja/34_ERC721_ja/String.sol
new file mode 100644
index 000000000..1e11464ae
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/String.sol
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
+
+pragma solidity ^0.8.21;
+
+/**
+ * @dev String操作.
+ */
+library Strings {
+ bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
+ uint8 private constant _ADDRESS_LENGTH = 20;
+
+ /**
+ * @dev `uint256`をASCII `string`の10進表現に変換します.
+ */
+ function toString(uint256 value) internal pure returns (string memory) {
+ // Inspired by OraclizeAPI's implementation - MIT licence
+ // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
+
+ if (value == 0) {
+ return "0";
+ }
+ uint256 temp = value;
+ uint256 digits;
+ while (temp != 0) {
+ digits++;
+ temp /= 10;
+ }
+ bytes memory buffer = new bytes(digits);
+ while (value != 0) {
+ digits -= 1;
+ buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
+ value /= 10;
+ }
+ return string(buffer);
+ }
+
+ /**
+ * @dev `uint256`をASCII `string`の16進表現に変換します.
+ */
+ function toHexString(uint256 value) internal pure returns (string memory) {
+ if (value == 0) {
+ return "0x00";
+ }
+ uint256 temp = value;
+ uint256 length = 0;
+ while (temp != 0) {
+ length++;
+ temp >>= 8;
+ }
+ return toHexString(value, length);
+ }
+
+ /**
+ * @dev `uint256`を固定長のASCII `string`の16進表現に変換します.
+ */
+ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
+ bytes memory buffer = new bytes(2 * length + 2);
+ buffer[0] = "0";
+ buffer[1] = "x";
+ for (uint256 i = 2 * length + 1; i > 1; --i) {
+ buffer[i] = _HEX_SYMBOLS[value & 0xf];
+ value >>= 4;
+ }
+ require(value == 0, "Strings: hex length insufficient");
+ return string(buffer);
+ }
+
+ /**
+ * @dev 20バイトの固定長の`address`をチェックサムされていないASCII `string`の16進表現に変換します.
+ */
+ function toHexString(address addr) internal pure returns (string memory) {
+ return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/34_ERC721_ja/WTFApe.sol b/Languages/ja/34_ERC721_ja/WTFApe.sol
new file mode 100644
index 000000000..d0adce172
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/WTFApe.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: MIT
+// by 0xAA
+pragma solidity ^0.8.21;
+
+import "./ERC721.sol";
+
+contract WTFApe is ERC721{
+ uint public MAX_APES = 10000; // 総量
+
+ // コンストラクタ
+ constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){
+ }
+
+ //BAYCのbaseURIは ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+ function _baseURI() internal pure override returns (string memory) {
+ return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
+ }
+
+ // ミント関数
+ function mint(address to, uint tokenId) external {
+ require(tokenId >= 0 && tokenId < MAX_APES, "tokenId out of range");
+ _mint(to, tokenId);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/34_ERC721_ja/img/34-1.png b/Languages/ja/34_ERC721_ja/img/34-1.png
new file mode 100644
index 000000000..fec4d4359
Binary files /dev/null and b/Languages/ja/34_ERC721_ja/img/34-1.png differ
diff --git a/Languages/ja/34_ERC721_ja/img/34-2.png b/Languages/ja/34_ERC721_ja/img/34-2.png
new file mode 100644
index 000000000..4de29e274
Binary files /dev/null and b/Languages/ja/34_ERC721_ja/img/34-2.png differ
diff --git a/Languages/ja/34_ERC721_ja/img/34-3.png b/Languages/ja/34_ERC721_ja/img/34-3.png
new file mode 100644
index 000000000..1fb89cfd2
Binary files /dev/null and b/Languages/ja/34_ERC721_ja/img/34-3.png differ
diff --git a/Languages/ja/34_ERC721_ja/img/34-4.png b/Languages/ja/34_ERC721_ja/img/34-4.png
new file mode 100644
index 000000000..87d5aae14
Binary files /dev/null and b/Languages/ja/34_ERC721_ja/img/34-4.png differ
diff --git a/Languages/ja/34_ERC721_ja/img/34-5.png b/Languages/ja/34_ERC721_ja/img/34-5.png
new file mode 100644
index 000000000..dfbcb9a87
Binary files /dev/null and b/Languages/ja/34_ERC721_ja/img/34-5.png differ
diff --git a/Languages/ja/34_ERC721_ja/readme.md b/Languages/ja/34_ERC721_ja/readme.md
new file mode 100644
index 000000000..7d3f2ab7d
--- /dev/null
+++ b/Languages/ja/34_ERC721_ja/readme.md
@@ -0,0 +1,625 @@
+---
+title: 34. ERC721
+tags:
+ - solidity
+ - application
+ - wtfacademy
+ - ERC721
+ - ERC165
+ - OpenZeppelin
+---
+
+# WTF Solidity極簡入門: 34. ERC721
+
+私は最近Solidityを再学習し、詳細を固めながら「WTF Solidity極簡入門」を書いています。これは初心者向けです(プログラミング上級者は他のチュートリアルを参照してください)。毎週1-3講を更新します。
+
+Twitter:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[WeChatグループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化されています:[github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+`BTC`や`ETH`などのトークンは同質化トークンに属し、マイナーが採掘した第`1`枚目の`BTC`と第`10000`枚目の`BTC`には違いがなく、等価です。しかし、世界には多くの非同質なアイテムがあり、不動産、骨董品、バーチャルアート作品などが含まれ、これらのアイテムは同質化トークンで抽象化することができません。そこで、[イーサリアムEIP721](https://eips.ethereum.org/EIPS/eip-721)が`ERC721`標準を提案し、非同質なアイテムを抽象化しました。この講義では、`ERC721`標準を紹介し、それを基にして`NFT`を発行します。
+
+## EIPとERC
+
+ここで理解すべき点があります。本節のタイトルは`ERC721`ですが、ここで`EIP721`についても言及しています。この二つの関係は何でしょうか?
+
+`EIP`は`Ethereum Improvement Proposals`(イーサリアム改善提案)の略で、イーサリアム開発者コミュニティが提案する改善提案であり、番号で整理された一連の文書で、インターネット上のIETFのRFCに似ています。
+
+`EIP`は`Ethereum`エコシステムの任意の領域の改善であり、新機能、ERC、プロトコル改善、プログラミングツールなどが含まれます。
+
+`ERC`は`Ethereum Request For Comment`(イーサリアム意見征求稿)の略で、イーサリアム上のアプリケーションレベルの各種開発標準とプロトコルを記録するために使用されます。典型的なトークン標準(`ERC20`、`ERC721`)、名前登録(`ERC26`、`ERC13`)、URI範式(`ERC67`)、Library/Package形式(`EIP82`)、ウォレット形式(`EIP75`、`EIP85`)などがあります。
+
+ERCプロトコル標準はイーサリアムの発展に影響を与える重要な要因であり、`ERC20`、`ERC223`、`ERC721`、`ERC777`などは、すべてイーサリアムエコシステムに大きな影響を与えました。
+
+したがって最終的な結論:`EIP`には`ERC`が含まれます。
+
+**この節の学習完了後に、なぜ最初に`ERC165`について学び、`ERC721`ではないのかが理解できます。結論を見たい場合は最下部に直接移動してください**
+
+## ERC165
+
+[ERC165標準](https://eips.ethereum.org/EIPS/eip-165)を通じて、スマートコントラクトは自身がサポートするインターフェースを宣言し、他のコントラクトが確認できるようにします。簡単に言うと、ERC165はスマートコントラクトが`ERC721`、`ERC1155`のインターフェースをサポートしているかどうかをチェックする仕組みです。
+
+`IERC165`インターフェースコントラクトは`supportsInterface`関数のみを宣言し、クエリしたい`interfaceId`インターフェースidを入力し、コントラクトがそのインターフェースidを実装している場合は`true`を返します:
+
+```solidity
+interface IERC165 {
+ /**
+ * @dev コントラクトがクエリされた`interfaceId`を実装している場合はtrueを返します
+ * ルールの詳細:https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
+ *
+ */
+ function supportsInterface(bytes4 interfaceId) external view returns (bool);
+}
+```
+
+`ERC721`が`supportsInterface()`関数をどのように実装しているかを見てみましょう:
+
+```solidity
+ function supportsInterface(bytes4 interfaceId) external pure override returns (bool)
+ {
+ return
+ interfaceId == type(IERC721).interfaceId ||
+ interfaceId == type(IERC165).interfaceId;
+ }
+```
+
+クエリが`IERC721`または`IERC165`のインターフェースidの場合、`true`を返し、そうでない場合は`false`を返します。
+
+## IERC721
+
+`IERC721`は`ERC721`標準のインターフェースコントラクトで、`ERC721`が実装すべき基本機能を規定しています。`tokenId`を使用して特定の非同質化トークンを表現し、承認や転送には`tokenId`を明確にする必要があります。一方、`ERC20`は転送額を明確にするだけで済みます。
+
+```solidity
+/**
+ * @dev ERC721標準インターフェース.
+ */
+interface IERC721 is IERC165 {
+ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
+ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
+ event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
+
+ function balanceOf(address owner) external view returns (uint256 balance);
+
+ function ownerOf(uint256 tokenId) external view returns (address owner);
+
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes calldata data
+ ) external;
+
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 tokenId
+ ) external;
+
+ function transferFrom(
+ address from,
+ address to,
+ uint256 tokenId
+ ) external;
+
+ function approve(address to, uint256 tokenId) external;
+
+ function setApprovalForAll(address operator, bool _approved) external;
+
+ function getApproved(uint256 tokenId) external view returns (address operator);
+
+ function isApprovedForAll(address owner, address operator) external view returns (bool);
+}
+```
+
+### IERC721イベント
+`IERC721`には3つのイベントが含まれており、その中の`Transfer`と`Approval`イベントは`ERC20`にもあります。
+- `Transfer`イベント:転送時に発行され、トークンの送信元アドレス`from`、受信アドレス`to`、`tokenId`を記録します。
+- `Approval`イベント:承認時に発行され、承認アドレス`owner`、被承認アドレス`approved`、`tokenId`を記録します。
+- `ApprovalForAll`イベント:一括承認時に発行され、一括承認の送信元アドレス`owner`、被承認アドレス`operator`、承認の有無`approved`を記録します。
+
+### IERC721関数
+- `balanceOf`:あるアドレスのNFT保有量`balance`を返します。
+- `ownerOf`:ある`tokenId`の所有者`owner`を返します。
+- `transferFrom`:通常の転送で、転送元アドレス`from`、受信アドレス`to`、`tokenId`をパラメータとします。
+- `safeTransferFrom`:安全転送(受信者がコントラクトアドレスの場合、`ERC721Receiver`インターフェースの実装が必要)。転送元アドレス`from`、受信アドレス`to`、`tokenId`をパラメータとします。
+- `approve`:別のアドレスにあなたのNFTの使用を承認します。被承認アドレス`to`と`tokenId`をパラメータとします。
+- `getApproved`:`tokenId`がどのアドレスに承認されているかをクエリします。
+- `setApprovalForAll`:自身が保有するそのシリーズのNFTを特定のアドレス`operator`に一括承認します。
+- `isApprovedForAll`:あるアドレスのNFTが別の`operator`アドレスに一括承認されているかをクエリします。
+- `safeTransferFrom`:安全転送のオーバーロード関数で、パラメータに`data`が含まれます。
+
+## IERC721Receiver
+
+コントラクトが`ERC721`の関連関数を実装していない場合、転送された`NFT`はブラックホールに入り、永遠に転送できなくなります。誤転送を防ぐため、`ERC721`は`safeTransferFrom()`安全転送関数を実装し、対象コントラクトが`IERC721Receiver`インターフェースを実装している必要があり、そうでなければ`revert`します。`IERC721Receiver`インターフェースには`onERC721Received()`関数のみが含まれます。
+
+```solidity
+// ERC721受信者インターフェース:コントラクトはこのインターフェースを実装して安全転送でERC721を受信する必要があります
+interface IERC721Receiver {
+ function onERC721Received(
+ address operator,
+ address from,
+ uint tokenId,
+ bytes calldata data
+ ) external returns (bytes4);
+}
+```
+
+`ERC721`が`_checkOnERC721Received`を使用して対象コントラクトが`onERC721Received()`関数を実装していることを確認する方法(`onERC721Received`の`selector`を返す)を見てみましょう:
+```solidity
+function _checkOnERC721Received(
+ address operator,
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes memory data
+) internal {
+ if (to.code.length > 0) {
+ try IERC721Receiver(to).onERC721Received(operator, from, tokenId, data) returns (bytes4 retval) {
+ if (retval != IERC721Receiver.onERC721Received.selector) {
+ // トークンが拒否されました
+ revert IERC721Errors.ERC721InvalidReceiver(to);
+ }
+ } catch (bytes memory reason) {
+ if (reason.length == 0) {
+ // IERC721Receiver実装者ではありません
+ revert IERC721Errors.ERC721InvalidReceiver(to);
+ } else {
+ /// @solidity memory-safe-assembly
+ assembly {
+ revert(add(32, reason), mload(reason))
+ }
+ }
+ }
+ }
+}
+```
+
+## IERC721Metadata
+`IERC721Metadata`は`ERC721`の拡張インターフェースで、`metadata`メタデータをクエリする3つの一般的な関数を実装しています:
+
+- `name()`:トークン名を返します。
+- `symbol()`:トークンシンボルを返します。
+- `tokenURI()`:`tokenId`を通じて`metadata`のリンク`url`をクエリします。`ERC721`特有の関数です。
+
+```solidity
+interface IERC721Metadata is IERC721 {
+ function name() external view returns (string memory);
+
+ function symbol() external view returns (string memory);
+
+ function tokenURI(uint256 tokenId) external view returns (string memory);
+}
+```
+
+## ERC721メインコントラクト
+`ERC721`メインコントラクトは`IERC721`、`IERC165`、`IERC721Metadata`で定義されたすべての機能を実装し、`4`つの状態変数と`17`の関数を含みます。実装は比較的シンプルで、各関数の機能はコードコメントを参照してください:
+
+```solidity
+// SPDX-License-Identifier: MIT
+// by 0xAA
+pragma solidity ^0.8.21;
+
+import "./IERC165.sol";
+import "./IERC721.sol";
+import "./IERC721Receiver.sol";
+import "./IERC721Metadata.sol";
+import "./String.sol";
+
+contract ERC721 is IERC721, IERC721Metadata{
+ using Strings for uint256; // Stringsライブラリを使用
+
+ // トークン名
+ string public override name;
+ // トークンシンボル
+ string public override symbol;
+ // tokenId から owner address への所有者マッピング
+ mapping(uint => address) private _owners;
+ // address から保有数量への保有量マッピング
+ mapping(address => uint) private _balances;
+ // tokenID から承認アドレスへの承認マッピング
+ mapping(uint => address) private _tokenApprovals;
+ // ownerアドレス から operatorアドレスへの一括承認マッピング
+ mapping(address => mapping(address => bool)) private _operatorApprovals;
+
+ // エラー 無効な受信者
+ error ERC721InvalidReceiver(address receiver);
+
+ /**
+ * コンストラクタ、`name` と`symbol` を初期化 .
+ */
+ constructor(string memory name_, string memory symbol_) {
+ name = name_;
+ symbol = symbol_;
+ }
+
+ // IERC165インターフェースsupportsInterfaceを実装
+ function supportsInterface(bytes4 interfaceId)
+ external
+ pure
+ override
+ returns (bool)
+ {
+ return
+ interfaceId == type(IERC721).interfaceId ||
+ interfaceId == type(IERC165).interfaceId ||
+ interfaceId == type(IERC721Metadata).interfaceId;
+ }
+
+ // IERC721のbalanceOfを実装、_balances変数を使用してownerアドレスのbalanceをクエリ。
+ function balanceOf(address owner) external view override returns (uint) {
+ require(owner != address(0), "owner = zero address");
+ return _balances[owner];
+ }
+
+ // IERC721のownerOfを実装、_owners変数を使用してtokenIdのownerをクエリ。
+ function ownerOf(uint tokenId) public view override returns (address owner) {
+ owner = _owners[tokenId];
+ require(owner != address(0), "token doesn't exist");
+ }
+
+ // IERC721のisApprovedForAllを実装、_operatorApprovals変数を使用してownerアドレスが保有するNFTをoperatorアドレスに一括承認しているかをクエリ。
+ function isApprovedForAll(address owner, address operator)
+ external
+ view
+ override
+ returns (bool)
+ {
+ return _operatorApprovals[owner][operator];
+ }
+
+ // IERC721のsetApprovalForAllを実装、保有トークンを全てoperatorアドレスに承認。_setApprovalForAll関数を呼び出し。
+ function setApprovalForAll(address operator, bool approved) external override {
+ _operatorApprovals[msg.sender][operator] = approved;
+ emit ApprovalForAll(msg.sender, operator, approved);
+ }
+
+ // IERC721のgetApprovedを実装、_tokenApprovals変数を使用してtokenIdの承認アドレスをクエリ。
+ function getApproved(uint tokenId) external view override returns (address) {
+ require(_owners[tokenId] != address(0), "token doesn't exist");
+ return _tokenApprovals[tokenId];
+ }
+
+ // 承認関数。_tokenApprovalsを調整して、to アドレスに tokenId の操作を承認し、同時にApprovalイベントを発行。
+ function _approve(
+ address owner,
+ address to,
+ uint tokenId
+ ) private {
+ _tokenApprovals[tokenId] = to;
+ emit Approval(owner, to, tokenId);
+ }
+
+ // IERC721のapproveを実装、tokenIdを to アドレスに承認。条件:toはownerではなく、msg.senderはownerまたは承認アドレス。_approve関数を呼び出し。
+ function approve(address to, uint tokenId) external override {
+ address owner = _owners[tokenId];
+ require(
+ msg.sender == owner || _operatorApprovals[owner][msg.sender],
+ "not owner nor approved for all"
+ );
+ _approve(owner, to, tokenId);
+ }
+
+ // spenderアドレスがtokenIdを使用できるかをクエリ(ownerまたは承認アドレスである必要がある)
+ function _isApprovedOrOwner(
+ address owner,
+ address spender,
+ uint tokenId
+ ) private view returns (bool) {
+ return (spender == owner ||
+ _tokenApprovals[tokenId] == spender ||
+ _operatorApprovals[owner][spender]);
+ }
+
+ /*
+ * 転送関数。_balancesと_owner変数を調整して tokenId を from から to に転送し、同時にTransferイベントを発行。
+ * 条件:
+ * 1. tokenId が from によって所有されている
+ * 2. to が0アドレスではない
+ */
+ function _transfer(
+ address owner,
+ address from,
+ address to,
+ uint tokenId
+ ) private {
+ require(from == owner, "not owner");
+ require(to != address(0), "transfer to the zero address");
+
+ _approve(owner, address(0), tokenId);
+
+ _balances[from] -= 1;
+ _balances[to] += 1;
+ _owners[tokenId] = to;
+
+ emit Transfer(from, to, tokenId);
+ }
+
+ // IERC721のtransferFromを実装、非安全転送、推奨されません。_transfer関数を呼び出し
+ function transferFrom(
+ address from,
+ address to,
+ uint tokenId
+ ) external override {
+ address owner = ownerOf(tokenId);
+ require(
+ _isApprovedOrOwner(owner, msg.sender, tokenId),
+ "not owner nor approved"
+ );
+ _transfer(owner, from, to, tokenId);
+ }
+
+ /**
+ * 安全転送、tokenId トークンを from から to に安全に転送し、コントラクト受信者がERC721プロトコルを理解しているかをチェックしてトークンが永続的にロックされることを防止。_transfer関数と_checkOnERC721Received関数を呼び出し。条件:
+ * from は0アドレスではない.
+ * to は0アドレスではない.
+ * tokenId トークンが存在し、from によって所有されている.
+ * to がスマートコントラクトの場合、IERC721Receiver-onERC721Receivedをサポートする必要がある.
+ */
+ function _safeTransfer(
+ address owner,
+ address from,
+ address to,
+ uint tokenId,
+ bytes memory _data
+ ) private {
+ _transfer(owner, from, to, tokenId);
+ _checkOnERC721Received(from, to, tokenId, _data);
+ }
+
+ /**
+ * IERC721のsafeTransferFromを実装、安全転送、_safeTransfer関数を呼び出し。
+ */
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint tokenId,
+ bytes memory _data
+ ) public override {
+ address owner = ownerOf(tokenId);
+ require(
+ _isApprovedOrOwner(owner, msg.sender, tokenId),
+ "not owner nor approved"
+ );
+ _safeTransfer(owner, from, to, tokenId, _data);
+ }
+
+ // safeTransferFromオーバーロード関数
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint tokenId
+ ) external override {
+ safeTransferFrom(from, to, tokenId, "");
+ }
+
+ /**
+ * ミント関数。_balancesと_owners変数を調整してtokenIdをミントし、to に転送、同時にTransferイベントを発行。ミント関数。_balancesと_owners変数を調整してtokenIdをミントし、to に転送、同時にTransferイベントを発行。
+ * このmint関数は誰でも呼び出すことができ、実際の使用では開発者が書き直して条件を追加する必要があります。
+ * 条件:
+ * 1. tokenIdがまだ存在しない。
+ * 2. toが0アドレスではない.
+ */
+ function _mint(address to, uint tokenId) internal virtual {
+ require(to != address(0), "mint to zero address");
+ require(_owners[tokenId] == address(0), "token already minted");
+
+ _balances[to] += 1;
+ _owners[tokenId] = to;
+
+ emit Transfer(address(0), to, tokenId);
+ }
+
+ // バーン関数、_balancesと_owners変数を調整してtokenIdを破棄し、同時にTransferイベントを発行。条件:tokenIdが存在する。
+ function _burn(uint tokenId) internal virtual {
+ address owner = ownerOf(tokenId);
+ require(msg.sender == owner, "not owner of token");
+
+ _approve(owner, address(0), tokenId);
+
+ _balances[owner] -= 1;
+ delete _owners[tokenId];
+
+ emit Transfer(owner, address(0), tokenId);
+ }
+
+ // _checkOnERC721Received:関数、to がコントラクトの場合にIERC721Receiver-onERC721Receivedを呼び出し、tokenId が誤ってブラックホールに転送されることを防ぐ。
+ function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
+ if (to.code.length > 0) {
+ try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
+ if (retval != IERC721Receiver.onERC721Received.selector) {
+ revert ERC721InvalidReceiver(to);
+ }
+ } catch (bytes memory reason) {
+ if (reason.length == 0) {
+ revert ERC721InvalidReceiver(to);
+ } else {
+ /// @solidity memory-safe-assembly
+ assembly {
+ revert(add(32, reason), mload(reason))
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * IERC721MetadataのtokenURI関数を実装、metadataをクエリ。
+ */
+ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
+ require(_owners[tokenId] != address(0), "Token Not Exist");
+
+ string memory baseURI = _baseURI();
+ return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
+ }
+
+ /**
+ * {tokenURI}のBaseURIを計算、tokenURIはbaseURIとtokenIdを連結したもので、開発者が書き直す必要がある。
+ * BAYCのbaseURIは ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+ */
+ function _baseURI() internal view virtual returns (string memory) {
+ return "";
+ }
+}
+
+```
+
+## 無料ミントのAPEを書く
+`ERC721`を使用して無料ミントの`WTF APE`を作成し、総量を`10000`に設定します。`mint()`と`baseURI()`関数を書き直すだけです。`baseURI()`は`BAYC`と同じに設定されているため、メタデータは直接Bored Apeのものを取得し、[RRBAYC](https://rrbayc.com/)に似ています:
+
+```solidity
+// SPDX-License-Identifier: MIT
+// by 0xAA
+pragma solidity ^0.8.21;
+
+import "./ERC721.sol";
+
+contract WTFApe is ERC721{
+ uint public MAX_APES = 10000; // 総量
+
+ // コンストラクタ
+ constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){
+ }
+
+ //BAYCのbaseURIは ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+ function _baseURI() internal pure override returns (string memory) {
+ return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
+ }
+
+ // ミント関数
+ function mint(address to, uint tokenId) external {
+ require(tokenId >= 0 && tokenId < MAX_APES, "tokenId out of range");
+ _mint(to, tokenId);
+ }
+}
+```
+## `ERC721`NFTの発行
+
+`ERC721`標準があることで、`ETH`チェーン上でのNFT発行が非常に簡単になりました。今、私たち自身のNFTを発行してみましょう。
+
+`Remix`で`ERC721`コントラクトと`WTFApe`コントラクトをコンパイルし(順序に従って)、デプロイ欄の下ボタンをクリックし、コンストラクタのパラメータを入力します。`name_`と`symbol_`の両方を`WTF`に設定し、`transact`キーをクリックしてデプロイします。
+
+
+
+
+これで、`WTF`NFTを作成しました。`mint()`関数を実行して自分自身にいくつかのトークンをミントする必要があります。`mint`関数の欄で右側の下ボタンを開き、アカウントアドレスとtokenidを入力し、`mint`ボタンをクリックして自分自身に`0`番の`WTF`NFTをミントします。
+
+右側のDebugボタンをクリックして、下記のlogsの詳細を確認できます。
+
+その中には4つの重要な情報が含まれています:
+- イベント`Transfer`
+- ミントアドレス`0x0000000000000000000000000000000000000000`
+- 受信アドレス`0x5B38Da6a701c568545dCfcB03FcB875f56beddC4`
+- tokenid`0`
+
+
+
+`balanceOf()`関数を使用してアカウント残高をクエリします。現在のアカウントを入力すると、`NFT`が1つあることがわかり、ミントが成功しました。
+
+アカウント情報は図の左側、右側は関数実行の詳細情報です。
+
+
+
+`ownerOf()`関数を使用してNFTがどのアカウントに属するかをクエリすることもできます。`tokenid`を入力すると、私たちのアドレスが表示され、クエリに間違いありません。
+
+
+
+## ERC165とERC721の詳細解説
+上記で述べたように、NFTが NFT を操作する能力を持たないコントラクトに転送されることを防ぐため、対象は正しくERC721TokenReceiverインターフェースを実装する必要があります:
+```solidity
+interface ERC721TokenReceiver {
+ function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
+}
+```
+プログラミング言語の世界に拡張すると、JavaのinterfaceでもRustのTrait(もちろんsolidity中ではtraitにより近いのはlibrary)でも、インターフェースに関連するものはすべて、ある意味を透露しています:インターフェースは特定の行動の集合であり(solidityではさらに、インターフェースは完全に関数セレクタの集合と等価)、ある型がインターフェースを実装している限り、その型がそのような機能を持っていることを示します。したがって、あるcontract型が上述の`ERC721TokenReceiver`インターフェース(より具体的には`onERC721Received`関数を実装)を実装している限り、そのcontract型は外部に対してNFTを管理する能力を持っていることを表明します。もちろん、NFTの操作ロジックはそのコントラクトの他の関数に実装されています。
+ERC721標準は`safeTransferFrom`を実行する際に、対象コントラクトが`onERC721Received`関数を実装しているかをチェックしますが、これはERC165の思想を利用した操作です。
+**それでは究極的にERC165とは何でしょうか?**
+ERC165は、自身が実装したインターフェースを外部に表明する技術標準です。上記で述べたように、インターフェースを実装することはコントラクトが特別な能力を持っていることを示します。一部のコントラクトが他のコントラクトと相互作用する際、対象コントラクトが特定の機能を持っていることを期待し、コントラクト間はERC165標準を通じて相手をクエリして相手が対応する能力を持っているかをチェックできます。
+ERC721コントラクトを例に、外部があるコントラクトがERC721かどうかをチェックする場合、[どうやって行うか?](https://eips.ethereum.org/EIPS/eip-165#how-to-detect-if-a-contract-implements-erc-165) 。この説明によると、チェック手順はまずそのコントラクトがERC165を実装しているかをチェックし、その後そのコントラクトが実装している他の特定インターフェースをチェックすることです。この時、その特定インターフェースはIERC721です。IERC721はERC721の基本インターフェースです(なぜ基本と言うかというと、`ERC721Metadata` `ERC721Enumerable` などの拡張もあるためです):
+
+```solidity
+/// 注意この**0x80ac58cd**
+/// **⚠⚠⚠ Note: the ERC-165 identifier for this interface is 0x80ac58cd. ⚠⚠⚠**
+interface ERC721 /* is ERC165 */ {
+ event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
+
+ event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
+
+ event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
+
+ function balanceOf(address _owner) external view returns (uint256);
+
+ function ownerOf(uint256 _tokenId) external view returns (address);
+
+ function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
+
+ function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
+
+ function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
+
+ function approve(address _approved, uint256 _tokenId) external payable;
+
+ function setApprovalForAll(address _operator, bool _approved) external;
+
+ function getApproved(uint256 _tokenId) external view returns (address);
+
+ function isApprovedForAll(address _owner, address _operator) external view returns (bool);
+}
+```
+**0x80ac58cd**=
+`bytes4(keccak256(ERC721.Transfer.selector) ^ keccak256(ERC721.Approval.selector) ^ ··· ^keccak256(ERC721.isApprovedForAll.selector))`、これはERC165で規定された計算方式です。
+
+同様に、ERC165自体のインターフェースを計算することができます(そのインターフェースには
+`function supportsInterface(bytes4 interfaceID) external view returns (bool);` 関数のみがあり、これに対して`bytes4(keccak256(supportsInterface.selector))` を実行すると**0x01ffc9a7**が得られます。さらに、ERC721には`ERC721Metadata` などの拡張インターフェースも定義されています:
+
+```solidity
+/// Note: the ERC-165 identifier for this interface is 0x5b5e139f.
+interface ERC721Metadata /* is ERC721 */ {
+ function name() external view returns (string _name);
+ function symbol() external view returns (string _symbol);
+ function tokenURI(uint256 _tokenId) external view returns (string); // これは非常に重要で、フロントエンドで表示される小画像のリンクはすべてこの関数が返すものです
+}
+```
+
+この**0x5b5e139f** の計算は:
+
+```solidity
+IERC721Metadata.name.selector ^ IERC721Metadata.symbol.selector ^ IERC721Metadata.tokenURI.selector
+```
+
+solmateが実装した[ERC721.sol](https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)はどのようにしてこれらのERC165要求の特性を完成させているのでしょうか?
+
+```solidity
+function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
+ return
+ interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
+ interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
+ interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
+}
+```
+
+そう、これほどシンプルです。外部が[link1](https://eips.ethereum.org/EIPS/eip-165#how-to-detect-if-a-contract-implements-erc-165) の手順に従ってチェックを行う場合、外部がこのコントラクトが165を実装しているかをチェックしたい場合、supportsInterface関数の入力パラメータが`0x01ffc9a7`の時にtrueを返し、入力パラメータが`0xffffffff`の時に返り値がfalseである必要があります。上述の実装は完璧に要求を満たしています。
+
+外部がこのコントラクトがERC721かどうかをチェックしたい場合、入力パラメータが**0x80ac58cd** の時に外部がこのチェックを行いたいことを示します。trueを返します。
+
+外部がこのコントラクトがERC721の拡張ERC721Metadataインターフェースを実装しているかをチェックしたい場合、入力パラメータは0x5b5e139fです。trueを返しました。
+
+そして、この関数がvirtualであるため、このコントラクトの使用者はこのコントラクトを継承し、その後`ERC721Enumerable` インターフェースを実装することができます。その中の`totalSupply` などの関数を実装した後、継承した`supportsInterface`を再実装して
+
+```solidity
+function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
+ return
+ interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
+ interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
+ interfaceId == 0x5b5e139f || // ERC165 Interface ID for ERC721Metadata
+ interfaceId == 0x780e9d63; // ERC165 Interface ID for ERC721Enumerable
+}
+```
+
+**エレガントで、簡潔で、拡張性が最大限です。**
+
+## まとめ
+この講義では、`ERC721`標準、インターフェース、およびその実装を紹介し、コントラクトコードに日本語注釈を追加しました。また、`ERC721`を使用して無料ミントの`WTF APE` NFTを作成し、メタデータを直接`BAYC`から呼び出しました。`ERC721`標準は現在も継続的に発展しており、現在人気のバージョンは`ERC721Enumerable`(NFTのアクセシビリティを向上)と`ERC721A`(ミント`gas`を節約)です。
\ No newline at end of file
diff --git a/Languages/ja/35_DutchAuction_ja/readme.md b/Languages/ja/35_DutchAuction_ja/readme.md
new file mode 100644
index 000000000..f805668e3
--- /dev/null
+++ b/Languages/ja/35_DutchAuction_ja/readme.md
@@ -0,0 +1,185 @@
+---
+title: 35. ダッチオークション
+tags:
+ - solidity
+ - application
+ - wtfacademy
+ - ERC721
+ - Dutch Auction
+---
+
+# WTF Solidity極簡入門: 35. ダッチオークション
+
+私は最近Solidityを再学習し、詳細を固めながら「WTF Solidity極簡入門」を書いています。これは初心者向けです(プログラミング上級者は他のチュートリアルを参照してください)。毎週1-3講を更新します。
+
+Twitter:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[WeChatグループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化されています:[github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+----
+
+この講義では、ダッチオークションについて紹介し、簡素化版`Azuki`ダッチオークションコードを通じて、`ダッチオークション`を使用して`ERC721`標準の`NFT`を発行する方法を説明します。
+
+## ダッチオークション
+
+ダッチオークション(`Dutch Auction`)は特殊なオークション形式です。「減価オークション」とも呼ばれ、オークション対象の競売価格が高値から順次下降し、最初の競買人が応価(底値に達するか超える)した時点で落札が成立するオークションを指します。
+
+
+
+暗号通貨の世界では、多くの`NFT`がダッチオークションを通じて発売されており、`Azuki`や`World of Women`が含まれ、その中で`Azuki`はダッチオークションを通じて`8000`枚を超える`ETH`を調達しました。
+
+プロジェクト側がこのオークション形式を非常に好む主な理由は2つあります:
+
+1. ダッチオークションの価格は最高値からゆっくりと下降し、プロジェクト側が最大の収益を得られます。
+
+2. オークションが長時間続く(通常6時間以上)ため、`gas war`を避けることができます。
+
+## `DutchAuction`コントラクト
+
+コードは`Azuki`の[コード](https://etherscan.io/address/0xed5af388653567af2f388e6224dc7c4b3241c544#code)を簡素化したものです。`DutchAuction`コントラクトは、以前に紹介した`ERC721`および`Ownable`コントラクトを継承しています:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.21;
+
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/ERC721.sol";
+
+contract DutchAuction is Ownable, ERC721 {
+```
+
+### `DutchAuction`状態変数
+
+コントラクトには合計`9`個の状態変数があり、そのうち`6`個がオークションに関連しています:
+
+- `COLLECTION_SIZE`:NFTの総数
+- `AUCTION_START_PRICE`:ダッチオークションの開始価格(最高価格)
+- `AUCTION_END_PRICE`:ダッチオークションの終了価格(最低価格/フロア価格)
+- `AUCTION_TIME`:オークションの持続時間
+- `AUCTION_DROP_INTERVAL`:価格が下降する間隔
+- `auctionStartTime`:オークションの開始時間(ブロックチェーンタイムスタンプ、`block.timestamp`)
+
+```solidity
+ uint256 public constant COLLECTION_SIZE = 10000; // NFTの総数
+ uint256 public constant AUCTION_START_PRICE = 1 ether; // 開始価格(最高価格)
+ uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 終了価格(最低価格/フロア価格)
+ uint256 public constant AUCTION_TIME = 10 minutes; // オークション時間、テストの便宜上10分に設定
+ uint256 public constant AUCTION_DROP_INTERVAL = 1 minutes; // 価格が下降する間隔
+ uint256 public constant AUCTION_DROP_PER_STEP =
+ (AUCTION_START_PRICE - AUCTION_END_PRICE) /
+ (AUCTION_TIME / AUCTION_DROP_INTERVAL); // 各価格下降ステップ
+
+ uint256 public auctionStartTime; // オークション開始タイムスタンプ
+ string private _baseTokenURI; // metadata URI
+ uint256[] private _allTokens; // すべての存在するtokenIdを記録
+```
+
+### `DutchAuction`関数
+
+ダッチオークションコントラクトには合計`9`個の関数があります。`ERC721`に関連する関数はここでは再度説明せず、オークションに関連する関数のみを紹介します。
+
+- オークション開始時間の設定:コンストラクタで現在のブロック時間を開始時間として宣言し、プロジェクト側は`setAuctionStartTime()`関数を通じて調整することもできます:
+
+```solidity
+ constructor() ERC721("WTF Dutch Auction", "WTF Dutch Auction") {
+ auctionStartTime = block.timestamp;
+ }
+
+ // auctionStartTime setter関数、onlyOwner
+ function setAuctionStartTime(uint32 timestamp) external onlyOwner {
+ auctionStartTime = timestamp;
+ }
+```
+
+- オークションリアルタイム価格の取得:`getAuctionPrice()`関数は現在のブロック時間とオークション関連の状態変数を通じてリアルタイムオークション価格を計算します。
+
+`block.timestamp`が開始時間より小さい場合、価格は最高価格`AUCTION_START_PRICE`;
+
+`block.timestamp`が終了時間より大きい場合、価格は最低価格`AUCTION_END_PRICE`;
+
+`block.timestamp`が両者の間にある場合、現在の減衰価格を計算します。
+
+```solidity
+ // オークションリアルタイム価格を取得
+ function getAuctionPrice()
+ public
+ view
+ returns (uint256)
+ {
+ if (block.timestamp < auctionStartTime) {
+ return AUCTION_START_PRICE;
+ }else if (block.timestamp - auctionStartTime >= AUCTION_TIME) {
+ return AUCTION_END_PRICE;
+ } else {
+ uint256 steps = (block.timestamp - auctionStartTime) /
+ AUCTION_DROP_INTERVAL;
+ return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);
+ }
+ }
+```
+
+- ユーザーオークションと`NFT`ミント:ユーザーは`auctionMint()`関数を呼び出して`ETH`を支払い、ダッチオークションに参加して`NFT`をミントします。
+
+この関数はまずオークションが開始されているか/ミントが`NFT`総数を超えていないかをチェックします。次に、コントラクトは`getAuctionPrice()`とミント数量を通じてオークションコストを計算し、ユーザーが支払った`ETH`が十分かをチェックします:十分であれば、`NFT`をユーザーにミントし、超過分の`ETH`を返金します;そうでなければ、トランザクションを戻します。
+
+```solidity
+ // オークションmint関数
+ function auctionMint(uint256 quantity) external payable{
+ uint256 _saleStartTime = uint256(auctionStartTime); // ローカル変数を作成、gas消費を削減
+ require(
+ _saleStartTime != 0 && block.timestamp >= _saleStartTime,
+ "sale has not started yet"
+ ); // 開始オークション時間が設定されているか、オークションが開始されているかをチェック
+ require(
+ totalSupply() + quantity <= COLLECTION_SIZE,
+ "not enough remaining reserved for auction to support desired mint amount"
+ ); // NFT上限を超えていないかをチェック
+
+ uint256 totalCost = getAuctionPrice() * quantity; // mintコストを計算
+ require(msg.value >= totalCost, "Need to send more ETH."); // ユーザーが十分なETHを支払っているかをチェック
+
+ // NFTをミント
+ for(uint256 i = 0; i < quantity; i++) {
+ uint256 mintIndex = totalSupply();
+ _mint(msg.sender, mintIndex);
+ _addTokenToAllTokensEnumeration(mintIndex);
+ }
+ // 余剰ETHを返金
+ if (msg.value > totalCost) {
+ payable(msg.sender).transfer(msg.value - totalCost); //ここでリエントランシーのリスクがないか注意
+ }
+ }
+```
+
+- プロジェクト側による調達した`ETH`の引き出し:プロジェクト側は`withdrawMoney()`関数を通じてオークションで調達した`ETH`を引き出すことができます。
+
+```solidity
+ // 引き出し関数、onlyOwner
+ function withdrawMoney() external onlyOwner {
+ (bool success, ) = msg.sender.call{value: address(this).balance}(""); // call関数の呼び出し方法は第22講を参照
+ require(success, "Transfer failed.");
+ }
+```
+
+## Remixデモ
+
+1. コントラクトのデプロイ:まず、`DutchAuction.sol`コントラクトをデプロイし、`setAuctionStartTime()`関数を通じてオークション開始時間を設定します。
+この例では、開始時間を2023年3月19日14:34(UTC時間1679207640に対応)に使用します。実験時にはツールウェブサイト([こちら](https://tool.chinaz.com/tools/unixtime.aspx)など)で対応する時間を自分で確認できます。
+
+
+
+2. ダッチオークション:その後、`getAuctionPrice()`関数を通じて**現在の**オークション価格を取得できます。オークション開始前の価格は`開始価格 AUCTION_START_PRICE`であることが観察でき、オークションが進行するにつれて、オークション価格は徐々に下降し、`フロア価格 AUCTION_END_PRICE`まで下降した後は変化しません。
+
+
+
+3. Mint操作:`auctionMint()`関数を通じてmintを完了します。この例では、時間がすでにオークション時間を超えているため、`フロア価格`のみでオークションが完了したことが分かります。
+
+
+
+4. `ETH`の引き出し:`withdrawMoney()`関数を通じて直接、調達した`ETH`を`call()`でコントラクト作成者のアドレスに送信できます。
+
+## まとめ
+
+この講義では、ダッチオークションを紹介し、簡素化版`Azuki`ダッチオークションコードを通じて、`ダッチオークション`を使用して`ERC721`標準の`NFT`を発行する方法を説明しました。私がオークションで手に入れた最も高価な`NFT`は、音楽家`Jonathan Mann`の音楽`NFT`です。あなたはどうですか?
\ No newline at end of file
diff --git a/Languages/ja/36_MerkleTree_ja/readme.md b/Languages/ja/36_MerkleTree_ja/readme.md
new file mode 100644
index 000000000..14ba9aa10
--- /dev/null
+++ b/Languages/ja/36_MerkleTree_ja/readme.md
@@ -0,0 +1,213 @@
+---
+title: 36. マークルツリー
+tags:
+ - solidity
+ - application
+ - wtfacademy
+ - ERC721
+ - Merkle Tree
+---
+
+# WTF Solidity極簡入門: 36. マークルツリー
+
+私は最近Solidityを再学習し、詳細を固めながら「WTF Solidity極簡入門」を書いています。これは初心者向けです(プログラミング上級者は他のチュートリアルを参照してください)。毎週1-3講を更新します。
+
+Twitter:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[WeChatグループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化されています:[github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+-----
+
+この講義では、`マークルツリー`について紹介し、それを使用して`NFT`ホワイトリストを配布する方法を説明します。
+
+## `マークルツリー`
+
+`マークルツリー`は、メルクルツリーまたはハッシュツリーとも呼ばれ、ブロックチェーンの基本的な暗号技術であり、ビットコインやイーサリアムブロックチェーンで広く使用されています。`マークルツリー`は下から上に構築される暗号化ツリーで、各リーフは対応するデータのハッシュに対応し、各非リーフは2つの子ノードのハッシュを表します。
+
+
+
+`マークルツリー`は大規模データ構造の内容を効率的かつ安全に検証(`マークル証明`)することを可能にします。`N`個のリーフノードを持つ`マークルツリー`において、指定されたデータが有効か(`マークルツリー`のリーフノードに属するか)を検証するのに必要なのは`log(N)`個のデータ(`proofs`)のみであり、非常に効率的です。データが間違っているか、与えられた`proof`が間違っている場合、`root`の根の値を復元することはできません。
+
+以下の例では、リーフ`L1`の`マークル証明`は`Hash 0-1`と`Hash 1`です:これら2つの値を知ることで、`L1`の値が`マークルツリー`のリーフにあるかどうかを検証できます。なぜでしょうか?
+リーフ`L1`を通じて`Hash 0-0`を計算でき、`Hash 0-1`も知っているので、`Hash 0-0`と`Hash 0-1`を組み合わせて`Hash 0`を計算でき、`Hash 1`も知っているので、`Hash 0`と`Hash 1`を組み合わせて`Top Hash`(根ノードのハッシュ)を計算できるからです。
+
+
+
+## `マークルツリー`の生成
+
+[ウェブページ](https://lab.miguelmota.com/merkletreejs/example/)またはJavascriptライブラリ[merkletreejs](https://github.com/miguelmota/merkletreejs)を使用して`マークルツリー`を生成できます。
+
+ここでは、ウェブページを使用して`4`つのアドレスをリーフノードとする`マークルツリー`を生成します。リーフノードの入力:
+
+```solidity
+ [
+ "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
+ "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
+ "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
+ "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
+ ]
+```
+
+メニューで`Keccak-256`、`hashLeaves`、`sortPairs`オプションを選択し、`Compute`をクリックすると、`マークルツリー`が生成されます。`マークルツリー`は以下のように展開されます:
+
+```
+└─ Root: eeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097
+ ├─ 9d997719c0a5b5f6db9b8ac69a988be57cf324cb9fffd51dc2c37544bb520d65
+ │ ├─ Leaf0:5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229
+ │ └─ Leaf1:999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb
+ └─ 4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c
+ ├─ Leaf2:04a10bfd00977f54cc3450c9b25c9b3a502a089eba0097ba35fc33c4ea5fcb54
+ └─ Leaf3:dfbe3e504ac4e35541bebad4d0e7574668e16fefa26cd4172f93e18b59ce9486
+```
+
+
+
+## `マークル証明`の検証
+
+ウェブサイトを通じて、`アドレス0`の`proof`を以下のように取得できます。これは図2の青いノードのハッシュ値です:
+
+```solidity
+[
+ "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb",
+ "0x4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c"
+]
+```
+
+`MerkleProof`ライブラリを使用して検証します:
+
+```solidity
+library MerkleProof {
+ /**
+ * @dev `proof`と`leaf`から再構築された`root`が与えられた`root`と等しい場合、`true`を返します。データが有効であることを意味します。
+ * 再構築中、リーフノードペアと要素ペアの両方がソートされます。
+ */
+ function verify(
+ bytes32[] memory proof,
+ bytes32 root,
+ bytes32 leaf
+ ) internal pure returns (bool) {
+ return processProof(proof, leaf) == root;
+ }
+
+ /**
+ * @dev `leaf`と`proof`から計算された`マークルツリー`の`root`を返します。
+ * `proof`は再構築された`root`が与えられた`root`と等しい場合のみ有効です。
+ * 再構築中、リーフノードペアと要素ペアの両方がソートされます。
+ */
+ function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
+ bytes32 computedHash = leaf;
+ for (uint256 i = 0; i < proof.length; i++) {
+ computedHash = _hashPair(computedHash, proof[i]);
+ }
+ return computedHash;
+ }
+
+ // ソート済みペアハッシュ
+ function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
+ return a < b ? keccak256(abi.encodePacked(a, b)) : keccak256(abi.encodePacked(b, a));
+ }
+}
+```
+
+`MerkleProof`ライブラリには3つの関数が含まれています:
+
+1. `verify()`関数:`proof`を使用して`leaf`が`root`を根とする`マークルツリー`に属するかを検証します。属する場合は`true`を返します。`processProof()`関数を呼び出します。
+
+2. `processProof()`関数:`proof`と`leaf`を順番に使用して`マークルツリー`の`root`を計算します。`_hashPair()`関数を呼び出します。
+
+3. `_hashPair()`関数:`keccak256()`関数を使用して非根ノードに対応する2つの子ノードのハッシュ(ソート済み)を計算します。
+
+`verify()`関数に`アドレス0`、`root`、および対応する`proof`を入力すると、`true`を返します。なぜなら`アドレス0`は`root`を根とする`マークルツリー`にあり、`proof`が正しいからです。これらの値のいずれかを変更すると、`false`を返します。
+
+`マークルツリー`を使用したNFTホワイトリストの配布:
+
+800個のアドレスのホワイトリストを更新すると、ガス手数料で1 ETH以上を簡単に消費する可能性があります。しかし、`マークルツリー`検証を使用すると、`leaf`と`proof`はバックエンドに存在でき、チェーン上には`root`の値を1つだけ保存すればよく、非常にガス効率的です。多くの`ERC721` NFTや`ERC20`標準トークンのホワイトリスト/エアドロップは`マークルツリー`を使用して発行されており、Optimismのエアドロップなどがあります。
+
+ここでは、`MerkleTree`コントラクトを使用してNFTホワイトリストを配布する方法を紹介します:
+
+```solidity
+contract MerkleTree is ERC721 {
+ bytes32 immutable public root; // マークルツリーの根
+ mapping(address => bool) public mintedAddress; // 既にミントされたアドレスを記録
+
+ // コンストラクタ、NFTコレクションの名前とシンボル、マークルツリーの根を初期化
+ constructor(string memory name, string memory symbol, bytes32 merkleroot)
+ ERC721(name, symbol)
+ {
+ root = merkleroot;
+ }
+
+ // マークルツリーを使用してアドレスを検証し、ミント
+ function mint(address account, uint256 tokenId, bytes32[] calldata proof)
+ external
+ {
+ require(_verify(_leaf(account), proof), "Invalid merkle proof"); // マークル検証が通過
+ require(!mintedAddress[account], "Already minted!"); // アドレスがまだミントされていない
+
+ mintedAddress[account] = true; // ミントされたアドレスを記録
+ _mint(account, tokenId); // ミント
+ }
+
+ // マークルツリーのリーフのハッシュ値を計算
+ function _leaf(address account)
+ internal pure returns (bytes32)
+ {
+ return keccak256(abi.encodePacked(account));
+ }
+
+ // マークルツリー検証、MerkleProofライブラリのverify()関数を呼び出し
+ function _verify(bytes32 leaf, bytes32[] memory proof)
+ internal view returns (bool)
+ {
+ return MerkleProof.verify(proof, root, leaf);
+ }
+}
+```
+
+`MerkleTree`コントラクトは`ERC721`標準を継承し、`MerkleProof`ライブラリを利用しています。
+
+### 状態変数
+
+コントラクトには2つの状態変数があります:
+- `root`は`マークルツリー`の根を保存し、コントラクトデプロイ時に割り当てられます。
+- `mintedAddress`は`mapping`で、ミントされたアドレスを記録します。ミント成功後に値が割り当てられます。
+
+### 関数
+
+コントラクトには4つの関数があります:
+- コンストラクタ:NFTの名前とシンボル、`マークルツリー`の`root`を初期化します。
+- `mint()`関数:ホワイトリストを使用してNFTをミントします。引数として`account`(ホワイトリストアドレス)、`tokenId`(ミントされるID)、`proof`を取ります。関数はまず`address`がホワイトリストにあるかを検証します。検証が通過すると、ID `tokenId`のNFTがアドレスにミントされ、`mintedAddress`に記録されます。このプロセスは`_leaf()`関数と`_verify()`関数を呼び出します。
+- `_leaf()`関数:`マークルツリー`のリーフアドレスのハッシュを計算します。
+- `_verify()`関数:`MerkleProof`ライブラリの`verify()`関数を呼び出して`マークルツリー`を検証します。
+
+### `Remix`検証
+
+上記の例の4つのアドレスをホワイトリストとして使用し、`マークルツリー`を生成します。3つの引数で`MerkleTree`コントラクトをデプロイします:
+
+```solidity
+name = "WTF MerkleTree"
+symbol = "WTF"
+merkleroot = 0xeeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097
+```
+
+
+
+次に、`mint`関数を実行してアドレス0のために`NFT`をミントします。3つのパラメータを使用します:
+
+```solidity
+account = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+tokenId = 0
+proof = ["0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb", "0x4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c"]
+```
+
+`ownerOf`関数を使用して、NFTの`tokenId` 0がアドレス0にミントされたことを検証でき、コントラクトが正常に実行されたことが確認できます。
+
+`tokenId`の保有者を0に変更しても、コントラクトは正常に実行されます。
+
+この時点で再度`mint`関数を呼び出すと、アドレスは`マークル証明`検証を通過できますが、アドレスが既に`mintedAddress`に記録されているため、`"Already minted!"`によりトランザクションが中止されます。
+
+この講義では、`マークルツリー`の概念、簡単な`マークルツリー`の生成方法、スマートコントラクトを使用した`マークルツリー`の検証方法、およびそれを使用して`NFT`ホワイトリストを配布する方法を紹介しました。
+
+実際の使用では、複雑な`マークルツリー`はJavascriptの`merkletreejs`ライブラリを使用して生成・管理でき、チェーン上には1つの根の値のみを保存すればよく、非常にガス効率的です。多くのプロジェクトチームが`マークルツリー`を使用してホワイトリストを配布することを選択しています。
\ No newline at end of file
diff --git a/Languages/ja/37_Signature_ja/readme.md b/Languages/ja/37_Signature_ja/readme.md
new file mode 100644
index 000000000..608e9ab5e
--- /dev/null
+++ b/Languages/ja/37_Signature_ja/readme.md
@@ -0,0 +1,294 @@
+---
+title: 37. デジタル署名
+tags:
+ - Solidity
+ - Application
+ - WTF Academy
+ - ERC721
+ - Signature
+---
+
+# WTF Solidity極簡入門: 37. デジタル署名
+
+私は最近Solidityを再学習し、詳細を固めながら「WTF Solidity極簡入門」を書いています。これは初心者向けです(プログラミング上級者は他のチュートリアルを参照してください)。毎週1-3講を更新します。
+
+Twitter:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[WeChatグループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化されています:[github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+-----
+
+この講義では、イーサリアムのデジタル署名`ECDSA`について簡単に紹介し、それを使用して`NFT`ホワイトリストを発行する方法を説明します。コードで使用される`ECDSA`ライブラリは`OpenZeppelin`の同名ライブラリを簡素化したものです。
+
+## デジタル署名
+
+`opensea`で`NFT`を取引したことがあれば、署名は馴染みがあるでしょう。以下の画像は`metamask`ウォレットが署名する際にポップアップするウィンドウで、秘密鍵を公開することなく、秘密鍵を所有していることを証明できます。
+
+
+
+イーサリアムで使用されるデジタル署名アルゴリズムは楕円曲線デジタル署名アルゴリズム(`ECDSA`)と呼ばれ、楕円曲線の「秘密鍵-公開鍵」ペアに基づくデジタル署名アルゴリズムです。主に[3つの役割](https://en.wikipedia.org/wiki/Digital_signature)を果たします:
+
+1. **身元認証**:署名者が秘密鍵の所有者であることを証明します。
+2. **否認防止**:送信者がメッセージを送信したことを否定できません。
+3. **完全性**:メッセージが送信中に変更されていないことを保証します。
+
+## `ECDSA`コントラクト
+
+`ECDSA`標準は2つの部分で構成されています:
+
+1. 署名者が`秘密鍵`(非公開)を使用して`メッセージ`(公開)に対する`署名`(公開)を作成します。
+2. 他の人が`メッセージ`(公開)と`署名`(公開)を使用して署名者の`公開鍵`(公開)を復元し、署名を検証します。
+
+`ECDSA`ライブラリと一緒にこれら2つの部分を説明します。このチュートリアルで使用される`秘密鍵`、`公開鍵`、`メッセージ`、`イーサリアム署名メッセージ`、`署名`は以下の通りです:
+
+```
+秘密鍵: 0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b
+公開鍵: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2
+メッセージ: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
+イーサリアム署名メッセージ: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
+署名: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
+```
+
+### 署名の作成
+
+**1. メッセージのパッキング:** イーサリアム`ECDSA`標準では、署名される`メッセージ`は一組のデータの`keccak256`ハッシュで、`bytes32`型です。署名したい内容は`abi.encodePacked()`関数を使用してパッキングし、`keccak256()`を使用してハッシュを計算して`メッセージ`とします。この例では、`メッセージ`は'uint256`型変数と`address`型変数から取得されます。
+
+```solidity
+/*
+ * ミントアドレス(address型)とtokenId(uint256型)を連結してメッセージmsgHashを形成
+ * _account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+ * _tokenId: 0
+ * 対応するメッセージmsgHash: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
+ */
+function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){
+ return keccak256(abi.encodePacked(_account, _tokenId));
+}
+```
+
+
+
+**2. イーサリアム署名メッセージの計算:** `メッセージ`は実行可能なトランザクションでも他の何でもかまいません。ユーザーが誤って悪意のあるトランザクションに署名することを防ぐため、`EIP191`は`メッセージ`の前に`"\x19Ethereum Signed Message:\n32"`文字を追加し、再度`keccak256`ハッシュを行って`イーサリアム署名メッセージ`を作成することを推奨しています。`toEthSignedMessageHash()`関数で処理されたメッセージはトランザクションの実行に使用できません。
+
+```solidity
+ /**
+ * @dev イーサリアム署名メッセージハッシュを返します。
+ * `hash`: ハッシュ化されるメッセージ
+ * イーサリアム署名標準に従います: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
+ * および `EIP191`:https://eips.ethereum.org/EIPS/eip-191`
+ * 実行可能なトランザクションの署名を防ぐため"\x19Ethereum Signed Message:\n32"文字列を追加します。
+ */
+ function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
+ // ハッシュの長さは32
+ return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
+ }
+```
+
+処理されたメッセージは:
+
+```
+イーサリアム署名メッセージ: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
+```
+
+
+
+**3-1. ウォレットで署名:** 日常的な操作では、ほとんどのユーザーがこの方法でメッセージに署名します。署名が必要なメッセージを取得した後、`Metamask`ウォレットを使用して署名する必要があります。`Metamask`の`personal_sign`メソッドは自動的に`メッセージ`を`イーサリアム署名メッセージ`に変換してから署名を開始します。そのため、`メッセージ`と`署名者ウォレットアカウント`を入力するだけで済みます。なお、入力する`署名者ウォレットアカウント`は`Metamask`で現在接続されているアカウントと一致している必要があります。
+
+したがって、まず例の`秘密鍵`を`Metamask`ウォレットにインポートし、ブラウザの`コンソール`ページを開く必要があります:`Chromeメニュー-その他のツール-開発者ツール-Console`。ウォレットに接続された状態で(OpenSeaなどに接続、そうでなければエラーが発生)、以下の手順を順番に入力して署名します:
+
+```
+ethereum.enable()
+account = "0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2"
+hash = "0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c"
+ethereum.request({method: "personal_sign", params: [account, hash]})
+```
+
+作成された署名は返された結果(`PromiseResult`)で確認できます。異なるアカウントは異なる秘密鍵を持ち、作成される署名値も異なります。チュートリアルの秘密鍵を使用して作成された署名は以下の通りです:
+
+```
+0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
+```
+
+
+
+**3-2. web3.pyで署名:** バッチ呼び出しにおいては、コードでの署名が好まれます。以下はweb3.pyに基づく実装です。
+
+これは`web3`ライブラリと`eth_account`モジュールを使用して、与えられた秘密鍵とイーサリアムアドレスでメッセージに署名するPythonコードです。Ankr ETH RPCエンドポイントに接続し、メッセージのkeccakハッシュと結果の署名を出力します。
+
+実行結果は以下の通りです。計算されたメッセージ、署名、および以前の例は一致しています。
+
+```
+メッセージ:0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
+署名:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
+```
+
+### 署名の検証
+
+署名を検証するには、検証者は`メッセージ`、`署名`、およびメッセージの署名に使用された`公開鍵`を持っている必要があります。署名を検証できるのは、`秘密鍵`の所有者のみがそのトランザクションに対してそのような署名を生成でき、他の誰もできないからです。
+
+**4. 署名とメッセージから公開鍵を復元:** `署名`は数学的アルゴリズムによって生成されます。ここでは`rsv署名`を使用し、これは`r, s, v`の情報を含みます。次に、`r, s, v`と`イーサリアム署名メッセージ`から`公開鍵`を取得できます。以下の`recoverSigner()`関数は上記の手順を実装しています。`イーサリアム署名メッセージ _msgHash`と`署名 _signature`から`公開鍵`を復元します(シンプルなインラインアセンブリを使用):
+
+```solidity
+ // @dev _msgHashと署名_signatureから署名者アドレスを復元
+ function recoverSigner(bytes32 _msgHash, bytes memory _signature) internal pure returns (address) {
+ // 署名の長さをチェック。65は標準的なr,s,v署名の長さ。
+ require(_signature.length == 65, "invalid signature length");
+ bytes32 r;
+ bytes32 s;
+ uint8 v;
+ // 現在、アセンブリを使用してのみ署名からr,s,vの値を取得可能。
+ assembly {
+ /*
+ 最初の32バイトは署名の長さを保存(動的配列保存ルール)
+ add(sig, 32) = 署名ポインタ + 32
+ 署名の最初の32バイトをスキップすることと等価
+ mload(p) はメモリアドレスpから次の32バイトのデータをロード
+ */
+ // 長さデータの後の次の32バイトを読み取り
+ r := mload(add(_signature, 0x20))
+ // rの後の次の32バイトを読み取り
+ s := mload(add(_signature, 0x40))
+ // 最後のバイトを読み取り
+ v := byte(0, mload(add(_signature, 0x60)))
+ }
+ // ecrecover(グローバル関数)を使用してmsgHash、r,s,vから署名者アドレスを復元
+ return ecrecover(_msgHash, v, r, s);
+ }
+```
+
+パラメータは:
+
+```
+_msgHash:0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
+_signature:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
+```
+
+
+
+**5. 公開鍵の比較と署名の検証:** 次に、復元された`公開鍵`と署名者の公開鍵`_signer`を比較して等しいかどうかを判定するだけです:等しければ署名は有効、そうでなければ署名は無効です。
+
+```solidity
+/**
+* @dev ECDSAを通じて署名アドレスが正しいかを検証します。正しければtrueを返します。
+* _msgHashはメッセージのハッシュです。
+* _signatureは署名です。
+* _signerは署名者のアドレスです。
+*/
+function verify(bytes32 _msgHash, bytes memory _signature, address _signer) internal pure returns (bool) {
+ return recoverSigner(_msgHash, _signature) == _signer;
+}
+```
+
+パラメータは:
+
+```
+_msgHash:0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
+_signature:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
+_signer:0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2
+```
+
+
+
+## 署名を使用したNFTホワイトリストの発行
+
+`NFT`プロジェクトは`ECDSA`の機能を使用してホワイトリストを発行できます。署名はオフチェーンで行われ、`gas`を必要としないため、このホワイトリスト発行モードは`マークルツリー`モードよりも経済的です。方法は非常にシンプルです。プロジェクトがプロジェクトアカウントを使用してホワイトリスト発行アドレスに署名します(アドレスがミントできる`tokenId`を追加可能)。そして、`ミント`時に`ECDSA`を使用して署名が有効かをチェックします。有効であれば`ミント`を許可します。
+
+`SignatureNFT`コントラクトは署名を使用した`NFT`ホワイトリストの発行を実装しています。
+
+### 状態変数
+
+コントラクトには2つの状態変数があります:
+- `signer`:`公開鍵`、プロジェクト署名アドレス。
+- `mintedAddress`は`mapping`で、既に`ミント`されたアドレスを記録します。
+
+### 関数
+
+コントラクトには4つの関数があります:
+- コンストラクタは`NFT`の名前とシンボル、`ECDSA`署名の`signer`アドレスを初期化します。
+- `mint()`関数は3つのパラメータを受け取ります:アドレス`address`、`tokenId`、`_signature`、署名が有効かを検証します:有効であれば、`tokenId`の`NFT`を`address`アドレスにミントし、`mintedAddress`に記録します。`getMessageHash()`、`ECDSA.toEthSignedMessageHash()`、`verify()`関数を呼び出します。
+- `getMessageHash()`関数は`ミント`アドレス(`address`型)と`tokenId`(`uint256`型)を組み合わせて`メッセージ`にします。
+- `verify()`関数は`ECDSA`ライブラリの`verify()`関数を呼び出して`ECDSA`署名検証を行います。
+
+```solidity
+contract SignatureNFT is ERC721 {
+ // ミントリクエストに署名するアドレス
+ address immutable public signer;
+
+ // ミントに既に使用されたアドレスを追跡するマッピング
+ mapping(address => bool) public mintedAddress;
+
+ // NFTコレクションの名前、シンボル、署名者アドレスを初期化するコンストラクタ関数
+ constructor(string memory _name, string memory _symbol, address _signer)
+ ERC721(_name, _symbol)
+ {
+ signer = _signer;
+ }
+
+ // ECDSAを使用して署名を検証し、指定されたアドレスに指定されたIDで新しいトークンをミント
+ function mint(address _account, uint256 _tokenId, bytes memory _signature)
+ external
+ {
+ bytes32 _msgHash = getMessageHash(_account, _tokenId); // アドレスとトークンIDを連結してメッセージハッシュを作成
+ bytes32 _ethSignedMessageHash = ECDSA.toEthSignedMessageHash(_msgHash); // イーサリアム署名メッセージハッシュを計算
+ require(verify(_ethSignedMessageHash, _signature), "Invalid signature"); // ECDSAを使用して署名を検証
+ require(!mintedAddress[_account], "Already minted!"); // アドレスがまだミントに使用されていないことを確認
+
+ mintedAddress[_account] = true; // アドレスがミントに使用されたことを記録
+ _mint(_account, _tokenId); // 指定されたアドレスに新しいトークンをミント
+ }
+
+ /*
+ * アドレスとトークンIDを連結してメッセージハッシュを作成
+ * _account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+ * _tokenId: 0
+ * 対応するメッセージハッシュ: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
+ */
+ function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){
+ return keccak256(abi.encodePacked(_account, _tokenId));
+ }
+
+ // ECDSAライブラリを使用して署名を検証
+ function verify(bytes32 _msgHash, bytes memory _signature)
+ public view returns (bool)
+ {
+ return ECDSA.verify(_msgHash, _signature, signer);
+ }
+}
+```
+
+### `remix`検証
+
+- イーサリアムで`署名`をオフチェーンで署名し、`tokenId = 0`で`_account`アドレスをホワイトリストに追加します。使用されるデータについては<`ECDSA`コントラクト>セクションを参照してください。
+
+- 以下のパラメータで`SignatureNFT`コントラクトをデプロイします:
+
+```
+_name: WTF Signature
+_symbol: WTF
+_signer: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2
+```
+
+SignatureNFTコントラクトのデプロイ。
+
+ECDSA検証を使用してコントラクトに署名・ミントするために`mint()`関数を呼び出します。パラメータは以下の通りです:
+
+```
+_account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+_tokenId: 0
+_signature: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
+```
+
+
+
+- `ownerOf()`関数を呼び出すことで、`tokenId = 0`が正常にアドレス`_account`にミントされたことが確認でき、コントラクトが正常に実行されたことが分かります!
+
+
+
+## まとめ
+
+この講義では、イーサリアムのデジタル署名`ECDSA`、`ECDSA`を使用した署名の作成と検証方法、`ECDSA`コントラクト、およびそれらを使用した`NFT`ホワイトリストの配布について紹介しました。コードの`ECDSA`ライブラリは`OpenZeppelin`の同じライブラリを簡素化したものです。
+
+- 署名はオフチェーンで行われ、`gas`を必要としないため、このホワイトリスト配布モデルは`マークルツリー`モデルよりもコスト効率が良いです;
+- ただし、ユーザーが署名を取得するために中央集権的なインターフェースにリクエストを送る必要があるため、必然的にある程度の分散化が犠牲になります;
+- もう一つの利点は、ホワイトリストを動的に変更できることです。プロジェクトの中央バックエンドインターフェースが任意の新しいアドレスからのリクエストを受け入れ、ホワイトリスト署名を提供できるため、コントラクトに事前にハードコードする必要がありません。
\ No newline at end of file
diff --git a/Languages/ja/38_NFTSwap_ja/readme.md b/Languages/ja/38_NFTSwap_ja/readme.md
new file mode 100644
index 000000000..29039aa8b
--- /dev/null
+++ b/Languages/ja/38_NFTSwap_ja/readme.md
@@ -0,0 +1,293 @@
+---
+title: 38. NFT取引所
+tags:
+ - solidity
+ - application
+ - wtfacademy
+ - ERC721
+ - NFT Swap
+---
+
+# WTF Solidity極簡入門: 38. NFT取引所
+
+私は最近Solidityを再学習し、詳細を固めながら「WTF Solidity極簡入門」を書いています。これは初心者向けです(プログラミング上級者は他のチュートリアルを参照してください)。毎週1-3講を更新します。
+
+Twitter:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[WeChatグループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化されています:[github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+-----
+
+「Opensea」はイーサリアム上で最大のNFT取引プラットフォームで、総取引額は300億ドルです。Openseaは取引に2.5%の手数料を課しており、つまりユーザーの取引を通じて少なくとも7億5000万ドルの利益を得ています。さらに、その運営は分散化されておらず、ユーザーに補償するトークンを発行する計画もありません。NFTプレイヤーはOpenseaに長い間不満を抱いています。今日、私たちはスマートコントラクトを使用して手数料ゼロの分散型NFT取引所:NFTSwapを構築します。
+
+## 設計ロジック
+
+- 売り手:NFTを売る側で、商品を出品、出品を取り消し、価格を更新できます。
+- 買い手:NFTを買う側で、商品を購入できます。
+- 注文:売り手が発行するオンチェーンNFT注文。同じtokenIdのシリーズは最大1つの注文を持つことができ、出品価格と所有者情報が含まれます。注文が完了または取り消されると、情報はクリアされます。
+
+## NFTSwapコントラクト
+
+### イベント
+
+コントラクトには、NFTの出品(list)、取り消し(revoke)、価格更新(update)、購入(purchase)の動作に対応する4つのイベントが含まれています。
+
+```solidity
+ event List(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 price);
+ event Purchase(address indexed buyer, address indexed nftAddr, uint256 indexed tokenId, uint256 price);
+ event Revoke(address indexed seller, address indexed nftAddr, uint256 indexed tokenId);
+ event Update(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 newPrice);
+```
+
+### 注文
+
+`NFT`注文は`Order`構造として抽象化され、出品価格(`price`)と所有者(`owner`)の情報が含まれます。`nftList`マッピングは注文が対応する`NFT`シリーズ(コントラクトアドレス)と`tokenId`情報を記録します。
+
+```solidity
+ // 注文構造を定義
+ struct Order{
+ address owner;
+ uint256 price;
+ }
+ // NFT注文マッピング
+ mapping(address => mapping(uint256 => Order)) public nftList;
+```
+
+### フォールバック関数
+
+`NFTSwap`では、ユーザーは`ETH`を使用して`NFT`を購入します。そのため、コントラクトは`ETH`を受信するために`fallback()`関数を実装する必要があります。
+
+```solidity
+ fallback() external payable{}
+```
+
+### onERC721Received
+
+`ERC721`の安全転送関数は、受信コントラクトが`onERC721Received()`関数を実装し、正しいセレクタを返すかをチェックします。ユーザーが注文を出した後、`NFT`は`NFTSwap`コントラクトに送信される必要があります。そのため、`NFTSwap`コントラクトは`IERC721Receiver`インターフェースを継承し、`onERC721Received()`関数を実装します。
+
+これは「NFTSwap」という名前のスマートコントラクトで、「IERC721Receiver」インターフェースを実装しています。関数「onERC721Received」はERC721トークンを受信するために定義されています。4つのパラメータを取ります:
+- 「operator」:関数を呼び出したアドレス
+- 「from」:トークンをコントラクトに転送したアドレス
+- 「tokenId」:転送されたERC721トークンのID
+- 「data」:トークン転送と一緒に送信できる追加データ
+
+関数は「IERC721Receiver」インターフェースの「onERC721Received」関数のセレクタを返します。
+
+### 取引
+
+コントラクトは取引に関連する`4`つの関数を実装しています:
+
+- 出品`list()`:売り手が`NFT`を作成し、注文を作成し、`List`イベントを発行します。パラメータは`NFT`コントラクトアドレス`_nftAddr`、`NFT`の対応する`_tokenId`、出品価格`_price`(**注意:単位は`wei`**)です。成功後、`NFT`は売り手から`NFTSwap`コントラクトに転送されます。
+
+```solidity
+ // 出品:売り手がNFTを販売に出品、コントラクトアドレスは_nftAddr、tokenIdは_tokenId、価格は_price(単位はwei)
+ function list(address _nftAddr, uint256 _tokenId, uint256 _price) public{
+ IERC721 _nft = IERC721(_nftAddr); // インターフェースコントラクト変数IERC721を宣言
+ require(_nft.getApproved(_tokenId) == address(this), "Need Approval"); // コントラクトが承認されている
+ require(_price > 0); // 価格が0より大きい
+
+ Order storage _order = nftList[_nftAddr][_tokenId]; // NFT所有者と価格を設定
+ _order.owner = msg.sender;
+ _order.price = _price;
+ // NFTをコントラクトに転送
+ _nft.safeTransferFrom(msg.sender, address(this), _tokenId);
+
+ // Listイベントを発行
+ emit List(msg.sender, _nftAddr, _tokenId, _price);
+ }
+```
+
+- `revoke()`:売り手が注文をキャンセルし、`Revoke`イベントを発行します。パラメータには`NFT`コントラクトアドレス`_nftAddr`と対応する`_tokenId`が含まれます。実行成功後、`NFT`は`NFTSwap`コントラクトから売り手に返還されます。
+
+```solidity
+// 注文キャンセル:売り手が注文をキャンセル
+function revoke(address _nftAddr, uint256 _tokenId) public {
+ Order storage _order = nftList[_nftAddr][_tokenId]; // 注文を取得
+ require(_order.owner == msg.sender, "Not Owner"); // 所有者によって開始される必要がある
+ // IERC721インターフェースコントラクト変数を宣言
+ IERC721 _nft = IERC721(_nftAddr);
+ require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFTがコントラクト内にある
+
+ // NFTを売り手に転送
+ _nft.safeTransferFrom(address(this), msg.sender, _tokenId);
+ delete nftList[_nftAddr][_tokenId]; // 注文を削除
+
+ // Revokeイベントを発行
+ emit Revoke(msg.sender, _nftAddr, _tokenId);
+}
+```
+
+- 価格変更`update()`:売り手がNFT注文の価格を変更し、`Update`イベントを発行します。パラメータはNFTコントラクトアドレス`_nftAddr`、NFTの対応する`_tokenId`、更新された注文価格`_newPrice`(**注意:単位は`wei`**)です。
+
+```solidity
+ // 価格調整:売り手が出品価格を調整
+ function update(address _nftAddr, uint256 _tokenId, uint256 _newPrice) public {
+ require(_newPrice > 0, "Invalid Price"); // NFT価格は0より大きい必要がある
+ Order storage _order = nftList[_nftAddr][_tokenId]; // 注文を取得
+ require(_order.owner == msg.sender, "Not Owner"); // 所有者によって開始される必要がある
+ // IERC721インターフェースコントラクト変数を宣言
+ IERC721 _nft = IERC721(_nftAddr);
+ require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFTがコントラクト内にある
+
+ // NFT価格を調整
+ _order.price = _newPrice;
+
+ // Updateイベントを発行
+ emit Update(msg.sender, _nftAddr, _tokenId, _newPrice);
+ }
+```
+
+- 購入:買い手が`ETH`を支払って注文の`NFT`を購入し、`Purchase`イベントをトリガーします。パラメータは`NFT`コントラクトアドレス`_nftAddr`と`NFT`の対応する`_tokenId`です。成功すると、`ETH`は売り手に転送され、`NFT`は`NFTSwap`コントラクトから買い手に転送されます。
+
+```solidity
+ // 購入:買い手がNFTを購入、コントラクトは_nftAddr、tokenIdは_tokenId、関数呼び出し時にETHが必要
+ function purchase(address _nftAddr, uint256 _tokenId) public payable {
+ Order storage _order = nftList[_nftAddr][_tokenId]; // 注文を取得
+ require(_order.price > 0, "Invalid Price"); // NFT価格が0より大きい
+ require(msg.value >= _order.price, "Increase price"); // 購入価格が出品価格より大きい
+ // IERC721インターフェースコントラクト変数を宣言
+ IERC721 _nft = IERC721(_nftAddr);
+ require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFTがコントラクト内にある
+
+ // NFTを買い手に転送
+ _nft.safeTransferFrom(address(this), msg.sender, _tokenId);
+ // ETHを売り手に転送し、余剰のETHを買い手に返金
+ payable(_order.owner).transfer(_order.price);
+ if (msg.value > _order.price) {
+ payable(msg.sender).transfer(msg.value - _order.price);
+ }
+
+ // Purchaseイベントを発行
+ emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price);
+
+ delete nftList[_nftAddr][_tokenId]; // 注文を削除
+ }
+```
+
+## `Remix`での実装
+
+### 1. NFTコントラクトのデプロイ
+
+NFTについて学び、`WTFApe` NFTコントラクトをデプロイするには、[ERC721](https://github.com/AmazingAng/WTF-Solidity/tree/main/34_ERC721)チュートリアルを参照してください。
+
+
+
+最初のNFTを自分にミントします。これは将来NFTの出品や価格変更などの操作を実行できるようにするためです。
+
+`mint(address to, uint tokenId)`関数は2つのパラメータを取ります:
+
+`to`:NFTがミントされるアドレス。これは通常自分のウォレットアドレスです。
+
+`tokenId`:`WTFApe`コントラクトが合計10,000個のNFTを定義しているため、ここでミントされる最初の2つのNFTの`tokenId`値はそれぞれ`0`と`1`です。
+
+
+
+`WTFApe`コントラクトで、`ownerOf`を使用して`tokenId`が0のNFTを所有していることを確認します。
+
+`ownerOf(uint tokenId)`関数は1つのパラメータを取ります:
+
+`tokenId`:`tokenId`はNFTの一意の識別子で、この例では上述のミントプロセスで生成された`0` idを指します。
+
+
+
+上記の方法を使用して、`tokenId` `0`と`1`のNFTを自分にミントします。`tokenId` `0`には購入更新操作を実行し、`tokenId` `1`には出品取り消し操作を実行します。
+
+### 2. `NFTSwap`コントラクトのデプロイ
+
+`NFTSwap`コントラクトをデプロイします。
+
+
+
+### 3. `NFTSwap`コントラクトに出品のためのNFTを承認
+
+`WTFApe`コントラクトで、`approve()`承認関数を呼び出して、所有している`tokenId` `0`のNFTを`NFTSwap`コントラクトが出品できるように許可を与えます。
+
+`approve(address to, uint tokenId)`メソッドは2つのパラメータを持ちます:
+
+`to`:`tokenId`が転送を承認されるアドレス、この場合は`NFTSwap`コントラクトのアドレス。
+
+`tokenId`:`tokenId`はNFTの一意の識別子で、この例では上述のミントプロセスで生成された`0` idを指します。
+
+
+
+上記の方法に従って、`tokenId`が`1`のNFTを`NFTSwap`コントラクトアドレスに承認します。
+
+### 4. NFTを販売に出品
+
+`NFTSwap`コントラクトの`list()`関数を呼び出して、呼び出し者が所有する`tokenId`が`0`のNFTを`NFTSwap`に出品します。価格を1 `wei`に設定します。
+
+`list(address _nftAddr, uint256 _tokenId, uint256 _price)`メソッドは3つのパラメータを持ちます:
+
+`_nftAddr`:`_nftAddr`はNFTコントラクトアドレスで、この場合は`WTFApe`コントラクトアドレス。
+
+`_tokenId`:`_tokenId`はNFTのIDで、この場合は上述でミントされた`0` ID。
+
+`_price`:`_price`はNFTの価格で、この場合は1 `wei`。
+
+
+
+上記の方法に従って、呼び出し者が所有する`tokenId`が`1`のNFTを`NFTSwap`に出品し、価格を1 `wei`に設定します。
+
+### 5. 出品されたNFTを表示
+
+`NFTSwap`コントラクトの`nftList()`関数を呼び出して出品されたNFTを表示します。
+
+`nftList`:以下の構造を持つNFT注文のマッピングです:
+
+`nftList[_nftAddr][_tokenId]`:`_nftAddr`と`_tokenId`を入力すると、NFT注文を返します。
+
+
+
+### 6. NFT価格の更新
+
+`NFTSwap`コントラクトの`update()`関数を呼び出して、`tokenId` 0のNFTの価格を77 `wei`に更新します。
+
+`update(address _nftAddr, uint256 _tokenId, uint256 _newPrice)`メソッドは3つのパラメータを持ちます:
+
+`_nftAddr`:`_nftAddr`はNFTコントラクトのアドレスで、この場合は`WTFApe`コントラクトアドレス。
+
+`_tokenId`:`_tokenId`はNFTのidで、この場合は上述でミントされたNFTの0というid。
+
+`_newPrice`:`_newPrice`はNFTの新しい価格で、この場合は77 `wei`。
+
+`update()`実行後、`nftList`を呼び出して更新された価格を表示します。
+
+### 5. NFTの出品取り消し
+
+`NFTSwap`コントラクトの`revoke()`関数を呼び出してNFTの出品を取り消します。
+
+上記の記事で、私たちは`tokenId`が`0`と`1`のNFTをそれぞれ出品しました。この方法では、`tokenId`が`1`のNFTを出品取り消しします。
+
+`revoke(address _nftAddr, uint256 _tokenId)`関数は2つのパラメータを持ちます:
+
+`_nftAddr`:`_nftAddr`はNFTコントラクトのアドレスで、この例では`WTFApe`コントラクトアドレス。
+
+`_tokenId`:`_tokenId`はNFTのidで、この例ではミントの`1` Id。
+
+`NFTSwap`コントラクトの`nftList()`関数を呼び出すと、NFTが出品取り消しされたことが確認できます。再度出品するには再承認が必要です。
+
+**NFTを出品取り消しした後、購入前にステップ3から再度承認して出品する必要があることに注意してください。**
+
+### 6. `NFT`の購入
+
+別のアカウントに切り替えて、`NFTSwap`コントラクトの`purchase()`関数を呼び出してNFTを購入します。購入時には、`NFT`コントラクトアドレス、`tokenId`、支払いたい`ETH`の金額を入力する必要があります。
+
+私たちは`tokenId` 1のNFTを出品取り消ししましたが、`tokenId` 0のNFTはまだ購入可能です。
+
+`purchase(address _nftAddr, uint256 _tokenId, uint256 _wei)`メソッドは3つのパラメータを持ちます:
+
+`_nftAddr`:`_nftAddr`はNFTコントラクトアドレスで、この例では`WTFApe`コントラクトアドレス。
+
+`_tokenId`:`_tokenId`はNFTのIDで、上記でミントした0。
+
+`_wei`:`_wei`は支払う`ETH`の金額で、この例では77 `wei`。
+
+
+
+### 7. NFT所有者の変更を確認
+
+購入成功後、`WTFApe`コントラクトの`ownerOf()`関数を呼び出すと、`NFT`所有者が変更されており、購入が成功したことが示されます!
+
+まとめると、この講義では手数料ゼロの分散型`NFT`取引所を構築しました。`OpenSea`は`NFT`の発展に大きく貢献しましたが、その欠点も非常に明白です:高い取引手数料、ユーザーへの報酬なし、フィッシング攻撃を招きやすい取引メカニズムなど、ユーザーが資産を失う原因となっています。現在、`Looksrare`や`dydx`などの新しい`NFT`取引プラットフォームが`OpenSea`の地位に挑戦しており、`Uniswap`も新しい`NFT`取引所を研究しています。近い将来、より優れた`NFT`取引所が利用できるようになると信じています。
\ No newline at end of file
diff --git a/Languages/ja/39_Random_ja/readme.md b/Languages/ja/39_Random_ja/readme.md
new file mode 100644
index 000000000..2128edfe8
--- /dev/null
+++ b/Languages/ja/39_Random_ja/readme.md
@@ -0,0 +1,327 @@
+---
+title: 39. Chainlinkランダム性
+tags:
+ - solidity
+ - application
+ - wtfacademy
+ - ERC721
+ - random
+ - chainlink
+---
+
+# WTF Solidity極簡入門: 39. Chainlinkランダム性
+
+私は最近Solidityを再学習し、詳細を固めながら「WTF Solidity極簡入門」を書いています。これは初心者向けです(プログラミング上級者は他のチュートリアルを参照してください)。毎週1-3講を更新します。
+
+Twitter:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[WeChatグループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化されています:[github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+多くのイーサリアムアプリケーションは乱数の使用を必要とします。例えば、NFTランダムtokenId選択、ブラインドボックス抽選、ゲームファイでの戦闘勝者のランダム決定などです。しかし、イーサリアム上のすべてのデータが公開で決定論的であるため、他のプログラミング言語のような乱数生成方法を開発者に提供できません。このチュートリアルでは、オンチェーン(ハッシュ関数)とオフチェーン(Chainlinkオラクル)の2つの乱数生成方法を紹介し、それらを使用してtokenIdランダムミントNFTを作成します。
+
+## オンチェーン乱数生成
+
+いくつかのオンチェーングローバル変数をシードとして使用し、`keccak256()`ハッシュ関数を使用して疑似乱数を取得できます。これは、ハッシュ関数が感度と均一性を持ち、「見た目に」ランダムな結果を生成できるためです。以下の`getRandomOnchain()`関数は、グローバル変数`block.timestamp`、`msg.sender`、`blockhash(block.number-1)`をシードとして乱数を取得します:
+
+```solidity
+/**
+ * チェーン上で疑似乱数を生成します。
+ * keccak256()を使用していくつかのオンチェーングローバル変数/カスタム変数をパッキングします。
+ * 返す際にuint256型に変換されます。
+*/
+function getRandomOnchain() public view returns(uint256){
+ // Remixでblockhashを生成するとエラーになります。
+ bytes32 randomBytes = keccak256(abi.encodePacked(block.timestamp, msg.sender, blockhash(block.number-1)));
+
+ return uint256(randomBytes);
+}
+```
+
+**注意**:この方法は安全ではありません:
+- 第一に、`block.timestamp`、`msg.sender`、`blockhash(block.number-1)`などの変数はすべて公開されています。ユーザーはこれらのシードによって生成される乱数を予測し、望む出力を選択してスマートコントラクトを実行できます。
+- 第二に、マイナーは`blockhash`と`block.timestamp`を操作して、自分の利益に適した乱数を生成できます。
+
+しかし、この方法は最も便利なオンチェーン乱数生成方法であり、多くのプロジェクト側がこれに依存して安全でない乱数を生成しています。`meebits`や`loots`などの有名なプロジェクトも含まれます。もちろん、これらのプロジェクトはすべて攻撃を受けました:攻撃者はランダムに抽選するのではなく、望む希少な`NFT`を偽造できます。
+
+## オフチェーン乱数生成
+
+オフチェーンで乱数を生成し、オラクルを通じてチェーンにアップロードできます。ChainlinkはVRF(Verifiable Random Function)サービスを提供しており、オンチェーン開発者はLINKトークンを支払って乱数を取得できます。Chainlink VRFには2つのバージョンがあります。第2バージョンは公式ウェブサイトでの登録と前払い手数料が必要で、使用方法は似ているため、ここでは第1バージョンVRF v1のみを紹介します。
+
+### `Chainlink VRF`使用手順
+
+
+
+簡単なコントラクトを使用してChainlink VRFの使用手順を紹介します。`RandomNumberConsumer`コントラクトはVRFから乱数をリクエストし、状態変数`randomResult`に保存できます。
+
+**1. ユーザーコントラクトが`VRFConsumerBase`を継承し、`LINK`トークンを転送**
+
+VRFを使用して乱数を取得するには、コントラクトは`VRFConsumerBase`コントラクトを継承し、コンストラクタで`VRF Coordinator`アドレス、`LINK`トークンアドレス、一意の識別子`Key Hash`、使用料`fee`を初期化する必要があります。
+
+**注意:** 異なるチェーンは異なるパラメータに対応します。詳細は[こちら](https://docs.chain.link/docs/vrf-contracts/v1/)を参照してください。
+
+チュートリアルでは、`Rinkeby`テストネットを使用します。コントラクトをデプロイした後、ユーザーはいくつかの`LINK`トークンをコントラクトに転送する必要があります。テストネット`LINK`トークンは[LINKフォーセット](https://faucets.chain.link/)から取得できます。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.21;
+
+import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
+
+contract RandomNumberConsumer is VRFConsumerBase {
+
+ bytes32 internal keyHash; // VRF一意識別子
+ uint256 internal fee; // VRF使用料
+
+uint256 public randomResult; // 乱数を保存
+
+ /**
+ * chainlink VRFを使用する際、コンストラクタはVRFConsumerBaseを継承する必要があります
+ * 異なるチェーンのパラメータは異なって記入されます。
+ *ネットワーク: Rinkebyテストネット
+ * Chainlink VRF Coordinatorアドレス: 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B
+ * LINKトークンアドレス: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709
+ * Key Hash: 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311
+ */
+ constructor()
+ VRFConsumerBase(
+ 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator
+ 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 // LINK Token
+ )
+ {
+ keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
+fee = 0.1 * 10 ** 18; // 0.1 LINK(VRF使用料、Rinkebyテストネットワーク)
+ }
+```
+
+**2. ユーザーがコントラクトを通じて乱数をリクエスト**
+
+ユーザーは`VRFConsumerBase`コントラクトから継承された`requestRandomness()`を呼び出して乱数をリクエストし、リクエスト識別子`requestId`を受け取ることができます。このリクエストは`VRF`コントラクトに渡されます。
+
+```solidity
+ /**
+ * VRFコントラクトから乱数をリクエスト
+ */
+ function getRandomNumber() public returns (bytes32 requestId) {
+ // コントラクトに十分なLINKが必要
+ require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
+
+ return requestRandomness(keyHash, fee);
+ }
+```
+
+3. `Chainlink`ノードがオフチェーンで乱数とデジタル署名を生成し、`VRF`コントラクトに送信します。
+
+4. `VRF`コントラクトが署名の有効性を検証します。
+
+5. ユーザーコントラクトが乱数を受信して使用します。
+
+`VRF`コントラクトで署名の有効性を検証した後、ユーザーコントラクトのコールバック関数`fulfillRandomness()`が自動的に呼び出され、オフチェーンで生成された乱数が送信されます。乱数を消費するロジックはこの関数で実装する必要があります。
+
+注意:ユーザーが乱数をリクエストするために呼び出す`requestRandomness()`関数と、`VRF`コントラクトが乱数を返す際に呼び出されるコールバック関数`fulfillRandomness()`は2つの別々のトランザクションで、ユーザーコントラクトと`VRF`コントラクトがそれぞれ呼び出し元となります。後者は前者より数分遅れます(チェーンごとに遅延が異なります)。
+
+```solidity
+ /**
+* VRFコントラクトのコールバック関数で、乱数が有効であることを検証した後に自動的に呼び出されます。
+ * 乱数を消費するロジックはここに書きます
+ */
+ function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
+ randomResult = randomness;
+ }
+```
+
+## `tokenId`ランダムミント`NFT`
+
+このセクションでは、オンチェーンとオフチェーンの乱数を使用して`tokenId`ランダムミント`NFT`を作成します。`Random`コントラクトは`ERC721`と`VRFConsumerBase`コントラクトの両方を継承しています。
+
+```Solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.21;
+
+import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/ERC721.sol";
+import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
+
+contract Random is ERC721, VRFConsumerBase{
+```
+
+### 状態変数
+
+- `NFT`関連
+ - `totalSupply`:`NFT`の総供給量。
+ - `ids`:`ミント`可能な`tokenId`を計算するために使用される配列、`pickRandomUniqueId()`関数を参照。
+ - `mintCount`:`ミント`された`NFT`の数。
+- `Chainlink VRF`関連
+ - `keyHash`:`VRF`の一意識別子。
+ - `fee`:`VRF`手数料。
+ - `requestToSender`:ミントのために`VRF`を申請したユーザーアドレスを記録。
+
+```solidity
+ // NFT関連
+ uint256 public totalSupply = 100; // 総供給量
+ uint256[100] public ids; // ミント可能なtokenIdを計算するために使用
+ uint256 public mintCount; // ミントされたトークン数
+ // Chainlink VRF関連
+ bytes32 internal keyHash; // Chainlink VRFのキーハッシュ
+ uint256 internal fee; // Chainlink VRFの手数料
+ // VRFリクエスト識別子に対応するミントアドレスを記録
+ mapping(bytes32 => address) public requestToSender;
+```
+
+### コンストラクタ
+
+継承された`VRFConsumerBase`と`ERC721`コントラクトの関連変数を初期化します。
+
+```
+/**
+ * Chainlink VRFを使用するため、コンストラクタはVRFConsumerBaseを継承する必要があります
+ * 異なるチェーンのパラメータは異なって記入されます
+ * ネットワーク: Rinkebyテストネット
+ * Chainlink VRF Coordinatorアドレス: 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B
+ * LINKトークンアドレス: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709
+ * Key Hash: 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311
+**/
+constructor()
+ VRFConsumerBase(
+ 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator
+ 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 // LINK Token
+ )
+ ERC721("WTF Random", "WTF")
+{
+ keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
+ fee = 0.1 * 10 ** 18; // 0.1 LINK(VRF使用料、Rinkebyテストネットワーク)
+}
+```
+
+### その他の関数
+
+コンストラクタ関数に加えて、コントラクトは他に5つの関数を定義しています:
+
+- `pickRandomUniqueId()`:乱数を受け取り、ミントに使用できる`tokenId`を返します。
+
+- `getRandomOnchain()`:オンチェーン乱数を返します(安全ではない)。
+
+- `mintRandomOnchain()`:オンチェーン乱数を使用してNFTをミントし、`getRandomOnchain()`と`pickRandomUniqueId()`を呼び出します。
+
+- `mintRandomVRF()`:`Chainlink VRF`から乱数をリクエストしてNFTをミントします。乱数を使用したミントのロジックはコールバック関数`fulfillRandomness()`にあり、これは`VRF`コントラクトによって呼び出されるため、NFTをミントするユーザーではないため、ここの関数は`requestToSender`状態変数を使用して`VRF`リクエスト識別子に対応するユーザーアドレスを記録する必要があります。
+
+- `fulfillRandomness()`:`VRF`のコールバック関数で、乱数の真正性を検証した後に`VRF`コントラクトによって自動的に呼び出されます。返されたオフチェーン乱数を使用してNFTをミントします。
+
+```solidity
+ /**
+ * uint256数値を入力し、ミント可能なtokenIdを返します
+ * アルゴリズムプロセスは次のように理解できます:totalSupply個の空のカップ(0で初期化されたids)が一列に並んでおり、各カップの隣にボールが置かれ、[0, totalSupply - 1]で番号が付けられています。
+ フィールドからボールをランダムに取る度に(ボールはカップの隣にある可能性があり、これは初期状態;カップの中にある可能性もあり、カップの隣のボールが取られたことを示し、この時はカップに最後から新しいボールを入れる)
+ そして最後のボール(まだカップの中またはカップの隣にある可能性がある)を取り出されたボールのカップに入れ、totalSupply回ループします。従来のランダム配列と比較して、ids[]の初期化のgasが省略されます。
+ */
+ function pickRandomUniqueId(
+ uint256 random
+ ) private returns (uint256 tokenId) {
+ // 最初に減算を計算してから++を計算し、(a++, ++a)の違いに注意
+ uint256 len = totalSupply - mintCount++; // ミント数量
+ require(len > 0, "mint close"); // すべてのtokenIdがミント完了
+ uint256 randomIndex = random % len; // チェーン上の乱数を取得
+
+ // 乱数を剰余してtokenIdを配列の添字として取得し、同時にlen-1として値を記録。剰余で取得した値が既に存在する場合、tokenIdは配列添字の値を取る
+ tokenId = ids[randomIndex] != 0 ? ids[randomIndex] : randomIndex; // tokenIdを取得
+ ids[randomIndex] = ids[len - 1] == 0 ? len - 1 : ids[len - 1]; // idsリストを更新
+ ids[len - 1] = 0; // 最後の要素を削除、gasを返還可能
+ }
+
+ /**
+ * チェーン上疑似乱数生成
+ * keccak256(abi.encodePacked() チェーン上のいくつかのグローバル変数/カスタム変数を記入
+ * 返す際にuint256型に変換
+ */
+ function getRandomOnchain() public view returns (uint256) {
+ /*
+ * この場合、チェーン上のランダム性はブロックハッシュ、呼び出し元アドレス、ブロック時間にのみ依存します、
+ * ランダム性を向上させたい場合、nonce等の属性を追加できますが、セキュリティ問題を根本的に解決することはできません
+ */
+ bytes32 randomBytes = keccak256(
+ abi.encodePacked(
+ blockhash(block.number - 1),
+ msg.sender,
+ block.timestamp
+ )
+ );
+ return uint256(randomBytes);
+ }
+
+ // チェーン上の疑似乱数を使用してNFTをキャスト
+ function mintRandomOnchain() public {
+ uint256 _tokenId = pickRandomUniqueId(getRandomOnchain()); // チェーン上の乱数を使用してtokenIdを生成
+ _mint(msg.sender, _tokenId);
+ }
+
+ /**
+ * VRFを呼び出して乱数を取得しNFTをミント
+ * requestRandomness()関数を呼び出して取得し、乱数を消費するロジックはVRFコールバック関数fulfillRandomness()に書かれています
+ * 呼び出し前にこのコントラクトにLINKトークンを転送してください
+ */
+ function mintRandomVRF() public returns (bytes32 requestId) {
+ // コントラクト内のLINK残高をチェック
+ require(
+ LINK.balanceOf(address(this)) >= fee,
+ "Not enough LINK - fill contract with faucet"
+ );
+ // requestRandomnessを呼び出して乱数を取得
+ requestId = requestRandomness(keyHash, fee);
+ requestToSender[requestId] = msg.sender;
+ return requestId;
+ }
+
+ /**
+ * VRFコールバック関数、VRF Coordinatorによって呼び出される
+ * 乱数を消費するロジックはこの関数に書かれています
+ */
+ function fulfillRandomness(
+ bytes32 requestId,
+ uint256 randomness
+ ) internal override {
+ address sender = requestToSender[requestId]; // requestToSenderからミンターユーザーアドレスを取得
+ uint256 _tokenId = pickRandomUniqueId(randomness); // VRFが返した乱数を使用してtokenIdを生成
+ _mint(sender, _tokenId);
+ }
+```
+
+## `remix`検証
+
+### 1. `Rinkeby`テストネットで`Random`コントラクトをデプロイ
+
+
+
+### 2. `Chainlink`フォーセットを使用して`Rinkeby`テストネットで`LINK`と`ETH`を取得
+
+
+
+### 3. `LINK`トークンを`Random`コントラクトに転送
+
+コントラクトがデプロイされた後、コントラクトアドレスをコピーし、通常の転送と同じように`LINK`をコントラクトアドレスに転送します。
+
+
+
+### 4. オンチェーン乱数を使用してNFTをミント
+
+`remix`インターフェースで、左側のオレンジ色の関数`mintRandomOnchain`をクリックし、ポップアップの`Metamask`で確認をクリックして、オンチェーン乱数を使用したミントトランザクションを開始します。
+
+
+
+### 5. `Chainlink VRF`オフチェーン乱数を使用してNFTをミント
+
+同様に、`remix`インターフェースで左側のオレンジ色の関数`mintRandomVRF`をクリックし、ポップアップの小さな狐ウォレットで確認をクリックします。`Chainlink VRF`オフチェーン乱数を使用してNFTをミントするトランザクションが開始されました。
+
+注意:`VRF`を使用して`NFT`をミントする際、トランザクションの開始とミントの成功は同じブロックではありません。
+
+
+
+
+### 6. `NFT`がミントされたことを確認
+
+上記のスクリーンショットから、この例では`tokenId=87`の`NFT`がオンチェーンでランダムにミントされ、`tokenId=77`の`NFT`が`VRF`を使用してミントされたことが分かります。
+
+## 結論
+
+`Solidity`で乱数を生成することは、他のプログラミング言語ほど簡単ではありません。このチュートリアルでは、オンチェーン(ハッシュ関数使用)とオフチェーン(`Chainlink`オラクル)の2つの乱数生成方法を紹介し、それらを使用してランダムに割り当てられた`tokenId`を持つ`NFT`を作成しました。両方の方法にはそれぞれ利点と欠点があります:オンチェーン乱数の使用は効率的ですが安全ではなく、オフチェーン乱数の生成はサードパーティのオラクルサービスに依存しますが、比較的安全で、それほど簡単で経済的ではありません。プロジェクトチームは具体的なビジネスニーズに応じて適切な方法を選択する必要があります。
+
+これらの方法に加えて、他の組織もRNG(Random Number Generation)の新しい方法を試しています。例えば[randao](https://github.com/randao/randao)は、DAOパターンでオンチェーンで真のランダム性サービスを提供することを提案しています。
\ No newline at end of file
diff --git a/Languages/ja/40_ERC1155_ja/BAYC1155.sol b/Languages/ja/40_ERC1155_ja/BAYC1155.sol
new file mode 100644
index 000000000..057b2a016
--- /dev/null
+++ b/Languages/ja/40_ERC1155_ja/BAYC1155.sol
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+// by 0xAA
+pragma solidity ^0.8.21;
+
+import "./ERC1155.sol";
+
+contract BAYC1155 is ERC1155{
+ uint256 constant MAX_ID = 10000;
+ // コンストラクタ
+ constructor() ERC1155("BAYC1155", "BAYC1155"){
+ }
+
+ //BAYCのbaseURIは ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+ function _baseURI() internal pure override returns (string memory) {
+ return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
+ }
+
+ // ミント関数
+ function mint(address to, uint256 id, uint256 amount) external {
+ // id は 10,000 を超えることはできない
+ require(id < MAX_ID, "id overflow");
+ _mint(to, id, amount, "");
+ }
+
+ // バッチミント関数
+ function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) external {
+ // id は 10,000 を超えることはできない
+ for (uint256 i = 0; i < ids.length; i++) {
+ require(ids[i] < MAX_ID, "id overflow");
+ }
+ _mintBatch(to, ids, amounts, "");
+ }
+
+}
\ No newline at end of file
diff --git a/Languages/ja/40_ERC1155_ja/ERC1155.sol b/Languages/ja/40_ERC1155_ja/ERC1155.sol
new file mode 100644
index 000000000..ebea1f501
--- /dev/null
+++ b/Languages/ja/40_ERC1155_ja/ERC1155.sol
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "./IERC1155.sol";
+import "./IERC1155Receiver.sol";
+import "./IERC1155MetadataURI.sol";
+import "../34_ERC721/String.sol";
+import "../34_ERC721/IERC165.sol";
+
+/**
+ * @dev ERC1155マルチトークン標準
+ * 詳細 https://eips.ethereum.org/EIPS/eip-1155
+ */
+contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI {
+
+ using Strings for uint256; // Stringライブラリを使用
+ // トークン名
+ string public name;
+ // トークンシンボル
+ string public symbol;
+ // トークン種類id から アカウントaccount から 残高balances へのマッピング
+ mapping(uint256 => mapping(address => uint256)) private _balances;
+ // 発起方アドレス から 承認アドレスoperator から 承認状況bool へのバッチ承認マッピング
+ mapping(address => mapping(address => bool)) private _operatorApprovals;
+
+ /**
+ * コンストラクタ、`name` と `symbol`を初期化
+ */
+ constructor(string memory name_, string memory symbol_) {
+ name = name_;
+ symbol = symbol_;
+ }
+
+ /**
+ * @dev {IERC165-supportsInterface}を参照
+ */
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
+ return
+ interfaceId == type(IERC1155).interfaceId ||
+ interfaceId == type(IERC1155MetadataURI).interfaceId ||
+ interfaceId == type(IERC165).interfaceId;
+ }
+
+ /**
+ * @dev 残高照会 IERC1155のbalanceOfを実装、accountアドレスのid種類トークン残高を返す
+ */
+ function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
+ require(account != address(0), "ERC1155: address zero is not a valid owner");
+ return _balances[id][account];
+ }
+
+ /**
+ * @dev バッチ残高照会
+ * 要件:
+ * - `accounts` と `ids` 配列の長さが等しい
+ */
+ function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
+ public view virtual override
+ returns (uint256[] memory)
+ {
+ require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
+ uint256[] memory batchBalances = new uint256[](accounts.length);
+ for (uint256 i = 0; i < accounts.length; ++i) {
+ batchBalances[i] = balanceOf(accounts[i], ids[i]);
+ }
+ return batchBalances;
+ }
+
+ /**
+ * @dev バッチ承認、呼び出し元がoperatorにすべてのトークンの使用を承認
+ * {ApprovalForAll}イベントを発行
+ * 条件:msg.sender != operator
+ */
+ function setApprovalForAll(address operator, bool approved) public virtual override {
+ require(msg.sender != operator, "ERC1155: setting approval status for self");
+ _operatorApprovals[msg.sender][operator] = approved;
+ emit ApprovalForAll(msg.sender, operator, approved);
+ }
+
+ /**
+ * @dev バッチ承認照会
+ */
+ function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
+ return _operatorApprovals[account][operator];
+ }
+
+ /**
+ * @dev 安全転送、`amount`単位の`id`種類トークンを`from`から`to`に転送
+ * {TransferSingle}イベントを発行
+ * 要件:
+ * - to はゼロアドレスではない
+ * - from は十分な残高を持ち、呼び出し元は承認を持つ
+ * - to がスマートコントラクトの場合、IERC1155Receiver-onERC1155Receivedをサポートする必要がある
+ */
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes memory data
+ ) public virtual override {
+ address operator = msg.sender;
+ // 呼び出し元は所有者または承認されている
+ require(
+ from == operator || isApprovedForAll(from, operator),
+ "ERC1155: caller is not token owner nor approved"
+ );
+ require(to != address(0), "ERC1155: transfer to the zero address");
+ // fromアドレスは十分な残高を持つ
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
+ // 残高を更新
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+ _balances[id][to] += amount;
+ // イベントを発行
+ emit TransferSingle(operator, from, to, id, amount);
+ // 安全チェック
+ _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
+ }
+
+ /**
+ * @dev バッチ安全転送、`amounts`配列単位の`ids`配列種類トークンを`from`から`to`に転送
+ * {TransferBatch}イベントを発行
+ * 要件:
+ * - to はゼロアドレスではない
+ * - from は十分な残高を持ち、呼び出し元は承認を持つ
+ * - to がスマートコントラクトの場合、IERC1155Receiver-onERC1155BatchReceivedをサポートする必要がある
+ * - ids と amounts 配列の長さが等しい
+ */
+ function safeBatchTransferFrom(
+ address from,
+ address to,
+ uint256[] memory ids,
+ uint256[] memory amounts,
+ bytes memory data
+ ) public virtual override {
+ address operator = msg.sender;
+ // 呼び出し元は所有者または承認されている
+ require(
+ from == operator || isApprovedForAll(from, operator),
+ "ERC1155: caller is not token owner nor approved"
+ );
+ require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
+ require(to != address(0), "ERC1155: transfer to the zero address");
+
+ // forループで残高を更新
+ for (uint256 i = 0; i < ids.length; ++i) {
+ uint256 id = ids[i];
+ uint256 amount = amounts[i];
+
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+ _balances[id][to] += amount;
+ }
+
+ emit TransferBatch(operator, from, to, ids, amounts);
+ // 安全チェック
+ _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
+ }
+
+ /**
+ * @dev ミント
+ * {TransferSingle}イベントを発行
+ */
+ function _mint(
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes memory data
+ ) internal virtual {
+ require(to != address(0), "ERC1155: mint to the zero address");
+
+ address operator = msg.sender;
+
+ _balances[id][to] += amount;
+ emit TransferSingle(operator, address(0), to, id, amount);
+
+ _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
+ }
+
+ /**
+ * @dev バッチミント
+ * {TransferBatch}イベントを発行
+ */
+ function _mintBatch(
+ address to,
+ uint256[] memory ids,
+ uint256[] memory amounts,
+ bytes memory data
+ ) internal virtual {
+ require(to != address(0), "ERC1155: mint to the zero address");
+ require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
+
+ address operator = msg.sender;
+
+ for (uint256 i = 0; i < ids.length; i++) {
+ _balances[ids[i]][to] += amounts[i];
+ }
+
+ emit TransferBatch(operator, address(0), to, ids, amounts);
+
+ _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
+ }
+
+ /**
+ * @dev バーン
+ */
+ function _burn(
+ address from,
+ uint256 id,
+ uint256 amount
+ ) internal virtual {
+ require(from != address(0), "ERC1155: burn from the zero address");
+
+ address operator = msg.sender;
+
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+
+ emit TransferSingle(operator, from, address(0), id, amount);
+ }
+
+ /**
+ * @dev バッチバーン
+ */
+ function _burnBatch(
+ address from,
+ uint256[] memory ids,
+ uint256[] memory amounts
+ ) internal virtual {
+ require(from != address(0), "ERC1155: burn from the zero address");
+ require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
+
+ address operator = msg.sender;
+
+ for (uint256 i = 0; i < ids.length; i++) {
+ uint256 id = ids[i];
+ uint256 amount = amounts[i];
+
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+ }
+
+ emit TransferBatch(operator, from, address(0), ids, amounts);
+ }
+
+ // エラー 無効な受信者
+ error ERC1155InvalidReceiver(address receiver);
+
+ // @dev ERC1155の安全転送チェック
+ function _doSafeTransferAcceptanceCheck(
+ address operator,
+ address from,
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes memory data
+ ) private {
+ if (to.code.length > 0) {
+ try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
+ if (response != IERC1155Receiver.onERC1155Received.selector) {
+ revert ERC1155InvalidReceiver(to);
+ }
+ } catch (bytes memory reason) {
+ if (reason.length == 0) {
+ revert ERC1155InvalidReceiver(to);
+ } else {
+ /// @solidity memory-safe-assembly
+ assembly {
+ revert(add(32, reason), mload(reason))
+ }
+ }
+ }
+ }
+ }
+
+ // @dev ERC1155のバッチ安全転送チェック
+ function _doSafeBatchTransferAcceptanceCheck(
+ address operator,
+ address from,
+ address to,
+ uint256[] memory ids,
+ uint256[] memory amounts,
+ bytes memory data
+ ) private {
+ if (to.code.length > 0) {
+ try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
+ bytes4 response
+ ) {
+ if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
+ revert ERC1155InvalidReceiver(to);
+ }
+ } catch (bytes memory reason) {
+ if (reason.length == 0) {
+ revert ERC1155InvalidReceiver(to);
+ } else {
+ /// @solidity memory-safe-assembly
+ assembly {
+ revert(add(32, reason), mload(reason))
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @dev ERC1155のid種類トークンのuriを返し、metadataを格納、ERC721のtokenURIに似ている
+ */
+ function uri(uint256 id) public view virtual override returns (string memory) {
+ string memory baseURI = _baseURI();
+ return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, id.toString())) : "";
+ }
+
+ /**
+ * {uri}のBaseURIを計算、uriはbaseURIとtokenIdを連結したもので、開発者が再実装する必要がある
+ */
+ function _baseURI() internal view virtual returns (string memory) {
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/40_ERC1155_ja/IERC1155.sol b/Languages/ja/40_ERC1155_ja/IERC1155.sol
new file mode 100644
index 000000000..6513658eb
--- /dev/null
+++ b/Languages/ja/40_ERC1155_ja/IERC1155.sol
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "../34_ERC721/IERC165.sol";
+
+/**
+ * @dev ERC1155標準のインターフェースコントラクト、EIP1155の機能を実装
+ * 詳細:https://eips.ethereum.org/EIPS/eip-1155[EIP].
+ */
+interface IERC1155 is IERC165 {
+ /**
+ * @dev 単一種類トークン転送イベント
+ * `value`個の`id`種類のトークンが`operator`によって`from`から`to`に転送されたときに発行
+ */
+ event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
+
+ /**
+ * @dev マルチ種類トークン転送イベント
+ * idsとvaluesは転送されるトークン種類と数量の配列
+ */
+ event TransferBatch(
+ address indexed operator,
+ address indexed from,
+ address indexed to,
+ uint256[] ids,
+ uint256[] values
+ );
+
+ /**
+ * @dev バッチ承認イベント
+ * `account`がすべてのトークンを`operator`に承認したときに発行
+ */
+ event ApprovalForAll(address indexed account, address indexed operator, bool approved);
+
+ /**
+ * @dev `id`種類のトークンのURIが変更されたときに発行、`value`は新しいURI
+ */
+ event URI(string value, uint256 indexed id);
+
+ /**
+ * @dev 残高照会、`account`が所有する`id`種類のトークンの残高を返す
+ */
+ function balanceOf(address account, uint256 id) external view returns (uint256);
+
+ /**
+ * @dev バッチ残高照会、`accounts`と`ids`配列の長さは等しくなければならない
+ */
+ function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
+ external
+ view
+ returns (uint256[] memory);
+
+ /**
+ * @dev バッチ承認、呼び出し元のトークンを`operator`アドレスに承認
+ * {ApprovalForAll}イベントを発行
+ */
+ function setApprovalForAll(address operator, bool approved) external;
+
+ /**
+ * @dev バッチ承認照会、承認アドレス`operator`が`account`によって承認されている場合`true`を返す
+ * {setApprovalForAll}関数を参照
+ */
+ function isApprovedForAll(address account, address operator) external view returns (bool);
+
+ /**
+ * @dev 安全転送、`amount`単位の`id`種類のトークンを`from`から`to`に転送
+ * {TransferSingle}イベントを発行
+ * 要件:
+ * - 呼び出し元が`from`アドレスでない場合、`from`の承認が必要
+ * - `from`アドレスは十分な残高を持つ必要がある
+ * - 受信者がコントラクトの場合、`IERC1155Receiver`の`onERC1155Received`メソッドを実装し、対応する値を返す必要がある
+ */
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes calldata data
+ ) external;
+
+ /**
+ * @dev バッチ安全転送
+ * {TransferBatch}イベントを発行
+ * 要件:
+ * - `ids`と`amounts`の長さが等しい
+ * - 受信者がコントラクトの場合、`IERC1155Receiver`の`onERC1155BatchReceived`メソッドを実装し、対応する値を返す必要がある
+ */
+ function safeBatchTransferFrom(
+ address from,
+ address to,
+ uint256[] calldata ids,
+ uint256[] calldata amounts,
+ bytes calldata data
+ ) external;
+}
\ No newline at end of file
diff --git a/Languages/ja/40_ERC1155_ja/IERC1155MetadataURI.sol b/Languages/ja/40_ERC1155_ja/IERC1155MetadataURI.sol
new file mode 100644
index 000000000..af797fe21
--- /dev/null
+++ b/Languages/ja/40_ERC1155_ja/IERC1155MetadataURI.sol
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "./IERC1155.sol";
+
+/**
+ * @dev ERC1155のオプションインターフェース、uri()関数でメタデータを照会
+ */
+interface IERC1155MetadataURI is IERC1155 {
+ /**
+ * @dev 第`id`種類トークンのURIを返す
+ */
+ function uri(uint256 id) external view returns (string memory);
+}
\ No newline at end of file
diff --git a/Languages/ja/40_ERC1155_ja/IERC1155Receiver.sol b/Languages/ja/40_ERC1155_ja/IERC1155Receiver.sol
new file mode 100644
index 000000000..c830eeaef
--- /dev/null
+++ b/Languages/ja/40_ERC1155_ja/IERC1155Receiver.sol
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "../34_ERC721/IERC165.sol";
+
+/**
+ * @dev ERC1155受信コントラクト、ERC1155の安全転送を受け入れるためにはこのコントラクトを実装する必要がある
+ */
+interface IERC1155Receiver is IERC165 {
+ /**
+ * @dev ERC1155安全転送`safeTransferFrom`を受け入れる
+ * 0xf23a6e61 または `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`を返す必要がある
+ */
+ function onERC1155Received(
+ address operator,
+ address from,
+ uint256 id,
+ uint256 value,
+ bytes calldata data
+ ) external returns (bytes4);
+
+ /**
+ * @dev ERC1155バッチ安全転送`safeBatchTransferFrom`を受け入れる
+ * 0xbc197c81 または `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`を返す必要がある
+ */
+ function onERC1155BatchReceived(
+ address operator,
+ address from,
+ uint256[] calldata ids,
+ uint256[] calldata values,
+ bytes calldata data
+ ) external returns (bytes4);
+}
\ No newline at end of file
diff --git a/Languages/ja/40_ERC1155_ja/img/40-1.jpg b/Languages/ja/40_ERC1155_ja/img/40-1.jpg
new file mode 100644
index 000000000..f140ed3d1
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-1.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/img/40-2.jpg b/Languages/ja/40_ERC1155_ja/img/40-2.jpg
new file mode 100644
index 000000000..12d59d37d
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-2.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/img/40-3.jpg b/Languages/ja/40_ERC1155_ja/img/40-3.jpg
new file mode 100644
index 000000000..1a9f614e5
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-3.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/img/40-4.jpg b/Languages/ja/40_ERC1155_ja/img/40-4.jpg
new file mode 100644
index 000000000..9411823ab
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-4.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/img/40-5.jpg b/Languages/ja/40_ERC1155_ja/img/40-5.jpg
new file mode 100644
index 000000000..5c86ec659
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-5.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/img/40-6.jpg b/Languages/ja/40_ERC1155_ja/img/40-6.jpg
new file mode 100644
index 000000000..a1a4e2c1b
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-6.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/img/40-7.jpg b/Languages/ja/40_ERC1155_ja/img/40-7.jpg
new file mode 100644
index 000000000..7d1d6dab2
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-7.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/img/40-8.jpg b/Languages/ja/40_ERC1155_ja/img/40-8.jpg
new file mode 100644
index 000000000..c34ffa749
Binary files /dev/null and b/Languages/ja/40_ERC1155_ja/img/40-8.jpg differ
diff --git a/Languages/ja/40_ERC1155_ja/readme.md b/Languages/ja/40_ERC1155_ja/readme.md
new file mode 100644
index 000000000..1931ae0e8
--- /dev/null
+++ b/Languages/ja/40_ERC1155_ja/readme.md
@@ -0,0 +1,651 @@
+---
+title: 40. ERC1155
+tags:
+ - solidity
+ - application
+ - wtfacademy
+ - ERC1155
+---
+
+# WTF Solidity 超シンプル入門: 40. ERC1155
+
+最近、Solidityを再学習し、詳細を確認しながら「WTF Solidity超シンプル入門」を作成しています。これは初心者向けのガイドです(プログラミング上級者は他のチュートリアルをご利用ください)。毎週1〜3レッスンを更新していきます。
+
+Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ: [Discord](https://discord.gg/5akcruXrsk) | [Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化しています: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、一つのコントラクトが複数のトークンをサポートする`ERC1155`標準について学習します。そして、改造された退屈な猿 - `BAYC1155`を発行します。これは`10,000`種類のトークンを含み、メタデータは`BAYC`と一致します。
+
+## `EIP1155`
+`ERC20`や`ERC721`標準のいずれにおいても、各コントラクトは独立したトークンに対応しています。イーサリアム上で『World of Warcraft』のような大規模なゲームを構築すると仮定すると、各装備に対してコントラクトをデプロイする必要があります。数千の装備があれば数千のコントラクトをデプロイして管理する必要があり、これは非常に面倒です。そのため、[イーサリアムEIP1155](https://eips.ethereum.org/EIPS/eip-1155)は、一つのコントラクトが複数の同質化トークンと非同質化トークンを含むことができるマルチトークン標準`ERC1155`を提案しました。`ERC1155`はGameFiアプリケーションで最も多く使用されており、DecentralandやSandboxなどの有名なブロックチェーンゲームでも使用されています。
+
+簡単に言うと、`ERC1155`は以前に紹介した非同質化トークン標準[ERC721](https://github.com/AmazingAng/WTF-Solidity/tree/main/34_ERC721)と似ています。`ERC721`では、各トークンに固有の識別子としての`tokenId`があり、各`tokenId`は一つのトークンにのみ対応します。一方、`ERC1155`では、各トークンタイプに固有の識別子としての`id`があり、各`id`は一種類のトークンに対応します。このように、トークンの種類を非同質的に同じコントラクト内で管理でき、各トークンタイプにはメタデータを格納するURL `uri`があり、これは`ERC721`の`tokenURI`に似ています。以下は`ERC1155`のメタデータインターフェースコントラクト`IERC1155MetadataURI`です:
+
+```solidity
+/**
+ * @dev ERC1155のオプションインターフェース、uri()関数でメタデータを照会
+ */
+interface IERC1155MetadataURI is IERC1155 {
+ /**
+ * @dev 第`id`種類トークンのURIを返す
+ */
+ function uri(uint256 id) external view returns (string memory);
+}
+```
+
+では、`ERC1155`の特定のトークンが同質化トークンか非同質化トークンかはどのように区別するのでしょうか?実は非常に簡単です。特定の`id`に対応するトークンの総供給量が`1`の場合、それは非同質化トークンで、`ERC721`と似ています。特定の`id`に対応するトークンの総供給量が`1`より大きい場合、それは同質化トークンです。これらのトークンは同じ`id`を共有するため、`ERC20`と似ています。
+
+## `IERC1155`インターフェースコントラクト
+
+`IERC1155`インターフェースコントラクトは`EIP1155`で実装する必要がある機能を抽象化しており、`4`つのイベントと`6`つの関数を含んでいます。`ERC721`とは異なり、`ERC1155`は複数種類のトークンを含むため、バッチ転送とバッチ残高照会を実装し、一度の操作で複数種類のトークンを処理できます。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/IERC165.sol";
+
+/**
+ * @dev ERC1155標準のインターフェースコントラクト、EIP1155の機能を実装
+ * 詳細: https://eips.ethereum.org/EIPS/eip-1155[EIP].
+ */
+interface IERC1155 is IERC165 {
+ /**
+ * @dev 単一種類トークン転送イベント
+ * `value`個の`id`種類のトークンが`operator`によって`from`から`to`に転送されたときに発行
+ */
+ event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
+
+ /**
+ * @dev バッチトークン転送イベント
+ * idsとvaluesは転送されるトークン種類と数量の配列
+ */
+ event TransferBatch(
+ address indexed operator,
+ address indexed from,
+ address indexed to,
+ uint256[] ids,
+ uint256[] values
+ );
+
+ /**
+ * @dev バッチ承認イベント
+ * `account`がすべてのトークンを`operator`に承認したときに発行
+ */
+ event ApprovalForAll(address indexed account, address indexed operator, bool approved);
+
+ /**
+ * @dev `id`種類のトークンのURIが変更されたときに発行、`value`は新しいURI
+ */
+ event URI(string value, uint256 indexed id);
+
+ /**
+ * @dev 残高照会、`account`が所有する`id`種類のトークンの残高を返す
+ */
+ function balanceOf(address account, uint256 id) external view returns (uint256);
+
+ /**
+ * @dev バッチ残高照会、`accounts`と`ids`配列の長さは等しくなければならない
+ */
+ function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
+ external
+ view
+ returns (uint256[] memory);
+
+ /**
+ * @dev バッチ承認、呼び出し元のトークンを`operator`アドレスに承認
+ * {ApprovalForAll}イベントを発行
+ */
+ function setApprovalForAll(address operator, bool approved) external;
+
+ /**
+ * @dev バッチ承認照会、承認アドレス`operator`が`account`によって承認されている場合`true`を返す
+ * {setApprovalForAll}関数を参照
+ */
+ function isApprovedForAll(address account, address operator) external view returns (bool);
+
+ /**
+ * @dev 安全転送、`amount`単位の`id`種類のトークンを`from`から`to`に転送
+ * {TransferSingle}イベントを発行
+ * 要件:
+ * - 呼び出し元が`from`アドレスでない場合、`from`の承認が必要
+ * - `from`アドレスは十分な残高を持つ必要がある
+ * - 受信者がコントラクトの場合、`IERC1155Receiver`の`onERC1155Received`メソッドを実装し、対応する値を返す必要がある
+ */
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes calldata data
+ ) external;
+
+ /**
+ * @dev バッチ安全転送
+ * {TransferBatch}イベントを発行
+ * 要件:
+ * - `ids`と`amounts`の長さが等しい
+ * - 受信者がコントラクトの場合、`IERC1155Receiver`の`onERC1155BatchReceived`メソッドを実装し、対応する値を返す必要がある
+ */
+ function safeBatchTransferFrom(
+ address from,
+ address to,
+ uint256[] calldata ids,
+ uint256[] calldata amounts,
+ bytes calldata data
+ ) external;
+}
+```
+
+### `IERC1155`イベント
+- `TransferSingle`イベント:単一種類トークン転送イベント、単一トークン種類転送時に発行されます。
+- `TransferBatch`イベント:バッチトークン転送イベント、複数トークン種類転送時に発行されます。
+- `ApprovalForAll`イベント:バッチ承認イベント、バッチ承認時に発行されます。
+- `URI`イベント:メタデータアドレス変更イベント、`uri`変更時に発行されます。
+
+### `IERC1155`関数
+- `balanceOf()`:単一トークン種類残高照会、`account`が所有する`id`種類のトークンの残高を返します。
+- `balanceOfBatch()`:複数トークン種類残高照会、照会するアドレス`accounts`配列とトークン種類`ids`配列の長さは等しくなければなりません。
+- `setApprovalForAll()`:バッチ承認、呼び出し元のトークンを`operator`アドレスに承認します。
+- `isApprovedForAll()`:バッチ承認情報照会、承認アドレス`operator`が`account`によって承認されている場合`true`を返します。
+- `safeTransferFrom()`:安全単一トークン転送、`amount`単位の`id`種類のトークンを`from`アドレスから`to`アドレスに転送します。`to`アドレスがコントラクトの場合、`onERC1155Received()`受信関数が実装されているかを検証します。
+- `safeBatchTransferFrom()`:安全マルチトークン転送、単一トークン転送と似ていますが、転送数量`amounts`とトークン種類`ids`が配列になり、長さが等しくなければなりません。`to`アドレスがコントラクトの場合、`onERC1155BatchReceived()`受信関数が実装されているかを検証します。
+
+## `ERC1155`受信コントラクト
+
+`ERC721`標準と同様に、トークンがブラックホールコントラクトに転送されることを避けるため、`ERC1155`ではトークン受信コントラクトが`IERC1155Receiver`を継承し、2つの受信関数を実装する必要があります:
+
+- `onERC1155Received()`:単一トークン転送受信関数、ERC1155安全転送`safeTransferFrom`を受け入れるために実装し、自身のセレクタ`0xf23a6e61`を返す必要があります。
+
+- `onERC1155BatchReceived()`:マルチトークン転送受信関数、ERC1155安全マルチトークン転送`safeBatchTransferFrom`を受け入れるために実装し、自身のセレクタ`0xbc197c81`を返す必要があります。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/IERC165.sol";
+
+/**
+ * @dev ERC1155受信コントラクト、ERC1155の安全転送を受け入れるためにはこのコントラクトを実装する必要がある
+ */
+interface IERC1155Receiver is IERC165 {
+ /**
+ * @dev ERC1155安全転送`safeTransferFrom`を受け入れる
+ * 0xf23a6e61 または `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`を返す必要がある
+ */
+ function onERC1155Received(
+ address operator,
+ address from,
+ uint256 id,
+ uint256 value,
+ bytes calldata data
+ ) external returns (bytes4);
+
+ /**
+ * @dev ERC1155バッチ安全転送`safeBatchTransferFrom`を受け入れる
+ * 0xbc197c81 または `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`を返す必要がある
+ */
+ function onERC1155BatchReceived(
+ address operator,
+ address from,
+ uint256[] calldata ids,
+ uint256[] calldata values,
+ bytes calldata data
+ ) external returns (bytes4);
+}
+```
+
+## `ERC1155`メインコントラクト
+
+`ERC1155`メインコントラクトは`IERC1155`インターフェースコントラクトで規定された関数を実装し、単一トークン/マルチトークンのミントとバーン関数も含みます。
+
+### `ERC1155`変数
+
+`ERC1155`メインコントラクトには`4`つの状態変数が含まれます:
+
+- `name`:トークン名
+- `symbol`:トークンシンボル
+- `_balances`:トークン残高マッピング、トークン種類`id`下での特定アドレス`account`の残高`balances`を記録します。
+- `_operatorApprovals`:バッチ承認マッピング、所有アドレスが別のアドレスに与えた承認状況を記録します。
+
+### `ERC1155`関数
+
+`ERC1155`メインコントラクトには`16`の関数が含まれます:
+
+- コンストラクタ:状態変数`name`と`symbol`を初期化します。
+- `supportsInterface()`:`ERC165`標準を実装し、サポートするインターフェースを宣言して他のコントラクトがチェックできるようにします。
+- `balanceOf()`:`IERC1155`の`balanceOf()`を実装し、残高を照会します。`ERC721`標準とは異なり、ここでは照会する残高アドレス`account`とトークン種類`id`を入力する必要があります。
+- `balanceOfBatch()`:`IERC1155`の`balanceOfBatch()`を実装し、バッチ残高照会を行います。
+- `setApprovalForAll()`:`IERC1155`の`setApprovalForAll()`を実装し、バッチ承認を行い、`ApprovalForAll`イベントを発行します。
+- `isApprovedForAll()`:`IERC1155`の`isApprovedForAll()`を実装し、バッチ承認情報を照会します。
+- `safeTransferFrom()`:`IERC1155`の`safeTransferFrom()`を実装し、単一トークン種類安全転送を行い、`TransferSingle`イベントを発行します。`ERC721`と異なり、ここでは送信者`from`、受信者`to`、トークン種類`id`だけでなく、転送数量`amount`も入力する必要があります。
+- `safeBatchTransferFrom()`:`IERC1155`の`safeBatchTransferFrom()`を実装し、マルチトークン種類安全転送を行い、`TransferBatch`イベントを発行します。
+- `_mint()`:単一トークン種類ミント関数。
+- `_mintBatch()`:マルチトークン種類ミント関数。
+- `_burn()`:単一トークン種類バーン関数。
+- `_burnBatch()`:マルチトークン種類バーン関数。
+- `_doSafeTransferAcceptanceCheck`:単一トークン種類転送の安全チェック、`safeTransferFrom()`によって呼び出され、受信者がコントラクトの場合、`onERC1155Received()`関数が実装されていることを確認します。
+- `_doSafeBatchTransferAcceptanceCheck`:マルチトークン種類転送の安全チェック、`safeBatchTransferFrom`によって呼び出され、受信者がコントラクトの場合、`onERC1155BatchReceived()`関数が実装されていることを確認します。
+- `uri()`:`ERC1155`の第`id`種類トークンのメタデータを格納するURLを返し、`ERC721`の`tokenURI`に似ています。
+- `baseURI()`:`baseURI`を返し、`uri`は`baseURI`と`id`を連結したもので、開発者が再実装する必要があります。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "./IERC1155.sol";
+import "./IERC1155Receiver.sol";
+import "./IERC1155MetadataURI.sol";
+import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/String.sol";
+import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/IERC165.sol";
+
+/**
+ * @dev ERC1155マルチトークン標準
+ * 詳細 https://eips.ethereum.org/EIPS/eip-1155
+ */
+contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI {
+
+ using Strings for uint256; // Stringライブラリを使用
+ // トークン名
+ string public name;
+ // トークンシンボル
+ string public symbol;
+ // トークン種類id から アカウントaccount から 残高balances へのマッピング
+ mapping(uint256 => mapping(address => uint256)) private _balances;
+ // address から 承認アドレス へのバッチ承認マッピング
+ mapping(address => mapping(address => bool)) private _operatorApprovals;
+
+ /**
+ * コンストラクタ、`name` と `symbol`を初期化
+ */
+ constructor(string memory name_, string memory symbol_) {
+ name = name_;
+ symbol = symbol_;
+ }
+
+ /**
+ * @dev {IERC165-supportsInterface}を参照
+ */
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
+ return
+ interfaceId == type(IERC1155).interfaceId ||
+ interfaceId == type(IERC1155MetadataURI).interfaceId ||
+ interfaceId == type(IERC165).interfaceId;
+ }
+
+ /**
+ * @dev 残高照会 IERC1155のbalanceOfを実装、accountアドレスのid種類トークン残高を返す
+ */
+ function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
+ require(account != address(0), "ERC1155: address zero is not a valid owner");
+ return _balances[id][account];
+ }
+
+ /**
+ * @dev バッチ残高照会
+ * 要件:
+ * - `accounts` と `ids` 配列の長さが等しい
+ */
+ function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
+ public view virtual override
+ returns (uint256[] memory)
+ {
+ require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
+ uint256[] memory batchBalances = new uint256[](accounts.length);
+ for (uint256 i = 0; i < accounts.length; ++i) {
+ batchBalances[i] = balanceOf(accounts[i], ids[i]);
+ }
+ return batchBalances;
+ }
+
+ /**
+ * @dev バッチ承認、呼び出し元がoperatorにすべてのトークンの使用を承認
+ * {ApprovalForAll}イベントを発行
+ * 条件:msg.sender != operator
+ */
+ function setApprovalForAll(address operator, bool approved) public virtual override {
+ require(msg.sender != operator, "ERC1155: setting approval status for self");
+ _operatorApprovals[msg.sender][operator] = approved;
+ emit ApprovalForAll(msg.sender, operator, approved);
+ }
+
+ /**
+ * @dev バッチ承認照会
+ */
+ function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
+ return _operatorApprovals[account][operator];
+ }
+
+ /**
+ * @dev 安全転送、`amount`単位の`id`種類トークンを`from`から`to`に転送
+ * {TransferSingle}イベントを発行
+ * 要件:
+ * - to はゼロアドレスではない
+ * - from は十分な残高を持ち、呼び出し元は承認を持つ
+ * - to がスマートコントラクトの場合、IERC1155Receiver-onERC1155Receivedをサポートする必要がある
+ */
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes memory data
+ ) public virtual override {
+ address operator = msg.sender;
+ // 呼び出し元は所有者または承認されている
+ require(
+ from == operator || isApprovedForAll(from, operator),
+ "ERC1155: caller is not token owner nor approved"
+ );
+ require(to != address(0), "ERC1155: transfer to the zero address");
+ // fromアドレスは十分な残高を持つ
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
+ // 残高を更新
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+ _balances[id][to] += amount;
+ // イベントを発行
+ emit TransferSingle(operator, from, to, id, amount);
+ // 安全チェック
+ _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
+ }
+
+ /**
+ * @dev バッチ安全転送、`amounts`配列単位の`ids`配列種類トークンを`from`から`to`に転送
+ * {TransferBatch}イベントを発行
+ * 要件:
+ * - to はゼロアドレスではない
+ * - from は十分な残高を持ち、呼び出し元は承認を持つ
+ * - to がスマートコントラクトの場合、IERC1155Receiver-onERC1155BatchReceivedをサポートする必要がある
+ * - ids と amounts 配列の長さが等しい
+ */
+ function safeBatchTransferFrom(
+ address from,
+ address to,
+ uint256[] memory ids,
+ uint256[] memory amounts,
+ bytes memory data
+ ) public virtual override {
+ address operator = msg.sender;
+ // 呼び出し元は所有者または承認されている
+ require(
+ from == operator || isApprovedForAll(from, operator),
+ "ERC1155: caller is not token owner nor approved"
+ );
+ require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
+ require(to != address(0), "ERC1155: transfer to the zero address");
+
+ // forループで残高を更新
+ for (uint256 i = 0; i < ids.length; ++i) {
+ uint256 id = ids[i];
+ uint256 amount = amounts[i];
+
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+ _balances[id][to] += amount;
+ }
+
+ emit TransferBatch(operator, from, to, ids, amounts);
+ // 安全チェック
+ _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
+ }
+
+ /**
+ * @dev ミント
+ * {TransferSingle}イベントを発行
+ */
+ function _mint(
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes memory data
+ ) internal virtual {
+ require(to != address(0), "ERC1155: mint to the zero address");
+
+ address operator = msg.sender;
+
+ _balances[id][to] += amount;
+ emit TransferSingle(operator, address(0), to, id, amount);
+
+ _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
+ }
+
+ /**
+ * @dev バッチミント
+ * {TransferBatch}イベントを発行
+ */
+ function _mintBatch(
+ address to,
+ uint256[] memory ids,
+ uint256[] memory amounts,
+ bytes memory data
+ ) internal virtual {
+ require(to != address(0), "ERC1155: mint to the zero address");
+ require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
+
+ address operator = msg.sender;
+
+ for (uint256 i = 0; i < ids.length; i++) {
+ _balances[ids[i]][to] += amounts[i];
+ }
+
+ emit TransferBatch(operator, address(0), to, ids, amounts);
+
+ _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
+ }
+
+ /**
+ * @dev バーン
+ */
+ function _burn(
+ address from,
+ uint256 id,
+ uint256 amount
+ ) internal virtual {
+ require(from != address(0), "ERC1155: burn from the zero address");
+
+ address operator = msg.sender;
+
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+
+ emit TransferSingle(operator, from, address(0), id, amount);
+ }
+
+ /**
+ * @dev バッチバーン
+ */
+ function _burnBatch(
+ address from,
+ uint256[] memory ids,
+ uint256[] memory amounts
+ ) internal virtual {
+ require(from != address(0), "ERC1155: burn from the zero address");
+ require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
+
+ address operator = msg.sender;
+
+ for (uint256 i = 0; i < ids.length; i++) {
+ uint256 id = ids[i];
+ uint256 amount = amounts[i];
+
+ uint256 fromBalance = _balances[id][from];
+ require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
+ unchecked {
+ _balances[id][from] = fromBalance - amount;
+ }
+ }
+
+ emit TransferBatch(operator, from, address(0), ids, amounts);
+ }
+
+ // エラー 無効な受信者
+ error ERC1155InvalidReceiver(address receiver);
+
+ // @dev ERC1155の安全転送チェック
+ function _doSafeTransferAcceptanceCheck(
+ address operator,
+ address from,
+ address to,
+ uint256 id,
+ uint256 amount,
+ bytes memory data
+ ) private {
+ if (to.code.length > 0) {
+ try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
+ if (response != IERC1155Receiver.onERC1155Received.selector) {
+ revert ERC1155InvalidReceiver(to);
+ }
+ } catch (bytes memory reason) {
+ if (reason.length == 0) {
+ revert ERC1155InvalidReceiver(to);
+ } else {
+ /// @solidity memory-safe-assembly
+ assembly {
+ revert(add(32, reason), mload(reason))
+ }
+ }
+ }
+ }
+ }
+
+ // @dev ERC1155のバッチ安全転送チェック
+ function _doSafeBatchTransferAcceptanceCheck(
+ address operator,
+ address from,
+ address to,
+ uint256[] memory ids,
+ uint256[] memory amounts,
+ bytes memory data
+ ) private {
+ if (to.code.length > 0) {
+ try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
+ bytes4 response
+ ) {
+ if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
+ revert ERC1155InvalidReceiver(to);
+ }
+ } catch (bytes memory reason) {
+ if (reason.length == 0) {
+ revert ERC1155InvalidReceiver(to);
+ } else {
+ /// @solidity memory-safe-assembly
+ assembly {
+ revert(add(32, reason), mload(reason))
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @dev ERC1155のid種類トークンのuriを返し、metadata を格納、ERC721のtokenURIに似ている
+ */
+ function uri(uint256 id) public view virtual override returns (string memory) {
+ string memory baseURI = _baseURI();
+ return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, id.toString())) : "";
+ }
+
+ /**
+ * {uri}のBaseURIを計算、uriはbaseURIとtokenIdを連結したもので、開発者が再実装する必要がある
+ */
+ function _baseURI() internal view virtual returns (string memory) {
+ return "";
+ }
+}
+```
+
+## `BAYC`だが`ERC1155`
+
+`ERC721`標準の退屈な猿`BAYC`を改造して、無料ミントの`BAYC1155`を作成しましょう。`_baseURI()`関数を修正して、`BAYC1155`の`uri`が`BAYC`の`tokenURI`と同じになるようにします。これにより、`BAYC1155`のメタデータは退屈な猿と同じになります:
+
+```solidity
+// SPDX-License-Identifier: MIT
+// by 0xAA
+pragma solidity ^0.8.21;
+
+import "./ERC1155.sol";
+
+contract BAYC1155 is ERC1155{
+ uint256 constant MAX_ID = 10000;
+ // コンストラクタ
+ constructor() ERC1155("BAYC1155", "BAYC1155"){
+ }
+
+ //BAYCのbaseURIは ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+ function _baseURI() internal pure override returns (string memory) {
+ return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
+ }
+
+ // ミント関数
+ function mint(address to, uint256 id, uint256 amount) external {
+ // id は 10,000 を超えることはできない
+ require(id < MAX_ID, "id overflow");
+ _mint(to, id, amount, "");
+ }
+
+ // バッチミント関数
+ function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) external {
+ // id は 10,000 を超えることはできない
+ for (uint256 i = 0; i < ids.length; i++) {
+ require(ids[i] < MAX_ID, "id overflow");
+ }
+ _mintBatch(to, ids, amounts, "");
+ }
+}
+```
+
+## Remixデモンストレーション
+
+### 1. `BAYC1155`コントラクトをデプロイ
+
+
+### 2. メタデータ`uri`を確認
+
+
+### 3. `mint`して残高変化を確認
+`mint`欄にアカウントアドレス、`id`、数量を入力し、`mint`ボタンをクリックしてミントします。数量が`1`の場合は非同質化トークン、数量が`1`より大きい場合は同質化トークンになります。
+
+
+
+`balanceOf`欄にアカウントアドレスと`id`を入力して対応する残高を確認
+
+
+
+### 4. バッチ`mint`して残高変化を確認
+`mintBatch`欄にミントする`ids`配列と対応する数量を入力、2つの配列の長さは等しくなければなりません
+
+
+
+ミントしたトークン`id`配列を入力すると確認できます
+
+
+
+### 5. バッチ転送して残高変化を確認
+
+ミントと同様ですが、今回は対応するトークンを持つアドレスから新しいアドレスに転送します。このアドレスは通常のアドレスでもコントラクトアドレスでも構いません。コントラクトアドレスの場合、`onERC1155Received()`受信関数が実装されているかを検証します。
+
+ここでは通常のアドレスに転送し、`ids`と`amounts`配列を入力します。
+
+
+
+転送先アドレスの残高変化を確認します。
+
+
+
+## まとめ
+
+今回はイーサリアム`EIP1155`で提案された`ERC1155`マルチトークン標準について学習しました。これは一つのコントラクトに複数の同質化または非同質化トークンを含むことを可能にします。そして、改造版退屈な猿 - `BAYC1155`を作成しました:`10,000`種類のトークンを含み、メタデータが`BAYC`と同じ`ERC1155`トークンです。現在、`ERC1155`は主に`GameFi`で応用されています。しかし、メタバース技術が発展し続けるにつれて、この標準はますます人気になると信じています。
\ No newline at end of file
diff --git a/Languages/ja/41_WETH_ja/WETH.sol b/Languages/ja/41_WETH_ja/WETH.sol
new file mode 100644
index 000000000..1e5e7566c
--- /dev/null
+++ b/Languages/ja/41_WETH_ja/WETH.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: MIT
+// author: 0xAA
+// original contract on ETH: https://rinkeby.etherscan.io/token/0xc778417e063141139fce010982780140aa0cd5ab?a=0xe16c1623c1aa7d919cd2241d8b36d9e79c1be2a2#code
+pragma solidity ^0.8.0;
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract WETH is ERC20{
+ // イベント:預金と引出
+ event Deposit(address indexed dst, uint wad);
+ event Withdrawal(address indexed src, uint wad);
+
+ // コンストラクタ、ERC20の名前とシンボルを初期化
+ constructor() ERC20("WETH", "WETH"){
+ }
+
+ // フォールバック関数、ユーザーがWETHコントラクトにETHを送金すると、deposit()関数がトリガーされる
+ fallback() external payable {
+ deposit();
+ }
+ // リシーブ関数、ユーザーがWETHコントラクトにETHを送金すると、deposit()関数がトリガーされる
+ receive() external payable {
+ deposit();
+ }
+
+ // 預金関数、ユーザーがETHを預けると、等量のWETHをミントする
+ function deposit() public payable {
+ _mint(msg.sender, msg.value);
+ emit Deposit(msg.sender, msg.value);
+ }
+
+ // 引出関数、ユーザーがWETHを破棄し、等量のETHを取り戻す
+ function withdraw(uint amount) public {
+ require(balanceOf(msg.sender) >= amount);
+ _burn(msg.sender, amount);
+ payable(msg.sender).transfer(amount);
+ emit Withdrawal(msg.sender, amount);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/41_WETH_ja/img/41-1.gif b/Languages/ja/41_WETH_ja/img/41-1.gif
new file mode 100644
index 000000000..8787b5d91
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-1.gif differ
diff --git a/Languages/ja/41_WETH_ja/img/41-2.jpg b/Languages/ja/41_WETH_ja/img/41-2.jpg
new file mode 100644
index 000000000..8954b2617
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-2.jpg differ
diff --git a/Languages/ja/41_WETH_ja/img/41-3.jpg b/Languages/ja/41_WETH_ja/img/41-3.jpg
new file mode 100644
index 000000000..84b3fe921
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-3.jpg differ
diff --git a/Languages/ja/41_WETH_ja/img/41-4.jpg b/Languages/ja/41_WETH_ja/img/41-4.jpg
new file mode 100644
index 000000000..4631bf765
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-4.jpg differ
diff --git a/Languages/ja/41_WETH_ja/img/41-5.jpg b/Languages/ja/41_WETH_ja/img/41-5.jpg
new file mode 100644
index 000000000..dc208d461
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-5.jpg differ
diff --git a/Languages/ja/41_WETH_ja/img/41-6.jpg b/Languages/ja/41_WETH_ja/img/41-6.jpg
new file mode 100644
index 000000000..ebdb54b6e
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-6.jpg differ
diff --git a/Languages/ja/41_WETH_ja/img/41-7.jpg b/Languages/ja/41_WETH_ja/img/41-7.jpg
new file mode 100644
index 000000000..844802ab5
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-7.jpg differ
diff --git a/Languages/ja/41_WETH_ja/img/41-8.jpg b/Languages/ja/41_WETH_ja/img/41-8.jpg
new file mode 100644
index 000000000..0d45e09aa
Binary files /dev/null and b/Languages/ja/41_WETH_ja/img/41-8.jpg differ
diff --git a/Languages/ja/41_WETH_ja/readme.md b/Languages/ja/41_WETH_ja/readme.md
new file mode 100644
index 000000000..5673335a1
--- /dev/null
+++ b/Languages/ja/41_WETH_ja/readme.md
@@ -0,0 +1,134 @@
+---
+title: 41. WETH
+tags:
+ - solidity
+ - application
+ - ERC20
+ - fallback
+---
+
+# WTF Solidity 超簡単な入門: 41. WETH
+
+最近、私はSolidityを学び直し、詳細を固めるとともに、初心者向けの「WTF Solidity 超簡単な入門」を書いています(プログラミングの上級者は他のチュートリアルを探してください)。毎週1-3レッスンを更新しています。
+
+Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ: [Discord](https://discord.gg/5akcruXrsk)|[WeChat群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはGitHubでオープンソース化されています: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回のレッスンでは、`WETH`(Wrapped ETH:ラップドETH)について学習します。
+
+## `WETH`とは何ですか?
+
+
+
+`WETH`(Wrapped ETH)は`ETH`のラップ版です。一般的に見かける`WETH`、`WBTC`、`WBNB`は、すべてラップされたネイティブトークンです。なぜこれらをラップする必要があるのでしょうか?
+
+2015年に[ERC20](https://github.com/AmazingAng/WTF-Solidity/blob/main/20_SendETH/readme.md)標準が登場しました。このトークン標準は、イーサリアム上のトークンに標準化されたルールセットを確立することを目的としており、新しいトークンの発行を簡素化し、ブロックチェーン上のすべてのトークンを相互に比較可能にしました。残念ながら、イーサ(ETH)自体は`ERC20`標準に準拠していません。`WETH`の開発は、ブロックチェーン間の相互運用性を向上させ、`ETH`を分散型アプリケーション(dApps)で使用できるようにするために行われました。これは、ネイティブトークンにスマートコントラクトで作られた服を着せるようなものです:服を着ているときは`WETH`となり、`ERC20`同質化トークン標準に準拠し、クロスチェーンやdAppで使用できます;服を脱ぐと、1:1で`ETH`に交換できます。
+
+## `WETH`コントラクト
+
+現在使用されている[メインネットWETHコントラクト](https://rinkeby.etherscan.io/token/0xc778417e063141139fce010982780140aa0cd5ab?a=0xe16c1623c1aa7d919cd2241d8b36d9e79c1be2a2)は2015年に書かれ、非常に古く、その時のSolidityは0.4版でした。私たちは0.8版で`WETH`を書き直します。
+
+`WETH`は`ERC20`標準に準拠しており、通常の`ERC20`よりも2つの追加機能があります:
+
+1. 預金(deposit):ラッピング。ユーザーが`ETH`を`WETH`コントラクトに預け、等量の`WETH`を取得します。
+
+2. 引出(withdraw):アンラッピング。ユーザーが`WETH`を破棄し、等量の`ETH`を取得します。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract WETH is ERC20{
+ // イベント:預金と引出
+ event Deposit(address indexed dst, uint wad);
+ event Withdrawal(address indexed src, uint wad);
+
+ // コンストラクタ、ERC20の名前とシンボルを初期化
+ constructor() ERC20("WETH", "WETH"){
+ }
+
+ // フォールバック関数、ユーザーがWETHコントラクトにETHを送金すると、deposit()関数がトリガーされる
+ fallback() external payable {
+ deposit();
+ }
+ // リシーブ関数、ユーザーがWETHコントラクトにETHを送金すると、deposit()関数がトリガーされる
+ receive() external payable {
+ deposit();
+ }
+
+ // 預金関数、ユーザーがETHを預けると、等量のWETHをミントする
+ function deposit() public payable {
+ _mint(msg.sender, msg.value);
+ emit Deposit(msg.sender, msg.value);
+ }
+
+ // 引出関数、ユーザーがWETHを破棄し、等量のETHを取り戻す
+ function withdraw(uint amount) public {
+ require(balanceOf(msg.sender) >= amount);
+ _burn(msg.sender, amount);
+ payable(msg.sender).transfer(amount);
+ emit Withdrawal(msg.sender, amount);
+ }
+}
+```
+
+### 継承
+
+`WETH`は`ERC20`トークン標準に準拠しているため、`WETH`コントラクトは`ERC20`コントラクトを継承しています。
+
+### イベント
+
+`WETH`コントラクトには`2`つのイベントがあります:
+
+1. `Deposit`:預金イベント、預金時に発行されます。
+2. `Withdrawal`:引出イベント、引出時に発行されます。
+
+### 関数
+
+`ERC20`標準の関数に加えて、`WETH`コントラクトには`5`つの関数があります:
+
+- コンストラクタ:`WETH`の名前とシンボルを初期化します。
+- フォールバック関数:`fallback()`と`receive()`。ユーザーが`WETH`コントラクトに`ETH`を送金すると、自動的に`deposit()`預金関数がトリガーされ、等量の`WETH`を取得します。
+- `deposit()`:預金関数。ユーザーが`ETH`を預けると、等量の`WETH`をミントします。
+- `withdraw()`:引出関数。ユーザーが`WETH`を破棄し、等量の`ETH`を返却します。
+
+## `Remix`デモンストレーション
+
+### 1. `WETH`コントラクトをデプロイ
+
+
+
+### 2. `deposit`を呼び出し、`1 ETH`を預けて、`WETH`残高を確認
+
+
+
+この時点で`WETH`残高は`1 WETH`です。
+
+
+
+### 3. `WETH`コントラクトに直接`1 ETH`を送金し、`WETH`残高を確認
+
+
+
+この時点で`WETH`残高は`2 WETH`です。
+
+
+
+### 4. `withdraw`を呼び出し、`1.5 ETH`を引き出して、`WETH`残高を確認
+
+
+
+この時点で`WETH`残高は`0.5 WETH`です。
+
+
+
+## まとめ
+
+今回のレッスンでは、`WETH`を紹介し、`WETH`コントラクトを実装しました。これは、ネイティブ`ETH`にスマートコントラクトで作られた服を着せるようなものです:服を着ているときは`WETH`となり、`ERC20`同質化トークン標準に準拠し、クロスチェーンや`dApp`で使用できます;服を脱ぐと、1:1で`ETH`に交換できます。
\ No newline at end of file
diff --git a/Languages/ja/42_PaymentSplit_ja/readme.md b/Languages/ja/42_PaymentSplit_ja/readme.md
new file mode 100644
index 000000000..4aa240a1a
--- /dev/null
+++ b/Languages/ja/42_PaymentSplit_ja/readme.md
@@ -0,0 +1,201 @@
+---
+title: 42. 分账
+tags:
+ - solidity
+ - application
+
+---
+
+# WTF Solidity 超シンプル入門: 42. 分账
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、分账合約について説明します。このコントラクトは、`ETH`を重み付けに従って一群のアカウントに分配することができます。コードはOpenZeppelinライブラリの[PaymentSplitterコントラクト](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/finance/PaymentSplitter.sol)を簡略化したものです。
+
+## 分账
+
+分账とは、一定の比率に従って資金を分配することです。現実世界では、よく「取り分が不平等」という問題が発生しますが、ブロックチェーンの世界では、`Code is Law`なので、事前に各人の取り分の比率をスマートコントラクトに記述しておき、収入を得た後にスマートコントラクトが分账を行うことができます。
+
+
+
+## 分账コントラクト
+
+分账コントラクト(`PaymentSplit`)には以下の特徴があります:
+
+1. コントラクト作成時に分账受益者`payees`と各人の持分`shares`を定めます。
+2. 持分は等しくても、その他の任意の比率でも構いません。
+3. このコントラクトが受け取った全ての`ETH`のうち、各受益者は自分に割り当てられた持分に比例した金額を引き出すことができます。
+4. 分账コントラクトは`Pull Payment`モデルに従い、支払いは自動的にアカウントに転送されず、このコントラクトに保存されます。受益者は`release()`関数を呼び出して実際の転送をトリガーします。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.21;
+
+/**
+ * 分账コントラクト
+ * @dev このコントラクトは受け取ったETHを事前に定めた持分に従って複数のアカウントに分配します。受け取ったETHは分账コントラクトに保存され、各受益者がrelease()関数を呼び出して受け取る必要があります。
+ */
+contract PaymentSplit{
+```
+
+### イベント
+
+分账コントラクトには合計`3`つのイベントがあります:
+
+- `PayeeAdded`:受益者追加イベント。
+- `PaymentReleased`:受益者出金イベント。
+- `PaymentReceived`:分账コントラクト入金イベント。
+
+```solidity
+ // イベント
+ event PayeeAdded(address account, uint256 shares); // 受益者追加イベント
+ event PaymentReleased(address to, uint256 amount); // 受益者出金イベント
+ event PaymentReceived(address from, uint256 amount); // コントラクト入金イベント
+```
+
+### 状態変数
+
+分账コントラクトには合計`5`つの状態変数があり、受益者アドレス、持分、支払い済み`ETH`などの変数を記録するために使用されます:
+
+- `totalShares`:総持分、`shares`の合計。
+- `totalReleased`:分账コントラクトから受益者に支払われた`ETH`、`released`の合計。
+- `payees`:`address`配列、受益者アドレスを記録
+- `shares`:`address`から`uint256`へのマッピング、各受益者の持分を記録。
+- `released`:`address`から`uint256`へのマッピング、分账コントラクトが各受益者に支払った金額を記録。
+
+```solidity
+ uint256 public totalShares; // 総持分
+ uint256 public totalReleased; // 総支払額
+
+ mapping(address => uint256) public shares; // 各受益者の持分
+ mapping(address => uint256) public released; // 各受益者への支払額
+ address[] public payees; // 受益者配列
+```
+
+### 関数
+
+分账コントラクトには合計`6`つの関数があります:
+
+- コンストラクタ:受益者配列`_payees`と分账持分配列`_shares`を初期化します。配列の長さは0であってはならず、2つの配列の長さは等しくなければなりません。`_shares`の要素は0より大きく、`_payees`のアドレスは0アドレスであってはならず、重複もあってはなりません。
+- `receive()`:コールバック関数、分账コントラクトが`ETH`を受け取った時に`PaymentReceived`イベントを発行します。
+- `release()`:分账関数、有効な受益者アドレス`_account`に対応する`ETH`を分配します。誰でもこの関数をトリガーできますが、`ETH`は受益者アドレス`account`に転送されます。`releasable()`関数を呼び出します。
+- `releasable()`:受益者アドレスが受け取るべき`ETH`を計算します。`pendingPayment()`関数を呼び出します。
+- `pendingPayment()`:受益者アドレス`_account`、分账コントラクトの総収入`_totalReceived`、そのアドレスが既に受け取った金額`_alreadyReleased`に基づいて、その受益者が現在分配されるべき`ETH`を計算します。
+- `_addPayee()`:受益者とその持分を追加する関数。コントラクトの初期化時に呼び出され、後から変更することはできません。
+
+```solidity
+ /**
+ * @dev 受益者配列_payeesと分账持分配列_sharesを初期化
+ * 配列の長さは等しく、0であってはなりません。_sharesの要素は0より大きく、_payeesのアドレスは0アドレスであってはならず、重複もあってはなりません
+ */
+ constructor(address[] memory _payees, uint256[] memory _shares) payable {
+ // _payeesと_shares配列の長さが同じで、0でないことをチェック
+ require(_payees.length == _shares.length, "PaymentSplitter: payees and shares length mismatch");
+ require(_payees.length > 0, "PaymentSplitter: no payees");
+ // _addPayeeを呼び出し、受益者アドレスpayees、受益者持分shares、総持分totalSharesを更新
+ for (uint256 i = 0; i < _payees.length; i++) {
+ _addPayee(_payees[i], _shares[i]);
+ }
+ }
+
+ /**
+ * @dev コールバック関数、ETH受信時にPaymentReceivedイベントを発行
+ */
+ receive() external payable virtual {
+ emit PaymentReceived(msg.sender, msg.value);
+ }
+
+ /**
+ * @dev 有効な受益者アドレス_accountに分账し、対応するETHを受益者アドレスに直接送信。誰でもこの関数をトリガーできますが、資金はaccountアドレスに送られます。
+ * releasable()関数を呼び出します。
+ */
+ function release(address payable _account) public virtual {
+ // accountは有効な受益者でなければなりません
+ require(shares[_account] > 0, "PaymentSplitter: account has no shares");
+ // accountが受け取るべきethを計算
+ uint256 payment = releasable(_account);
+ // 受け取るべきethは0であってはなりません
+ require(payment != 0, "PaymentSplitter: account is not due payment");
+ // 総支払額totalReleasedと各受益者への支払額releasedを更新
+ totalReleased += payment;
+ released[_account] += payment;
+ // 送金
+ _account.transfer(payment);
+ emit PaymentReleased(_account, payment);
+ }
+
+ /**
+ * @dev アカウントが受け取ることができるethを計算。
+ * pendingPayment()関数を呼び出します。
+ */
+ function releasable(address _account) public view returns (uint256) {
+ // 分账コントラクトの総収入totalReceivedを計算
+ uint256 totalReceived = address(this).balance + totalReleased;
+ // _pendingPaymentを呼び出してaccountが受け取るべきETHを計算
+ return pendingPayment(_account, totalReceived, released[_account]);
+ }
+
+ /**
+ * @dev 受益者アドレス`_account`、分账コントラクトの総収入`_totalReceived`、そのアドレスが既に受け取った金額`_alreadyReleased`に基づいて、その受益者が現在分配されるべき`ETH`を計算。
+ */
+ function pendingPayment(
+ address _account,
+ uint256 _totalReceived,
+ uint256 _alreadyReleased
+ ) public view returns (uint256) {
+ // accountが受け取るべきETH = 総受取予定ETH - 既に受け取ったETH
+ return (_totalReceived * shares[_account]) / totalShares - _alreadyReleased;
+ }
+
+ /**
+ * @dev 受益者_accountと対応する持分_accountSharesを追加。コンストラクタでのみ呼び出され、変更できません。
+ */
+ function _addPayee(address _account, uint256 _accountShares) private {
+ // _accountが0アドレスでないことをチェック
+ require(_account != address(0), "PaymentSplitter: account is the zero address");
+ // _accountSharesが0でないことをチェック
+ require(_accountShares > 0, "PaymentSplitter: shares are 0");
+ // _accountが重複していないことをチェック
+ require(shares[_account] == 0, "PaymentSplitter: account already has shares");
+ // payees、shares、totalSharesを更新
+ payees.push(_account);
+ shares[_account] = _accountShares;
+ totalShares += _accountShares;
+ // 受益者追加イベントを発行
+ emit PayeeAdded(_account, _accountShares);
+ }
+```
+
+## `Remix`デモ
+
+### 1. `PaymentSplit`分账コントラクトをデプロイし、`1 ETH`を転送
+
+コンストラクタで、2つの受益者アドレスを入力し、持分を`1`と`3`に設定します。
+
+
+
+### 2. 受益者アドレス、持分、分配されるべき`ETH`を確認
+
+
+
+
+
+### 3. 関数を使って`ETH`を受け取る
+
+
+
+### 4. 総支出、受益者残高、分配されるべき`ETH`の変化を確認
+
+
+
+## まとめ
+
+今回は分账コントラクトについて紹介しました。ブロックチェーンの世界では、`Code is Law`なので、事前に各人の取り分の比率をスマートコントラクトに記述しておき、収入を得た後にスマートコントラクトが分账を行うことで、事後の「取り分不平等」を避けることができます。
\ No newline at end of file
diff --git a/Languages/ja/43_TokenVesting_ja/readme.md b/Languages/ja/43_TokenVesting_ja/readme.md
new file mode 100644
index 000000000..a316cfb24
--- /dev/null
+++ b/Languages/ja/43_TokenVesting_ja/readme.md
@@ -0,0 +1,144 @@
+---
+title: 43. 線形釈放
+tags:
+ - solidity
+ - application
+ - ERC20
+
+---
+
+# WTF Solidity 超シンプル入門: 43. 線形釈放
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、トークン帰属条項について紹介し、`ERC20`トークンの線形釈放コントラクトを作成します。コードは`OpenZeppelin`の`VestingWallet`コントラクトを簡略化したものです。
+
+## トークン帰属条項
+
+
+
+従来の金融分野では、一部の企業が従業員や経営陣に株式を提供しています。しかし、大量の株式を同時に放出すると、短期的な売却圧力が生じ、株価を押し下げる可能性があります。そのため、通常企業は帰属期間を導入して、約束された資産の所有権を遅延させます。同様に、ブロックチェーン分野では、`Web3`スタートアップがチームにトークンを配分し、同時にトークンを低価格でベンチャーキャピタルやプライベートエクイティに売却します。もし彼らがこれらの低コストトークンを同時に取引所で現金化すれば、価格は暴落し、個人投資家が直接的な損失を被ることになります。
+
+そのため、プロジェクト方は通常トークン帰属条項(token vesting)を約定し、帰属期間内にトークンを段階的に釈放することで、売却圧力を緩和し、チームと資本方の早期撤退を防ぎます。
+
+## 線形釈放
+
+線形釈放とは、トークンが帰属期間内に均一な速度で釈放されることを指します。例えば、あるプライベートエクイティが365,000枚の`ICU`トークンを保有し、帰属期間が1年(365日)の場合、毎日1,000枚のトークンが釈放されます。
+
+以下では、`ERC20`トークンをロックして線形釈放するコントラクト`TokenVesting`を作成します。そのロジックは非常にシンプルです:
+
+- プロジェクト方が線形釈放の開始時間、帰属期間、受益者を規定します。
+- プロジェクト方がロックされた`ERC20`トークンを`TokenVesting`コントラクトに転送します。
+- 受益者は`release`関数を呼び出して、コントラクトから釈放されたトークンを取り出すことができます。
+
+### イベント
+線形釈放コントラクトには合計`1`つのイベントがあります。
+- `ERC20Released`:出金イベント、受益者が釈放されたトークンを引き出した際に発行されます。
+
+```solidity
+contract TokenVesting {
+ // イベント
+ event ERC20Released(address indexed token, uint256 amount); // 出金イベント
+```
+
+### 状態変数
+線形釈放コントラクトには合計`4`つの状態変数があります。
+- `beneficiary`:受益者アドレス。
+- `start`:帰属期間開始タイムスタンプ。
+- `duration`:帰属期間、単位は秒。
+- `erc20Released`:トークンアドレス->釈放量のマッピング、受益者が既に受け取ったトークン数量を記録。
+
+```solidity
+ // 状態変数
+ mapping(address => uint256) public erc20Released; // トークンアドレス->釈放量のマッピング、既に釈放されたトークンを記録
+ address public immutable beneficiary; // 受益者アドレス
+ uint256 public immutable start; // 開始タイムスタンプ
+ uint256 public immutable duration; // 帰属期間
+```
+
+### 関数
+線形釈放コントラクトには合計`3`つの関数があります。
+
+- コンストラクタ:受益者アドレス、帰属期間(秒)、開始タイムスタンプを初期化します。パラメータは受益者アドレス`beneficiaryAddress`と帰属期間`durationSeconds`です。便宜上、開始タイムスタンプはデプロイ時のブロックチェーンタイムスタンプ`block.timestamp`を使用します。
+- `release()`:トークン引き出し関数、既に釈放されたトークンを受益者に転送します。`vestedAmount()`関数を呼び出して引き出し可能なトークン数量を計算し、`ERC20Released`イベントを発行してから、トークンを受益者に`transfer`します。パラメータはトークンアドレス`token`です。
+- `vestedAmount()`:線形釈放公式に基づいて、既に釈放されたトークン数量をクエリします。開発者はこの関数を修正することで、釈放方法をカスタマイズできます。パラメータはトークンアドレス`token`とクエリのタイムスタンプ`timestamp`です。
+
+```solidity
+ /**
+ * @dev 受益者アドレス、釈放期間(秒)、開始タイムスタンプ(現在のブロックチェーンタイムスタンプ)を初期化
+ */
+ constructor(
+ address beneficiaryAddress,
+ uint256 durationSeconds
+ ) {
+ require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address");
+ beneficiary = beneficiaryAddress;
+ start = block.timestamp;
+ duration = durationSeconds;
+ }
+
+ /**
+ * @dev 受益者が既に釈放されたトークンを引き出し。
+ * vestedAmount()関数を呼び出して引き出し可能なトークン数量を計算し、その後受益者にtransferします。
+ * {ERC20Released}イベントを発行。
+ */
+ function release(address token) public {
+ // vestedAmount()関数を呼び出して引き出し可能なトークン数量を計算
+ uint256 releasable = vestedAmount(token, uint256(block.timestamp)) - erc20Released[token];
+ // 既に釈放されたトークン数量を更新
+ erc20Released[token] += releasable;
+ // トークンを受益者に転送
+ emit ERC20Released(token, releasable);
+ IERC20(token).transfer(beneficiary, releasable);
+ }
+
+ /**
+ * @dev 線形釈放公式に基づいて、既に釈放された数量を計算。開発者はこの関数を修正することで、釈放方法をカスタマイズできます。
+ * @param token: トークンアドレス
+ * @param timestamp: クエリのタイムスタンプ
+ */
+ function vestedAmount(address token, uint256 timestamp) public view returns (uint256) {
+ // コントラクトが合計で受け取ったトークン数量(現在の残高 + 既に引き出した分)
+ uint256 totalAllocation = IERC20(token).balanceOf(address(this)) + erc20Released[token];
+ // 線形釈放公式に基づいて、既に釈放された数量を計算
+ if (timestamp < start) {
+ return 0;
+ } else if (timestamp > start + duration) {
+ return totalAllocation;
+ } else {
+ return (totalAllocation * (timestamp - start)) / duration;
+ }
+ }
+```
+
+## `Remix`デモ
+
+### 1. [第31講](../31_ERC20/readme.md)の`ERC20`コントラクトをデプロイし、自分に`10000`枚のトークンを鋳造。
+
+
+
+
+
+### 2. `TokenVesting`線形釈放コントラクトをデプロイし、受益者を自分に設定し、帰属期間を`100`秒に設定。
+
+
+
+### 3. `10000`枚の`ERC20`トークンを線形釈放コントラクトに転送。
+
+
+
+### 4. `release()`関数を呼び出してトークンを引き出し。
+
+
+
+## まとめ
+
+トークンの短期間大量解錠は価格に巨大な圧力を与えますが、トークン帰属条項を約定することで売却圧力を緩和し、チームと資本方の早期撤退を防ぐことができます。今回は、トークン帰属条項について紹介し、`ERC20`トークンの線形釈放コントラクトを作成しました。
\ No newline at end of file
diff --git a/Languages/ja/44_TokenLocker_ja/readme.md b/Languages/ja/44_TokenLocker_ja/readme.md
new file mode 100644
index 000000000..8e2be5648
--- /dev/null
+++ b/Languages/ja/44_TokenLocker_ja/readme.md
@@ -0,0 +1,148 @@
+---
+title: 44. トークンロック
+tags:
+ - solidity
+ - application
+ - ERC20
+
+---
+
+# WTF Solidity 超シンプル入門: 44. トークンロック
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、流動性提供者`LP`トークンとは何か、なぜ流動性をロックする必要があるのかについて紹介し、シンプルな`ERC20`トークンロックコントラクトを作成します。
+
+## トークンロック
+
+
+
+トークンロック(Token Locker)は、シンプルなタイムロックコントラクトで、コントラクト内のトークンを一定期間ロックし、受益者はロック期間満了後にトークンを取り出すことができます。トークンロックは一般的に流動性提供者`LP`トークンをロックするために使用されます。
+
+### `LP`トークンとは?
+
+ブロックチェーンでは、ユーザーは分散型取引所`DEX`でトークンを取引します(例:`Uniswap`取引所)。`DEX`は中央集権取引所(`CEX`)とは異なり、分散型取引所は自動マーケットメーカー(`AMM`)メカニズムを使用し、ユーザーやプロジェクト方がプールを提供する必要があり、これにより他のユーザーが即座に売買できるようになります。簡単に言うと、ユーザー/プロジェクト方は対応するペア(例:`ETH/DAI`)をプールに質入れし、補償として`DEX`は対応する流動性提供者`LP`トークン証明書を鋳造し、彼らが対応する持分を質入れしたことを証明し、手数料を徴収できるようにします。
+
+### なぜ流動性をロックする必要があるのか?
+
+プロジェクト方が何の前触れもなく流動性プール内の`LP`トークンを引き出すと、投資者の手にあるトークンは現金化できなくなり、直接ゼロになってしまいます。この行為は`rug-pull`とも呼ばれ、2021年だけでも、様々な`rug-pull`詐欺が投資者から28億ドル以上の暗号通貨を騙し取りました。
+
+しかし、`LP`トークンがトークンロックコントラクト内にロックされている場合、ロック期間が終了する前にプロジェクト方は流動性プールを引き出すことができず、`rug pull`もできません。そのため、トークンロックはプロジェクト方の早期逃走を防ぐことができます(ロック期間満了時の逃走には注意が必要)。
+
+## トークンロックコントラクト
+
+以下では、`ERC20`トークンをロックするコントラクト`TokenLocker`を作成します。そのロジックは非常にシンプルです:
+
+- 開発者がコントラクトをデプロイする際に、ロック時間、受益者アドレス、およびトークンコントラクトを規定します。
+- 開発者が`TokenLocker`コントラクトにトークンを転送します。
+- ロック期間満了時に、受益者はコントラクト内のトークンを取り出すことができます。
+
+### イベント
+
+`TokenLocker`コントラクトには合計`2`つのイベントがあります。
+
+- `TokenLockStart`:ロック開始イベント、コントラクトデプロイ時に発行され、受益者アドレス、トークンアドレス、ロック開始時間、終了時間を記録。
+- `Release`:トークン釈放イベント、受益者がトークンを取り出した際に発行され、受益者アドレス、トークンアドレス、釈放トークン時間、トークン数量を記録。
+
+```solidity
+ // イベント
+ event TokenLockStart(address indexed beneficiary, address indexed token, uint256 startTime, uint256 lockTime);
+ event Release(address indexed beneficiary, address indexed token, uint256 releaseTime, uint256 amount);
+```
+
+### 状態変数
+
+`TokenLocker`コントラクトには合計`4`つの状態変数があります。
+
+- `token`:ロック対象トークンアドレス。
+- `beneficiary`:受益者アドレス。
+- `locktime`:ロック時間(秒)。
+- `startTime`:ロック開始タイムスタンプ(秒)。
+
+```solidity
+ // ロックされるERC20トークンコントラクト
+ IERC20 public immutable token;
+ // 受益者アドレス
+ address public immutable beneficiary;
+ // ロック時間(秒)
+ uint256 public immutable lockTime;
+ // ロック開始タイムスタンプ(秒)
+ uint256 public immutable startTime;
+```
+
+### 関数
+
+`TokenLocker`コントラクトには合計`2`つの関数があります。
+
+- コンストラクタ:トークンコントラクト、受益者アドレス、およびロック時間を初期化します。
+- `release()`:ロック期間満了後、トークンを受益者に釈放します。受益者が自発的に`release()`関数を呼び出してトークンを取り出す必要があります。
+
+```solidity
+ /**
+ * @dev タイムロックコントラクトをデプロイし、トークンコントラクトアドレス、受益者アドレス、ロック時間を初期化。
+ * @param token_: ロックされるERC20トークンコントラクト
+ * @param beneficiary_: 受益者アドレス
+ * @param lockTime_: ロック時間(秒)
+ */
+ constructor(
+ IERC20 token_,
+ address beneficiary_,
+ uint256 lockTime_
+ ) {
+ require(lockTime_ > 0, "TokenLock: lock time should greater than 0");
+ token = token_;
+ beneficiary = beneficiary_;
+ lockTime = lockTime_;
+ startTime = block.timestamp;
+
+ emit TokenLockStart(beneficiary_, address(token_), block.timestamp, lockTime_);
+ }
+
+ /**
+ * @dev ロック時間経過後、トークンを受益者に釈放。
+ */
+ function release() public {
+ require(block.timestamp >= startTime+lockTime, "TokenLock: current time is before release time");
+
+ uint256 amount = token.balanceOf(address(this));
+ require(amount > 0, "TokenLock: no tokens to release");
+
+ token.transfer(beneficiary, amount);
+
+ emit Release(msg.sender, address(token), block.timestamp, amount);
+ }
+```
+
+## `Remix`デモ
+
+### 1. [第31講](../31_ERC20/readme.md)の`ERC20`コントラクトをデプロイし、自分に`10000`枚のトークンを鋳造。
+
+
+
+### 2. `TokenLocker`コントラクトをデプロイし、トークンアドレスを`ERC20`コントラクトアドレスに、受益者を自分に、ロック期間を`180`秒に設定。
+
+
+
+### 3. `10000`枚のトークンをコントラクトに転送。
+
+
+
+### 4. ロック期間`180`秒内に`release()`関数を呼び出しても、トークンを取り出すことはできません。
+
+
+
+### 5. ロック期間後に`release()`関数を呼び出し、トークンの取り出しに成功。
+
+
+
+## まとめ
+
+今回は、トークンロックコントラクトについて紹介しました。プロジェクト方は一般的に`DEX`で流動性を提供し、投資者の取引を支援します。プロジェクト方が突然`LP`を引き出すと`rug-pull`が発生しますが、`LP`をトークンロックコントラクト内にロックすることで、この状況を回避できます。
\ No newline at end of file
diff --git a/Languages/ja/45_Timelock_ja/readme.md b/Languages/ja/45_Timelock_ja/readme.md
new file mode 100644
index 000000000..fa97631f8
--- /dev/null
+++ b/Languages/ja/45_Timelock_ja/readme.md
@@ -0,0 +1,270 @@
+---
+title: 45. タイムロック
+tags:
+ - solidity
+ - application
+
+---
+
+# WTF Solidity 超シンプル入門: 45. タイムロック
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、タイムロックとタイムロックコントラクトについて紹介します。コードはCompoundの[Timelockコントラクト](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol)を簡略化したものです。
+
+## タイムロック
+
+
+
+タイムロック(Timelock)は、銀行の金庫やその他の高セキュリティコンテナでよく見られるロック機構です。これはタイマーの一種で、たとえ開錠者が正しいパスワードを知っていても、設定された時間が経過する前に金庫やボルトが開かれることを防ぐように設計されています。
+
+ブロックチェーンでは、タイムロックは`DeFi`と`DAO`で広く採用されています。これは一段のコードで、スマートコントラクトの特定の機能を一定期間ロックできます。スマートコントラクトのセキュリティを大幅に向上させることができます。例えば、ハッカーが`Uniswap`のマルチシグをハックして金庫の資金を引き出そうとした場合、金庫コントラクトに2日間のロック期間のタイムロックが設定されていると、ハッカーが引き出しトランザクションを作成してから実際に資金を引き出すまでに2日間の待機期間が必要になります。この間、プロジェクト方は対応策を見つけることができ、投資者は事前にトークンを売却して損失を減らすことができます。
+
+## タイムロックコントラクト
+
+以下では、タイムロック`Timelock`コントラクトについて紹介します。そのロジックは複雑ではありません:
+
+- `Timelock`コントラクトを作成する際、プロジェクト方はロック期間を設定し、コントラクトの管理者を自分に設定できます。
+
+- タイムロックには主に3つの機能があります:
+ - トランザクションを作成し、タイムロックキューに追加する。
+ - トランザクションのロック期間満了後、トランザクションを実行する。
+ - 後悔した場合、タイムロックキュー内の特定のトランザクションをキャンセルする。
+
+- プロジェクト方は一般的にタイムロックコントラクトを重要なコントラクトの管理者に設定し(例:金庫コントラクト)、その後タイムロックを通してそれらを操作します。
+- タイムロックコントラクトの管理者は一般的にプロジェクトのマルチシグウォレットであり、分散化を保証します。
+
+### イベント
+`Timelock`コントラクトには合計`4`つのイベントがあります。
+- `QueueTransaction`:トランザクション作成およびタイムロックキュー参加イベント。
+- `ExecuteTransaction`:ロック期間満了後のトランザクション実行イベント。
+- `CancelTransaction`:トランザクションキャンセルイベント。
+- `NewAdmin`:管理者アドレス変更イベント。
+
+```solidity
+ // イベント
+ // トランザクションキャンセルイベント
+ event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime);
+ // トランザクション実行イベント
+ event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime);
+ // トランザクション作成およびキュー参加イベント
+ event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime);
+ // 管理者アドレス変更イベント
+ event NewAdmin(address indexed newAdmin);
+```
+
+### 状態変数
+`Timelock`コントラクトには合計`4`つの状態変数があります。
+
+- `admin`:管理者アドレス。
+- `delay`:ロック期間。
+- `GRACE_PERIOD`:トランザクション有効期限。トランザクションが実行時点に達しても`GRACE_PERIOD`内に実行されなかった場合、期限切れになります。
+- `queuedTransactions`:タイムロックキューに参加したトランザクションの識別子`txHash`から`bool`へのマッピング、タイムロックキュー内のすべてのトランザクションを記録。
+
+```solidity
+ // 状態変数
+ address public admin; // 管理者アドレス
+ uint public constant GRACE_PERIOD = 7 days; // トランザクション有効期限、期限切れのトランザクションは無効
+ uint public delay; // トランザクションロック時間(秒)
+ mapping (bytes32 => bool) public queuedTransactions; // txHashからboolへ、タイムロックキュー内のすべてのトランザクションを記録
+```
+
+### 修飾子
+`Timelock`コントラクトには合計`2`つの`modifier`があります。
+- `onlyOwner()`:修飾された関数は管理者のみが実行可能。
+- `onlyTimelock()`:修飾された関数はタイムロックコントラクトのみが実行可能。
+
+```solidity
+ // onlyOwner modifier
+ modifier onlyOwner() {
+ require(msg.sender == admin, "Timelock: Caller not admin");
+ _;
+ }
+
+ // onlyTimelock modifier
+ modifier onlyTimelock() {
+ require(msg.sender == address(this), "Timelock: Caller not Timelock");
+ _;
+ }
+```
+
+### 関数
+`Timelock`コントラクトには合計`7`つの関数があります。
+
+- コンストラクタ:トランザクションロック時間(秒)と管理者アドレスを初期化。
+- `queueTransaction()`:トランザクションを作成してタイムロックキューに追加。パラメータは複雑で、完全なトランザクションを記述する必要があります:
+ - `target`:対象コントラクトアドレス
+ - `value`:送信ETH数量
+ - `signature`:呼び出す関数シグネチャ(function signature)
+ - `data`:トランザクションのcall data
+ - `executeTime`:トランザクション実行のブロックチェーンタイムスタンプ。
+
+ この関数を呼び出す際、トランザクション予定実行時間`executeTime`が現在のブロックチェーンタイムスタンプ+ロック時間`delay`より大きいことを保証する必要があります。トランザクションの一意識別子はすべてのパラメータのハッシュ値で、`getTxHash()`関数で計算されます。キューに参加したトランザクションは`queuedTransactions`変数で更新され、`QueueTransaction`イベントを発行します。
+- `executeTransaction()`:トランザクションを実行。パラメータは`queueTransaction()`と同じです。実行されるトランザクションがタイムロックキューにあり、トランザクションの実行時間に達し、期限切れでないことが要求されます。トランザクション実行時には`solidity`の低級メンバー関数`call`を使用します([第22講](https://github.com/AmazingAng/WTF-Solidity/blob/main/22_Call/readme.md)で紹介)。
+- `cancelTransaction()`:トランザクションをキャンセル。パラメータは`queueTransaction()`と同じです。キャンセルされるトランザクションがキューにあることが要求され、`queuedTransactions`を更新して`CancelTransaction`イベントを発行します。
+- `changeAdmin()`:管理者アドレスを変更、`Timelock`コントラクトのみが呼び出し可能。
+- `getBlockTimestamp()`:現在のブロックチェーンタイムスタンプを取得。
+- `getTxHash()`:トランザクションの識別子を返す、多くのトランザクションパラメータの`hash`。
+
+```solidity
+ /**
+ * @dev コンストラクタ、トランザクションロック時間(秒)と管理者アドレスを初期化
+ */
+ constructor(uint delay_) {
+ delay = delay_;
+ admin = msg.sender;
+ }
+
+ /**
+ * @dev 管理者アドレスを変更、呼び出し者はTimelockコントラクトである必要があります。
+ */
+ function changeAdmin(address newAdmin) public onlyTimelock {
+ admin = newAdmin;
+
+ emit NewAdmin(newAdmin);
+ }
+
+ /**
+ * @dev トランザクションを作成してタイムロックキューに追加。
+ * @param target: 対象コントラクトアドレス
+ * @param value: 送信eth数量
+ * @param signature: 呼び出す関数シグネチャ(function signature)
+ * @param data: call data、内部にいくつかのパラメータがあります
+ * @param executeTime: トランザクション実行のブロックチェーンタイムスタンプ
+ *
+ * 要求:executeTime は 現在のブロックチェーンタイムスタンプ+delay より大きい
+ */
+ function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner returns (bytes32) {
+ // チェック:トランザクション実行時間がロック時間を満たす
+ require(executeTime >= getBlockTimestamp() + delay, "Timelock::queueTransaction: Estimated execution block must satisfy delay.");
+ // トランザクションの一意識別子を計算:一堆東西のhash
+ bytes32 txHash = getTxHash(target, value, signature, data, executeTime);
+ // トランザクションをキューに追加
+ queuedTransactions[txHash] = true;
+
+ emit QueueTransaction(txHash, target, value, signature, data, executeTime);
+ return txHash;
+ }
+
+ /**
+ * @dev 特定のトランザクションをキャンセル。
+ *
+ * 要求:トランザクションがタイムロックキューにある
+ */
+ function cancelTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner{
+ // トランザクションの一意識別子を計算:一堆東西のhash
+ bytes32 txHash = getTxHash(target, value, signature, data, executeTime);
+ // チェック:トランザクションがタイムロックキューにある
+ require(queuedTransactions[txHash], "Timelock::cancelTransaction: Transaction hasn't been queued.");
+ // トランザクションをキューから削除
+ queuedTransactions[txHash] = false;
+
+ emit CancelTransaction(txHash, target, value, signature, data, executeTime);
+ }
+
+ /**
+ * @dev 特定のトランザクションを実行。
+ *
+ * 要求:
+ * 1. トランザクションがタイムロックキューにある
+ * 2. トランザクションの実行時間に達している
+ * 3. トランザクションが期限切れでない
+ */
+ function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public payable onlyOwner returns (bytes memory) {
+ bytes32 txHash = getTxHash(target, value, signature, data, executeTime);
+ // チェック:トランザクションがタイムロックキューにあるか
+ require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
+ // チェック:トランザクションの実行時間に達しているか
+ require(getBlockTimestamp() >= executeTime, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
+ // チェック:トランザクションが期限切れでないか
+ require(getBlockTimestamp() <= executeTime + GRACE_PERIOD, "Timelock::executeTransaction: Transaction is stale.");
+ // トランザクションをキューから削除
+ queuedTransactions[txHash] = false;
+
+ // call dataを取得
+ bytes memory callData;
+ if (bytes(signature).length == 0) {
+ callData = data;
+ } else {
+ callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
+ }
+ // callを利用してトランザクションを実行
+ (bool success, bytes memory returnData) = target.call{value: value}(callData);
+ require(success, "Timelock::executeTransaction: Transaction execution reverted.");
+
+ emit ExecuteTransaction(txHash, target, value, signature, data, executeTime);
+
+ return returnData;
+ }
+
+ /**
+ * @dev 現在のブロックチェーンタイムスタンプを取得
+ */
+ function getBlockTimestamp() public view returns (uint) {
+ return block.timestamp;
+ }
+
+ /**
+ * @dev 一堆東西をまとめてトランザクションの識別子にする
+ */
+ function getTxHash(
+ address target,
+ uint value,
+ string memory signature,
+ bytes memory data,
+ uint executeTime
+ ) public pure returns (bytes32) {
+ return keccak256(abi.encode(target, value, signature, data, executeTime));
+ }
+```
+
+## `Remix`デモ
+### 1. `Timelock`コントラクトをデプロイ、ロック期間を`120`秒に設定。
+
+
+
+### 2. 直接`changeAdmin()`を呼び出すとエラーが発生。
+
+
+
+### 3. 管理者変更トランザクションを構築。
+トランザクションを構築するために、以下のパラメータをそれぞれ入力する必要があります:
+address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime
+- `target`:`Timelock`自体の関数を呼び出すため、コントラクトアドレスを入力。
+- `value`:ETHを転送しないため、ここは`0`。
+- `signature`:`changeAdmin()`の関数シグネチャ:`"changeAdmin(address)"`。
+- `data`:ここに渡すパラメータ、つまり新しい管理者のアドレスを入力。ただし、アドレスを32バイトのデータに埋め込み、[イーサリアムABIエンコード標準](https://github.com/AmazingAng/WTF-Solidity/blob/main/27_ABIEncode/readme.md)を満たす必要があります。[hashex](https://abi.hashex.org/)サイトでパラメータのABIエンコードができます。例:
+ ```solidity
+ エンコード前アドレス:0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
+ エンコード後アドレス:0x000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2
+ ```
+- `executeTime`:まず`getBlockTimestamp()`を呼び出して現在のブロックチェーン時間を取得し、その上に150秒追加して入力。
+
+
+### 4. `queueTransaction`を呼び出して、トランザクションをタイムロックキューに配置。
+
+
+
+### 5. ロック期間内に`executeTransaction`を呼び出すと、呼び出しに失敗。
+
+
+
+### 6. ロック期間満了時に`executeTransaction`を呼び出すと、トランザクションが成功。
+
+
+
+### 7. 新しい`admin`アドレスを確認。
+
+
+
+## まとめ
+
+タイムロックはスマートコントラクトの特定の機能を一定期間ロックし、プロジェクト方の`rug pull`やハッカー攻撃の機会を大幅に減らし、分散型アプリケーションのセキュリティを向上させることができます。`DeFi`と`DAO`で広く採用されており、`Uniswap`や`Compound`も含まれます。あなたが投資しているプロジェクトはタイムロックを使用していますか?
\ No newline at end of file
diff --git a/Languages/ja/46_ProxyContract_ja/readme.md b/Languages/ja/46_ProxyContract_ja/readme.md
new file mode 100644
index 000000000..528844200
--- /dev/null
+++ b/Languages/ja/46_ProxyContract_ja/readme.md
@@ -0,0 +1,200 @@
+---
+title: 46. プロキシコントラクト
+tags:
+ - solidity
+ - proxy
+
+---
+
+# WTF Solidity 超シンプル入門: 46. プロキシコントラクト
+
+最近、Solidityを再学習しており、詳細を確認しながら「WTF Solidity 超シンプル入門」を執筆しています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週1〜3レッスンのペースで更新していきます。
+
+Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ: [Discord](https://discord.gg/5akcruXrsk)|[WeChat グループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはGitHubにて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+このレッスンでは、プロキシコントラクト(Proxy Contract)について説明します。教材のコードはOpenZeppelinの[Proxyコントラクト](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol)を簡略化したものです。
+
+## プロキシパターン
+
+`Solidity`コントラクトがチェーン上にデプロイされた後、コードは不変(immutable)になります。これには長所と短所があります:
+
+- 長所:安全で、ユーザーは何が起こるかを知ることができる(大部分の場合)
+- 短所:コントラクトにバグが存在していても修正やアップグレードができず、新しいコントラクトをデプロイする必要がある。しかし、新しいコントラクトのアドレスは古いものとは異なり、コントラクトのデータも大量のガスを消費して移行する必要がある。
+
+コントラクトをデプロイ後に修正またはアップグレードする方法はあるのでしょうか?答えは「はい」です。それが**プロキシパターン**です。
+
+
+
+プロキシパターンはコントラクトのデータとロジックを分離し、それぞれを異なるコントラクトに保存します。上図のシンプルなプロキシコントラクトを例にすると、データ(状態変数)はプロキシコントラクトに保存され、ロジック(関数)は別のロジックコントラクトに保存されます。プロキシコントラクト(Proxy)は`delegatecall`を通じて、関数呼び出しを完全にロジックコントラクト(Implementation)に委託して実行し、最終的な結果を呼び出し元(Caller)に返します。
+
+プロキシパターンには主に2つの利点があります:
+1. アップグレード可能:コントラクトのロジックをアップグレードする必要がある場合、プロキシコントラクトを新しいロジックコントラクトに向けるだけで済みます。
+2. ガス節約:複数のコントラクトが同じロジックを使用する場合、1つのロジックコントラクトをデプロイし、データのみを保存する複数のプロキシコントラクトをデプロイして、ロジックコントラクトを参照すればよい。
+
+**ヒント**:`delegatecall`に慣れていない方は、本チュートリアルの[第23回 Delegatecall](https://github.com/AmazingAng/WTF-Solidity/tree/main/23_Delegatecall)をご覧ください。
+
+## プロキシコントラクト
+
+以下、OpenZeppelinの[Proxyコントラクト](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol)から簡略化されたシンプルなプロキシコントラクトを紹介します。これには3つの部分があります:プロキシコントラクト`Proxy`、ロジックコントラクト`Logic`、そして呼び出し例`Caller`です。ロジックはそれほど複雑ではありません:
+
+- まずロジックコントラクト`Logic`をデプロイする
+- プロキシコントラクト`Proxy`を作成し、状態変数`implementation`に`Logic`コントラクトのアドレスを記録
+- `Proxy`コントラクトはフォールバック関数`fallback`を利用して、すべての呼び出しを`Logic`コントラクトに委託
+- 最後に呼び出し例`Caller`コントラクトをデプロイし、`Proxy`コントラクトを呼び出す
+- **注意**:`Logic`コントラクトと`Proxy`コントラクトの状態変数の保存構造は同じでなければならず、そうでないと`delegatecall`が予期しない動作を引き起こし、セキュリティリスクが発生します。
+
+### プロキシコントラクト`Proxy`
+
+`Proxy`コントラクトは長くありませんが、インラインアセンブリを使用しているため理解が難しいです。状態変数1つ、コンストラクタ1つ、フォールバック関数1つのみです。状態変数`implementation`はコンストラクタで初期化され、`Logic`コントラクトのアドレスを保存するために使用されます。
+
+```solidity
+contract Proxy {
+ address public implementation; // ロジックコントラクトのアドレス。implementationコントラクトの同じ位置の状態変数の型はProxyコントラクトと同じでなければならない
+
+ /**
+ * @dev ロジックコントラクトのアドレスを初期化
+ */
+ constructor(address implementation_){
+ implementation = implementation_;
+ }
+```
+
+`Proxy`のフォールバック関数は、本コントラクトへの外部呼び出しを`Logic`コントラクトに委託します。このフォールバック関数は特殊で、インラインアセンブリ(inline assembly)を利用して、本来返り値を持つことができないフォールバック関数に返り値を持たせています。使用されているインラインアセンブリのオペコード:
+
+- `calldatacopy(t, f, s)`:calldata(入力データ)を位置`f`から`s`バイト分、メモリの位置`t`にコピー
+- `delegatecall(g, a, in, insize, out, outsize)`:アドレス`a`のコントラクトを呼び出し、入力は`mem[in..(in+insize))`、出力は`mem[out..(out+outsize))`、`g` weiのイーサリアムガスを提供。このオペコードはエラー時に`0`を返し、成功時に`1`を返す
+- `returndatacopy(t, f, s)`:returndata(出力データ)を位置`f`から`s`バイト分、メモリの位置`t`にコピー
+- `switch`:基本的な`if/else`、異なるケース`case`で異なる値を返す。デフォルトの`default`ケースを持つことができる
+- `return(p, s)`:関数の実行を終了し、データ`mem[p..(p+s))`を返す
+- `revert(p, s)`:関数の実行を終了し、状態をロールバック、データ`mem[p..(p+s))`を返す
+
+```solidity
+/**
+* @dev フォールバック関数、本コントラクトへの呼び出しを`implementation`コントラクトに委託
+* assemblyを通じて、フォールバック関数も返り値を持つことができる
+*/
+fallback() external payable {
+ address _implementation = implementation;
+ assembly {
+ // msg.dataをメモリにコピー
+ // calldatacopyオペコードのパラメータ:メモリ開始位置、calldata開始位置、calldata長さ
+ calldatacopy(0, 0, calldatasize())
+
+ // delegatecallを使用してimplementationコントラクトを呼び出す
+ // delegatecallオペコードのパラメータ:gas、ターゲットコントラクトアドレス、input mem開始位置、input mem長さ、output area mem開始位置、output area mem長さ
+ // output area開始位置と長さは0に設定
+ // delegatecall成功時は1を返し、失敗時は0を返す
+ let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
+
+ // return dataをメモリにコピー
+ // returndataオペコードのパラメータ:メモリ開始位置、returndata開始位置、returndata長さ
+ returndatacopy(0, 0, returndatasize())
+
+ switch result
+ // delegatecallが失敗した場合、revert
+ case 0 {
+ revert(0, returndatasize())
+ }
+ // delegatecallが成功した場合、mem開始位置0、長さreturndatasize()のデータ(bytes形式)を返す
+ default {
+ return(0, returndatasize())
+ }
+ }
+}
+```
+
+### ロジックコントラクト`Logic`
+
+これはプロキシコントラクトのデモンストレーションのための非常にシンプルなロジックコントラクトです。`2`つの変数、`1`つのイベント、`1`つの関数が含まれています:
+- `implementation`:プレースホルダー変数、`Proxy`コントラクトと一致させ、スロット競合を防ぐ
+- `x`:`uint`変数、`99`に設定されている
+- `CallSuccess`イベント:呼び出し成功時に発行される
+- `increment()`関数:`Proxy`コントラクトから呼び出され、`CallSuccess`イベントを発行し、`uint`を返す。セレクタは`0xd09de08a`。直接`increment()`を呼び出すと`100`を返すが、`Proxy`を通じて呼び出すと`1`を返す。なぜか考えてみてください。
+
+```solidity
+/**
+ * @dev ロジックコントラクト、委託された呼び出しを実行
+ */
+contract Logic {
+ address public implementation; // Proxyと一致させ、スロット競合を防ぐ
+ uint public x = 99;
+ event CallSuccess(); // 呼び出し成功イベント
+
+ // この関数はCallSuccessイベントを発行し、uintを返す
+ // 関数セレクタ: 0xd09de08a
+ function increment() external returns(uint) {
+ emit CallSuccess();
+ return x + 1;
+ }
+}
+```
+
+### 呼び出し元コントラクト`Caller`
+
+`Caller`コントラクトは、プロキシコントラクトを呼び出す方法を示します。これも非常にシンプルです。しかし、理解するためには本チュートリアルの[第22回 Call](https://github.com/AmazingAng/WTF-Solidity/tree/main/22_Call/readme.md)と[第27回 ABIエンコーディング](https://github.com/AmazingAng/WTF-Solidity/tree/main/27_ABIEncode/readme.md)を先に学習する必要があります。
+
+`1`つの変数と`2`つの関数があります:
+- `proxy`:状態変数、プロキシコントラクトのアドレスを記録
+- コンストラクタ:コントラクトデプロイ時に`proxy`変数を初期化
+- `increase()`:`call`を利用してプロキシコントラクトの`increment()`関数を呼び出し、`uint`を返す。呼び出し時には、`abi.encodeWithSignature()`を利用して`increment()`関数のセレクタを取得。返り値では、`abi.decode()`を利用して返り値を`uint`型にデコード
+
+```solidity
+/**
+ * @dev Callerコントラクト、プロキシコントラクトを呼び出し、実行結果を取得
+ */
+contract Caller{
+ address public proxy; // プロキシコントラクトアドレス
+
+ constructor(address proxy_){
+ proxy = proxy_;
+ }
+
+ // プロキシコントラクトを通じてincrement()関数を呼び出す
+ function increment() external returns(uint) {
+ ( , bytes memory data) = proxy.call(abi.encodeWithSignature("increment()"));
+ return abi.decode(data,(uint));
+ }
+}
+```
+
+## `Remix`でのデモンストレーション
+
+1. `Logic`コントラクトをデプロイする。
+
+
+
+2. `Logic`コントラクトの`increment()`関数を呼び出すと、`100`が返される。
+
+
+
+3. `Proxy`コントラクトをデプロイし、初期化時に`Logic`コントラクトのアドレスを入力する。
+
+
+
+4. `Proxy`コントラクトの`increment()`関数を呼び出すと、返り値はない。
+
+ 呼び出し方法:`Remix`のデプロイパネルで`Proxy`コントラクトをクリックし、一番下の`Low level interaction`に`increment()`関数のセレクタ`0xd09de08a`を入力し、`Transact`をクリック。
+
+
+
+5. `Caller`コントラクトをデプロイし、初期化時に`Proxy`コントラクトのアドレスを入力する。
+
+
+
+6. `Caller`コントラクトの`increment()`関数を呼び出すと、`1`が返される。
+
+
+
+## まとめ
+
+このレッスンでは、プロキシパターンとシンプルなプロキシコントラクトについて説明しました。プロキシコントラクトは`delegatecall`を利用して関数呼び出しを別のロジックコントラクトに委託し、データとロジックが異なるコントラクトによって管理されます。また、インラインアセンブリの黒魔術を利用して、返り値を持たないフォールバック関数でもデータを返せるようにしています。前述の質問への答え:なぜProxyを通じて`increment()`を呼び出すと1が返されるのか?[第23回 Delegatecall](https://github.com/AmazingAng/WTF-Solidity/tree/main/23_Delegatecall)で説明したように、CallerコントラクトがProxyコントラクトを通じてLogicコントラクトを`delegatecall`する際、Logicコントラクトの関数が状態変数を変更または読み取る場合、すべてProxyの対応する変数上で操作されます。ここでProxyコントラクトの`x`変数の値は0です(`x`変数を設定したことがないため、Proxyコントラクトのstorageエリアの対応位置の値は0)。そのため、Proxyを通じて`increment()`を呼び出すと1が返されます。
+
+次のレッスンでは、アップグレード可能なプロキシコントラクトについて説明します。
+
+プロキシコントラクトは非常に強力ですが、`バグ`が発生しやすいため、使用する際は[OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy)のテンプレートコントラクトを直接コピーすることをお勧めします。
\ No newline at end of file
diff --git a/Languages/ja/47_Upgrade_ja/readme.md b/Languages/ja/47_Upgrade_ja/readme.md
new file mode 100644
index 000000000..7c2e532fb
--- /dev/null
+++ b/Languages/ja/47_Upgrade_ja/readme.md
@@ -0,0 +1,140 @@
+---
+title: 47. アップグレード可能コントラクト
+tags:
+ - solidity
+ - proxy
+ - OpenZeppelin
+
+---
+
+# WTF Solidity 超シンプル入門: 47. アップグレード可能コントラクト
+
+最近、Solidityを再学習しており、詳細を確認しながら「WTF Solidity 超シンプル入門」を執筆しています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週1〜3レッスンのペースで更新していきます。
+
+Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ: [Discord](https://discord.gg/5akcruXrsk)|[WeChat グループ](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはGitHubにて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+このレッスンでは、アップグレード可能コントラクト(Upgradeable Contract)について説明します。教材で使用するコントラクトは`OpenZeppelin`のコントラクトを簡略化したもので、セキュリティ上の問題がある可能性があるため、本番環境では使用しないでください。
+
+## アップグレード可能コントラクト
+
+プロキシコントラクトを理解していれば、アップグレード可能コントラクトを理解するのは簡単です。これはロジックコントラクトを変更できるプロキシコントラクトです。
+
+
+
+## シンプルな実装
+
+以下、シンプルなアップグレード可能コントラクトを実装します。これには`3`つのコントラクトが含まれます:プロキシコントラクト、旧ロジックコントラクト、新ロジックコントラクトです。
+
+### プロキシコントラクト
+
+このプロキシコントラクトは[第46回](https://github.com/AmazingAng/WTF-Solidity/blob/main/46_ProxyContract/readme.md)のものよりシンプルです。`fallback()`関数で`インラインアセンブリ`を使用せず、単に`implementation.delegatecall(msg.data);`を使用しています。そのため、フォールバック関数には返り値がありませんが、教材としては十分です。
+
+`3`つの変数を含みます:
+
+- `implementation`:ロジックコントラクトのアドレス
+- `admin`:管理者アドレス
+- `words`:文字列、ロジックコントラクトの関数を通じて変更可能
+
+`3`つの関数を含みます:
+
+- コンストラクタ:管理者とロジックコントラクトのアドレスを初期化
+- `fallback()`:フォールバック関数、呼び出しをロジックコントラクトに委託
+- `upgrade()`:アップグレード関数、ロジックコントラクトのアドレスを変更、`admin`のみが呼び出し可能
+
+```solidity
+// SPDX-License-Identifier: MIT
+// wtf.academy
+pragma solidity ^0.8.21;
+
+// シンプルなアップグレード可能コントラクト、管理者はアップグレード関数を通じてロジックコントラクトアドレスを変更でき、
+// それによりコントラクトのロジックを変更できる
+// 教材デモ用、本番環境では使用しないでください
+contract SimpleUpgrade {
+ address public implementation; // ロジックコントラクトアドレス
+ address public admin; // 管理者アドレス
+ string public words; // 文字列、ロジックコントラクトの関数を通じて変更可能
+
+ // コンストラクタ、管理者とロジックコントラクトアドレスを初期化
+ constructor(address _implementation){
+ admin = msg.sender;
+ implementation = _implementation;
+ }
+
+ // fallback関数、呼び出しをロジックコントラクトに委託
+ fallback() external payable {
+ (bool success, bytes memory data) = implementation.delegatecall(msg.data);
+ }
+
+ // アップグレード関数、ロジックコントラクトアドレスを変更、管理者のみ呼び出し可能
+ function upgrade(address newImplementation) external {
+ require(msg.sender == admin);
+ implementation = newImplementation;
+ }
+}
+```
+
+### 旧ロジックコントラクト
+
+このロジックコントラクトには`3`つの状態変数が含まれ、プロキシコントラクトと一致させてスロット競合を防ぎます。`foo()`関数が1つだけあり、プロキシコントラクトの`words`の値を`"old"`に変更します。
+
+```solidity
+// ロジックコントラクト1
+contract Logic1 {
+ // 状態変数はproxyコントラクトと一致、スロット競合を防ぐ
+ address public implementation;
+ address public admin;
+ string public words; // 文字列、ロジックコントラクトの関数を通じて変更可能
+
+ // proxyの状態変数を変更、セレクタ:0xc2985578
+ function foo() public{
+ words = "old";
+ }
+}
+```
+
+### 新ロジックコントラクト
+
+このロジックコントラクトには`3`つの状態変数が含まれ、プロキシコントラクトと一致させてスロット競合を防ぎます。`foo()`関数が1つだけあり、プロキシコントラクトの`words`の値を`"new"`に変更します。
+
+```solidity
+// ロジックコントラクト2
+contract Logic2 {
+ // 状態変数はproxyコントラクトと一致、スロット競合を防ぐ
+ address public implementation;
+ address public admin;
+ string public words; // 文字列、ロジックコントラクトの関数を通じて変更可能
+
+ // proxyの状態変数を変更、セレクタ:0xc2985578
+ function foo() public{
+ words = "new";
+ }
+}
+```
+
+## `Remix`での実装
+
+1. 新旧ロジックコントラクト`Logic1`と`Logic2`をデプロイする。
+
+
+
+2. アップグレード可能コントラクト`SimpleUpgrade`をデプロイし、`implementation`アドレスを旧ロジックコントラクトに向ける。
+
+
+3. セレクタ`0xc2985578`を使用して、プロキシコントラクトで旧ロジックコントラクト`Logic1`の`foo()`関数を呼び出し、`words`の値を`"old"`に変更する。
+
+
+4. `upgrade()`を呼び出し、`implementation`アドレスを新ロジックコントラクト`Logic2`に向ける。
+
+
+5. セレクタ`0xc2985578`を使用して、プロキシコントラクトで新ロジックコントラクト`Logic2`の`foo()`関数を呼び出し、`words`の値を`"new"`に変更する。
+
+
+## まとめ
+
+このレッスンでは、シンプルなアップグレード可能コントラクトを紹介しました。これはロジックコントラクトを変更できるプロキシコントラクトで、変更不可能なスマートコントラクトにアップグレード機能を追加します。ただし、このコントラクトには`セレクタ競合`の問題があり、セキュリティリスクが存在します。次回は、このリスクを解決するアップグレード可能コントラクトの標準:透明プロキシと`UUPS`について説明します。
\ No newline at end of file
diff --git a/Languages/ja/48_TransparentProxy_ja/readme.md b/Languages/ja/48_TransparentProxy_ja/readme.md
new file mode 100644
index 000000000..57f6d46d2
--- /dev/null
+++ b/Languages/ja/48_TransparentProxy_ja/readme.md
@@ -0,0 +1,144 @@
+# WTF Solidity 超シンプル入門: 48. 透明プロキシ
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+この講義では、プロキシコントラクトのセレクター衝突(Selector Clash)と、この問題の解決策である透明プロキシ(Transparent Proxy)について説明します。教育用のコードは`OpenZeppelin`の[TransparentUpgradeableProxy](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/TransparentUpgradeableProxy.sol)を簡略化したもので、本番環境での使用は適していません。
+
+## セレクター衝突
+
+スマートコントラクトにおいて、関数セレクター(selector)は関数シグネチャのハッシュの最初の4バイトです。例えば`mint(address account)`のセレクターは`bytes4(keccak256("mint(address)"))`、つまり`0x6a627842`です。セレクターについての詳細は[WTF Solidity超シンプル入門第29講:関数セレクター](https://github.com/AmazingAng/WTF-Solidity/blob/main/29_Selector/readme.md)を参照してください。
+
+関数セレクターは4バイトのみで範囲が小さいため、異なる2つの関数が同じセレクターを持つ可能性があります。以下の例をご覧ください:
+
+```solidity
+// セレクター衝突の例
+contract Foo {
+ function burn(uint256) external {}
+ function collate_propagate_storage(bytes16) external {}
+}
+```
+
+
+
+この例では、関数`burn()`と`collate_propagate_storage()`のセレクターは共に`0x42966c68`で同じです。この状況を「セレクター衝突」と呼びます。この場合、`EVM`は関数セレクターによってユーザーがどの関数を呼び出したいかを判別できないため、このコントラクトはコンパイルできません。
+
+プロキシコントラクトとロジックコントラクトは2つの別々のコントラクトなので、それらの間に「セレクター衝突」があっても正常にコンパイルできますが、これは深刻なセキュリティ事故を引き起こす可能性があります。例えば、ロジックコントラクトの`a`関数とプロキシコントラクトのアップグレード関数のセレクターが同じ場合、管理者が`a`関数を呼び出すときに、プロキシコントラクトがブラックホールコントラクトにアップグレードされてしまうという深刻な結果を招く可能性があります。
+
+現在、この問題を解決する2つのアップグレード可能なコントラクト標準があります:透明プロキシ`Transparent Proxy`と汎用アップグレード可能プロキシ`UUPS`です。
+
+## 透明プロキシ
+
+透明プロキシのロジックは非常にシンプルです:管理者が「関数セレクター衝突」によってロジックコントラクトの関数を呼び出すときに、プロキシコントラクトのアップグレード関数を誤って呼び出してしまう可能性があります。そこで管理者の権限を制限し、ロジックコントラクトの関数を一切呼び出せないようにすることで衝突を解決します:
+
+- 管理者は道具役となり、プロキシコントラクトのアップグレード関数のみを呼び出してコントラクトをアップグレードでき、コールバック関数を通じてロジックコントラクトを呼び出すことはできません。
+- その他のユーザーはアップグレード関数を呼び出せませんが、ロジックコントラクトの関数は呼び出せます。
+
+### プロキシコントラクト
+
+ここのプロキシコントラクトは[第47講](https://github.com/AmazingAng/WTF-Solidity/blob/main/47_Upgrade/readme.md)のものと非常に似ていますが、`fallback()`関数が管理者アドレスの呼び出しを制限している点が異なります。
+
+3つの変数を含みます:
+- `implementation`:ロジックコントラクトのアドレス。
+- `admin`:管理者アドレス。
+- `words`:文字列、ロジックコントラクトの関数で変更可能。
+
+3つの関数を含みます:
+
+- コンストラクタ:管理者とロジックコントラクトのアドレスを初期化。
+- `fallback()`:コールバック関数、呼び出しをロジックコントラクトに委譲、`admin`は呼び出し不可。
+- `upgrade()`:アップグレード関数、ロジックコントラクトのアドレスを変更、`admin`のみ呼び出し可能。
+
+```solidity
+// 透明アップグレード可能コントラクトの教育用コード、本番環境では使用しないでください。
+contract TransparentProxy {
+ address implementation; // ロジックコントラクトのアドレス
+ address admin; // 管理者
+ string public words; // 文字列、ロジックコントラクトの関数で変更可能
+
+ // コンストラクタ、管理者とロジックコントラクトのアドレスを初期化
+ constructor(address _implementation){
+ admin = msg.sender;
+ implementation = _implementation;
+ }
+
+ // fallback関数、呼び出しをロジックコントラクトに委譲
+ // 管理者は呼び出し不可、セレクター衝突による事故を回避
+ fallback() external payable {
+ require(msg.sender != admin);
+ (bool success, bytes memory data) = implementation.delegatecall(msg.data);
+ }
+
+ // アップグレード関数、ロジックコントラクトのアドレスを変更、管理者のみ呼び出し可能
+ function upgrade(address newImplementation) external {
+ if (msg.sender != admin) revert();
+ implementation = newImplementation;
+ }
+}
+```
+
+### ロジックコントラクト
+
+ここの新旧ロジックコントラクトは[第47講](https://github.com/AmazingAng/WTF-Solidity/blob/main/47_Upgrade/readme.md)と同じです。ロジックコントラクトは3つの状態変数を含み、プロキシコントラクトと一致させてスロット衝突を防ぎます。関数`foo()`を1つ含み、旧ロジックコントラクトは`words`の値を`"old"`に変更し、新しいものは`"new"`に変更します。
+
+```solidity
+// 旧ロジックコントラクト
+contract Logic1 {
+ // 状態変数をプロキシコントラクトと一致させ、スロット衝突を防ぐ
+ address public implementation;
+ address public admin;
+ string public words; // 文字列、ロジックコントラクトの関数で変更可能
+
+ // プロキシ内の状態変数を変更、セレクター: 0xc2985578
+ function foo() public{
+ words = "old";
+ }
+}
+
+// 新ロジックコントラクト
+contract Logic2 {
+ // 状態変数をプロキシコントラクトと一致させ、スロット衝突を防ぐ
+ address public implementation;
+ address public admin;
+ string public words; // 文字列、ロジックコントラクトの関数で変更可能
+
+ // プロキシ内の状態変数を変更、セレクター:0xc2985578
+ function foo() public{
+ words = "new";
+ }
+}
+```
+
+## `Remix`での実装
+
+1. 新旧ロジックコントラクト`Logic1`と`Logic2`をデプロイします。
+
+
+
+2. 透明プロキシコントラクト`TranparentProxy`をデプロイし、`implementation`アドレスを旧ロジックコントラクトに指定します。
+
+
+3. セレクター`0xc2985578`を使用して、プロキシコントラクト内で旧ロジックコントラクト`Logic1`の`foo()`関数を呼び出します。管理者はロジックコントラクトを呼び出せないため、呼び出しは失敗します。
+
+
+4. 新しいウォレットに切り替えて、セレクター`0xc2985578`を使用し、プロキシコントラクト内で旧ロジックコントラクト`Logic1`の`foo()`関数を呼び出し、`words`の値を`"old"`に変更します。呼び出しは成功します。
+
+
+5. 管理者ウォレットに戻り、`upgrade()`を呼び出して、`implementation`アドレスを新ロジックコントラクト`Logic2`に指定します。
+
+
+6. 新しいウォレットに切り替えて、セレクター`0xc2985578`を使用し、プロキシコントラクト内で新ロジックコントラクト`Logic2`の`foo()`関数を呼び出し、`words`の値を`"new"`に変更します。
+
+
+## まとめ
+
+この講義では、プロキシコントラクトにおける「セレクター衝突」と、透明プロキシを使用してこの問題を回避する方法について説明しました。透明プロキシのロジックはシンプルで、管理者がロジックコントラクトを呼び出すことを制限することで「セレクター衝突」問題を解決します。欠点もあり、ユーザーが関数を呼び出すたびに管理者かどうかの追加チェックが行われ、より多くのガスを消費します。しかし、瑕疵を補って余りある利点があり、透明プロキシは依然として多くのプロジェクトが選択するソリューションです。
+
+次回の講義では、ガス効率が良いがより複雑な汎用アップグレード可能プロキシ`UUPS`について説明します。
\ No newline at end of file
diff --git a/Languages/ja/49_UUPS_ja/readme.md b/Languages/ja/49_UUPS_ja/readme.md
new file mode 100644
index 000000000..b851a2f7d
--- /dev/null
+++ b/Languages/ja/49_UUPS_ja/readme.md
@@ -0,0 +1,136 @@
+# WTF Solidity 超シンプル入門: 49. 汎用アップグレード可能プロキシ
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+この講義では、プロキシコントラクトにおけるセレクター衝突(Selector Clash)のもう一つの解決方法である汎用アップグレード可能プロキシ(UUPS、universal upgradeable proxy standard)について説明します。教育用のコードは`OpenZeppelin`の`UUPSUpgradeable`を簡略化したもので、本番環境での使用は適していません。
+
+## UUPS
+
+[前回の講義](https://github.com/AmazingAng/WTF-Solidity/blob/main/48_TransparentProxy/readme.md)で「セレクター衝突」(Selector Clash)について学習しました。これは、コントラクトに同じセレクターを持つ2つの関数が存在することで、深刻な結果を招く可能性があります。透明プロキシの代替案として、UUPSもこの問題を解決できます。
+
+UUPS(universal upgradeable proxy standard、汎用アップグレード可能プロキシ)は、アップグレード関数をロジックコントラクト内に配置します。これにより、他の関数とアップグレード関数の間に「セレクター衝突」が存在する場合、コンパイル時にエラーが報告されます。
+
+以下の表では、通常のアップグレード可能コントラクト、透明プロキシ、およびUUPSの違いをまとめています:
+
+
+
+## UUPSコントラクト
+
+まず、[WTF Solidity超シンプル入門第23講:Delegatecall](https://github.com/AmazingAng/WTF-Solidity/blob/main/23_Delegatecall/readme.md)を復習しましょう。ユーザーAがコントラクトB(プロキシコントラクト)を通してコントラクトC(ロジックコントラクト)を`delegatecall`する場合、コンテキストは依然としてコントラクトBのコンテキストであり、`msg.sender`は依然としてユーザーAでありコントラクトBではありません。したがって、UUPSコントラクトはアップグレード関数をロジックコントラクト内に配置し、呼び出し者が管理者であるかどうかをチェックできます。
+
+
+
+### UUPSのプロキシコントラクト
+
+UUPSのプロキシコントラクトは、アップグレード不可能なプロキシコントラクトのように見え、非常にシンプルです。アップグレード関数がロジックコントラクト内に配置されているためです。3つの変数を含みます:
+- `implementation`:ロジックコントラクトのアドレス。
+- `admin`:管理者アドレス。
+- `words`:文字列、ロジックコントラクトの関数で変更可能。
+
+2つの関数を含みます:
+
+- コンストラクタ:管理者とロジックコントラクトのアドレスを初期化。
+- `fallback()`:コールバック関数、呼び出しをロジックコントラクトに委譲。
+
+```solidity
+contract UUPSProxy {
+ address public implementation; // ロジックコントラクトのアドレス
+ address public admin; // 管理者アドレス
+ string public words; // 文字列、ロジックコントラクトの関数で変更可能
+
+ // コンストラクタ、管理者とロジックコントラクトのアドレスを初期化
+ constructor(address _implementation){
+ admin = msg.sender;
+ implementation = _implementation;
+ }
+
+ // fallback関数、呼び出しをロジックコントラクトに委譲
+ fallback() external payable {
+ (bool success, bytes memory data) = implementation.delegatecall(msg.data);
+ }
+}
+```
+
+### UUPSのロジックコントラクト
+
+UUPSのロジックコントラクトと[第47講](https://github.com/AmazingAng/WTF-Solidity/blob/main/47_Upgrade/readme.md)のものとの違いは、アップグレード関数が追加されていることです。UUPSロジックコントラクトは3つの状態変数を含み、プロキシコントラクトと一致させてスロット衝突を防ぎます。2つの関数を含みます:
+- `upgrade()`:アップグレード関数、ロジックコントラクトのアドレス`implementation`を変更、`admin`のみ呼び出し可能。
+- `foo()`:旧UUPSロジックコントラクトは`words`の値を`"old"`に変更し、新しいものは`"new"`に変更。
+
+```solidity
+// UUPSロジックコントラクト(アップグレード関数がロジックコントラクト内に記述)
+contract UUPS1{
+ // 状態変数をプロキシコントラクトと一致させ、スロット衝突を防ぐ
+ address public implementation;
+ address public admin;
+ string public words; // 文字列、ロジックコントラクトの関数で変更可能
+
+ // プロキシ内の状態変数を変更、セレクター: 0xc2985578
+ function foo() public{
+ words = "old";
+ }
+
+ // アップグレード関数、ロジックコントラクトのアドレスを変更、管理者のみ呼び出し可能。セレクター:0x0900f010
+ // UUPSでは、ロジックコントラクト内にアップグレード関数が必要、そうでなければ再度アップグレードできません。
+ function upgrade(address newImplementation) external {
+ require(msg.sender == admin);
+ implementation = newImplementation;
+ }
+}
+
+// 新UUPSロジックコントラクト
+contract UUPS2{
+ // 状態変数をプロキシコントラクトと一致させ、スロット衝突を防ぐ
+ address public implementation;
+ address public admin;
+ string public words; // 文字列、ロジックコントラクトの関数で変更可能
+
+ // プロキシ内の状態変数を変更、セレクター: 0xc2985578
+ function foo() public{
+ words = "new";
+ }
+
+ // アップグレード関数、ロジックコントラクトのアドレスを変更、管理者のみ呼び出し可能。セレクター:0x0900f010
+ // UUPSでは、ロジックコントラクト内にアップグレード関数が必要、そうでなければ再度アップグレードできません。
+ function upgrade(address newImplementation) external {
+ require(msg.sender == admin);
+ implementation = newImplementation;
+ }
+}
+```
+
+## `Remix`での実装
+
+1. UUPS新旧ロジックコントラクト`UUPS1`と`UUPS2`をデプロイします。
+
+
+
+2. UUPSプロキシコントラクト`UUPSProxy`をデプロイし、`implementation`アドレスを旧ロジックコントラクト`UUPS1`に指定します。
+
+
+
+3. セレクター`0xc2985578`を使用して、プロキシコントラクト内で旧ロジックコントラクト`UUPS1`の`foo()`関数を呼び出し、`words`の値を`"old"`に変更します。
+
+
+
+4. オンラインABIエンコーダー[HashEx](https://abi.hashex.org/)を使用してバイナリエンコーディングを取得し、アップグレード関数`upgrade()`を呼び出して、`implementation`アドレスを新ロジックコントラクト`UUPS2`に指定します。
+
+
+
+
+
+5. セレクター`0xc2985578`を使用して、プロキシコントラクト内で新ロジックコントラクト`UUPS2`の`foo()`関数を呼び出し、`words`の値を`"new"`に変更します。
+
+
+
+## まとめ
+
+この講義では、プロキシコントラクトの「セレクター衝突」のもう一つの解決策であるUUPSについて説明しました。透明プロキシとは異なり、UUPSはアップグレード関数をロジックコントラクト内に配置することで、「セレクター衝突」がコンパイルを通過できないようにします。透明プロキシと比較して、UUPSはガス効率が良いですが、より複雑でもあります。
\ No newline at end of file
diff --git a/Languages/ja/50_MultisigWallet_ja/readme.md b/Languages/ja/50_MultisigWallet_ja/readme.md
new file mode 100644
index 000000000..88e556f5f
--- /dev/null
+++ b/Languages/ja/50_MultisigWallet_ja/readme.md
@@ -0,0 +1,292 @@
+# WTF Solidity 超シンプル入門: 50. マルチシグウォレット
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+Vitalik氏は、マルチシグウォレットはハードウェアウォレットよりも安全だと述べています([ツイート](https://twitter.com/VitalikButerin/status/1558886893995134978?s=20&t=4WyoEWhwHNUtAuABEIlcRw))。この講義では、マルチシグウォレットについて説明し、極めてシンプルなマルチシグウォレットコントラクトを作成します。教育用コード(150行のコード)は、gnosis safeコントラクト(数千行のコード)を簡略化したものです。
+
+
+
+## マルチシグウォレット
+
+マルチシグウォレットは、複数の秘密鍵保有者(マルチシグ者)によってトランザクションが承認された後にのみ実行される電子ウォレットです。例えば、ウォレットが3人のマルチシグ者によって管理され、各トランザクションには少なくとも2人の署名承認が必要です。マルチシグウォレットは単一障害点(秘密鍵の紛失、単独での不正行為)を防ぎ、より分散化され、より安全で、多くのDAOに採用されています。
+
+Gnosis Safeマルチシグウォレットは、イーサリアムで最も人気のあるマルチシグウォレットで、約400億ドルの資産を管理しており、コントラクトは監査と実戦テストを経て、マルチチェーン(イーサリアム、BSC、Polygonなど)をサポートし、豊富なDAPPサポートを提供しています。詳細については、私が21年12月に書いた[Gnosis Safe使用チュートリアル](https://peopledao.mirror.xyz/nFCBXda8B5ZxQVqSbbDOn2frFDpTxNVtdqVBXGIjj0s)をご覧ください。
+
+## マルチシグウォレットコントラクト
+
+イーサリアム上のマルチシグウォレットは実際にはスマートコントラクトで、コントラクトウォレットに属します。以下では、極めてシンプルなマルチシグウォレット`MultisigWallet`コントラクトを作成します。そのロジックは非常にシンプルです:
+
+1. マルチシグ者と閾値を設定(オンチェーン):マルチシグコントラクトをデプロイする際、マルチシグ者リストと実行閾値(少なくともn人のマルチシグ者の署名承認後、トランザクションが実行可能)を初期化する必要があります。Gnosis Safeマルチシグウォレットはマルチシグ者の追加/削除および実行閾値の変更をサポートしていますが、私たちの極めてシンプルなバージョンではこの機能は考慮していません。
+
+2. トランザクションの作成(オフチェーン):承認待ちのトランザクションには以下の内容が含まれます:
+ - `to`:ターゲットコントラクト。
+ - `value`:トランザクションで送信するイーサリアムの量。
+ - `data`:calldata、呼び出し関数のセレクターとパラメータを含む。
+ - `nonce`:初期値は`0`、マルチシグコントラクトの各成功実行トランザクションとともに増加する値で、署名リプレイ攻撃を防ぐ。
+ - `chainid`:チェーンID、異なるチェーンの署名リプレイ攻撃を防ぐ。
+
+3. マルチシグ署名の収集(オフチェーン):前のステップのトランザクションをABIエンコードしてハッシュを計算し、トランザクションハッシュを取得し、マルチシグ者に署名してもらい、それらを連結してパック署名を得ます。ABIエンコードとハッシュについて理解していない場合は、WTF Solidity超シンプル入門[第27講](https://github.com/AmazingAng/WTF-Solidity/blob/main/27_ABIEncode/readme.md)と[第28講](https://github.com/AmazingAng/WTF-Solidity/blob/main/28_Hash/readme.md)をご覧ください。
+
+ ```solidity
+ トランザクションハッシュ: 0xc1b055cf8e78338db21407b425114a2e258b0318879327945b661bfdea570e66
+
+ マルチシグ者A署名: 0x014db45aa753fefeca3f99c2cb38435977ebb954f779c2b6af6f6365ba4188df542031ace9bdc53c655ad2d4794667ec2495196da94204c56b1293d0fbfacbb11c
+
+ マルチシグ者B署名: 0xbe2e0e6de5574b7f65cad1b7062be95e7d73fe37dd8e888cef5eb12e964ddc597395fa48df1219e7f74f48d86957f545d0fbce4eee1adfbaff6c267046ade0d81c
+
+ パック署名:
+ 0x014db45aa753fefeca3f99c2cb38435977ebb954f779c2b6af6f6365ba4188df542031ace9bdc53c655ad2d4794667ec2495196da94204c56b1293d0fbfacbb11cbe2e0e6de5574b7f65cad1b7062be95e7d73fe37dd8e888cef5eb12e964ddc597395fa48df1219e7f74f48d86957f545d0fbce4eee1adfbaff6c267046ade0d81c
+ ```
+
+4. マルチシグコントラクトの実行関数を呼び出し、署名を検証してトランザクションを実行(オンチェーン)。署名検証とトランザクション実行について理解していない場合は、WTF Solidity超シンプル入門[第22講](https://github.com/AmazingAng/WTF-Solidity/blob/main/22_Call/readme.md)と[第37講](https://github.com/AmazingAng/WTF-Solidity/blob/main/37_Signature/readme.md)をご覧ください。
+
+### イベント
+
+`MultisigWallet`コントラクトには2つのイベント、`ExecutionSuccess`と`ExecutionFailure`があり、それぞれトランザクションの成功と失敗時に発行され、パラメータはトランザクションハッシュです。
+
+```solidity
+ event ExecutionSuccess(bytes32 txHash); // トランザクション成功イベント
+ event ExecutionFailure(bytes32 txHash); // トランザクション失敗イベント
+```
+
+### 状態変数
+
+`MultisigWallet`コントラクトには5つの状態変数があります:
+1. `owners`:マルチシグ保有者配列
+2. `isOwner`:`address => bool`のマッピング、アドレスがマルチシグ保有者かどうかを記録。
+3. `ownerCount`:マルチシグ保有者数
+4. `threshold`:マルチシグ実行閾値、トランザクションは少なくともn人のマルチシグ者の署名がなければ実行できない。
+5. `nonce`:初期値は`0`、マルチシグコントラクトの各成功実行トランザクションとともに増加する値で、署名リプレイ攻撃を防ぐ。
+
+```solidity
+ address[] public owners; // マルチシグ保有者配列
+ mapping(address => bool) public isOwner; // アドレスがマルチシグ保有者かどうかを記録
+ uint256 public ownerCount; // マルチシグ保有者数
+ uint256 public threshold; // マルチシグ実行閾値、トランザクションは少なくともn人のマルチシグ者の署名がなければ実行できない
+ uint256 public nonce; // nonce、署名リプレイ攻撃を防ぐ
+```
+
+### 関数
+
+`MultisigWallet`コントラクトには6つの関数があります:
+
+1. コンストラクタ:`_setupOwners()`を呼び出し、マルチシグ保有者と実行閾値に関連する変数を初期化。
+ ```solidity
+ // コンストラクタ、owners, isOwner, ownerCount, thresholdを初期化
+ constructor(
+ address[] memory _owners,
+ uint256 _threshold
+ ) {
+ _setupOwners(_owners, _threshold);
+ }
+ ```
+
+2. `_setupOwners()`:コントラクトデプロイ時にコンストラクタによって呼び出され、`owners`、`isOwner`、`ownerCount`、`threshold`状態変数を初期化。渡されるパラメータで、実行閾値は1以上でマルチシグ者数以下である必要があります。マルチシグアドレスは`0`アドレスであってはならず、重複してもいけません。
+ ```solidity
+ /// @dev owners, isOwner, ownerCount, thresholdを初期化
+ /// @param _owners: マルチシグ保有者配列
+ /// @param _threshold: マルチシグ実行閾値、少なくとも何人のマルチシグ者がトランザクションに署名したか
+ function _setupOwners(address[] memory _owners, uint256 _threshold) internal {
+ // thresholdが初期化されていない
+ require(threshold == 0, "WTF5000");
+ // マルチシグ実行閾値がマルチシグ者数以下
+ require(_threshold <= _owners.length, "WTF5001");
+ // マルチシグ実行閾値が少なくとも1
+ require(_threshold >= 1, "WTF5002");
+
+ for (uint256 i = 0; i < _owners.length; i++) {
+ address owner = _owners[i];
+ // マルチシグ者は0アドレス、本コントラクトアドレス、重複であってはならない
+ require(owner != address(0) && owner != address(this) && !isOwner[owner], "WTF5003");
+ owners.push(owner);
+ isOwner[owner] = true;
+ }
+ ownerCount = _owners.length;
+ threshold = _threshold;
+ }
+ ```
+
+3. `execTransaction()`:十分なマルチシグ署名を収集した後、署名を検証してトランザクションを実行。渡されるパラメータは、ターゲットアドレス`to`、送信するイーサリアム量`value`、データ`data`、およびパック署名`signatures`。パック署名は、収集したマルチシグ者のトランザクションハッシュに対する署名を、マルチシグ保有者アドレスの昇順に[bytes]データにパックしたものです。このステップでは`encodeTransactionData()`を呼び出してトランザクションをエンコードし、`checkSignatures()`を呼び出して署名が有効で数量が実行閾値に達しているかを検証します。
+
+ ```solidity
+ /// @dev 十分なマルチシグ署名を収集した後、トランザクションを実行
+ /// @param to ターゲットコントラクトアドレス
+ /// @param value msg.value、支払うイーサリアム
+ /// @param data calldata
+ /// @param signatures パック署名、対応するマルチシグアドレスは小から大の順序で、チェックを容易にする。 ({bytes32 r}{bytes32 s}{uint8 v}) (第1マルチシグの署名, 第2マルチシグの署名 ... )
+ function execTransaction(
+ address to,
+ uint256 value,
+ bytes memory data,
+ bytes memory signatures
+ ) public payable virtual returns (bool success) {
+ // トランザクションデータをエンコードし、ハッシュを計算
+ bytes32 txHash = encodeTransactionData(to, value, data, nonce, block.chainid);
+ nonce++; // nonceを増加
+ checkSignatures(txHash, signatures); // 署名をチェック
+ // callを利用してトランザクションを実行し、トランザクション結果を取得
+ (success, ) = to.call{value: value}(data);
+ require(success , "WTF5004");
+ if (success) emit ExecutionSuccess(txHash);
+ else emit ExecutionFailure(txHash);
+ }
+ ```
+
+4. `checkSignatures()`:署名とトランザクションデータのハッシュが対応し、数量が閾値に達しているかをチェックし、そうでなければトランザクションはrevertします。単一の署名の長さは65バイトなので、パック署名の長さは`threshold * 65`以上である必要があります。`signatureSplit()`を呼び出して単一の署名を分離します。この関数の大まかな考え方:
+ - ecdsaを使用して署名アドレスを取得。
+ - `currentOwner > lastOwner`を利用して署名が異なるマルチシグからのもの(マルチシグアドレス昇順)であることを確認。
+ - `isOwner[currentOwner]`を利用して署名者がマルチシグ保有者であることを確認。
+
+ ```solidity
+ /**
+ * @dev 署名とトランザクションデータが対応しているかをチェック。無効な署名の場合、トランザクションはrevert
+ * @param dataHash トランザクションデータハッシュ
+ * @param signatures 複数のマルチシグ署名をパックしたもの
+ */
+ function checkSignatures(
+ bytes32 dataHash,
+ bytes memory signatures
+ ) public view {
+ // マルチシグ実行閾値を読み取り
+ uint256 _threshold = threshold;
+ require(_threshold > 0, "WTF5005");
+
+ // 署名の長さが十分であることをチェック
+ require(signatures.length >= _threshold * 65, "WTF5006");
+
+ // ループを通じて、収集した署名が有効かをチェック
+ // 大まかな考え方:
+ // 1. ecdsaでまず署名が有効かを検証
+ // 2. currentOwner > lastOwnerを利用して署名が異なるマルチシグからのもの(マルチシグアドレス昇順)であることを確認
+ // 3. isOwner[currentOwner]を利用して署名者がマルチシグ保有者であることを確認
+ address lastOwner = address(0);
+ address currentOwner;
+ uint8 v;
+ bytes32 r;
+ bytes32 s;
+ uint256 i;
+ for (i = 0; i < _threshold; i++) {
+ (v, r, s) = signatureSplit(signatures, i);
+ // ecrecoverを利用して署名が有効かをチェック
+ currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s);
+ require(currentOwner > lastOwner && isOwner[currentOwner], "WTF5007");
+ lastOwner = currentOwner;
+ }
+ }
+ ```
+
+5. `signatureSplit()`:パック署名から単一の署名を分離、パラメータはそれぞれパック署名`signatures`と読み取る署名位置`pos`。インラインアセンブリを利用して、署名の`r`、`s`、`v`の3つの値を分離。
+
+ ```solidity
+ /// 単一の署名をパック署名から分離
+ /// @param signatures パック署名
+ /// @param pos 読み取るマルチシグインデックス
+ function signatureSplit(bytes memory signatures, uint256 pos)
+ internal
+ pure
+ returns (
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ )
+ {
+ // 署名の形式:{bytes32 r}{bytes32 s}{uint8 v}
+ assembly {
+ let signaturePos := mul(0x41, pos)
+ r := mload(add(signatures, add(signaturePos, 0x20)))
+ s := mload(add(signatures, add(signaturePos, 0x40)))
+ v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff)
+ }
+ }
+ ```
+
+6. `encodeTransactionData()`:トランザクションデータをパックしてハッシュを計算、`abi.encode()`と`keccak256()`関数を利用。この関数はトランザクションのハッシュを計算でき、その後オフチェーンでマルチシグ者に署名してもらい収集し、再度`execTransaction()`関数を呼び出して実行します。
+
+ ```solidity
+ /// @dev トランザクションデータをエンコード
+ /// @param to ターゲットコントラクトアドレス
+ /// @param value msg.value、支払うイーサリアム
+ /// @param data calldata
+ /// @param _nonce トランザクションのnonce
+ /// @param chainid チェーンID
+ /// @return トランザクションハッシュbytes
+ function encodeTransactionData(
+ address to,
+ uint256 value,
+ bytes memory data,
+ uint256 _nonce,
+ uint256 chainid
+ ) public pure returns (bytes32) {
+ bytes32 safeTxHash =
+ keccak256(
+ abi.encode(
+ to,
+ value,
+ keccak256(data),
+ _nonce,
+ chainid
+ )
+ );
+ return safeTxHash;
+ }
+ ```
+
+## `Remix`デモ
+
+1. マルチシグコントラクトをデプロイ、2つのマルチシグアドレス、トランザクション実行閾値を2に設定。
+
+ ```solidity
+ マルチシグアドレス1: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+ マルチシグアドレス2: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
+ ```
+
+ 
+
+2. マルチシグコントラクトアドレスに`1 ETH`を送金。
+
+ 
+
+3. `encodeTransactionData()`を呼び出し、マルチシグアドレス1に`1 ETH`を送金するトランザクションをエンコードしてハッシュを計算。
+
+ ```solidity
+ パラメータ
+ to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+ value: 1000000000000000000
+ data: 0x
+ _nonce: 0
+ chainid: 1
+
+ 結果
+ トランザクションハッシュ: 0xb43ad6901230f2c59c3f7ef027c9a372f199661c61beeec49ef5a774231fc39b
+ ```
+
+ 
+
+4. RemixのACCOUNTの横にあるペンアイコンのボタンを利用して署名し、内容に上記のトランザクションハッシュを入力して署名を取得、2つのウォレットとも署名が必要。
+ ```
+ マルチシグアドレス1の署名: 0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b
+
+ マルチシグアドレス2の署名: 0x6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c
+
+ 2つの署名を連結してパック署名を取得: 0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c
+ ```
+
+ 
+
+5. `execTransaction()`関数を呼び出してトランザクションを実行、第3ステップのトランザクションパラメータとパック署名をパラメータとして渡す。トランザクションが正常に実行され、`ETH`がマルチシグから送金されることがわかります。
+
+ 
+
+## まとめ
+
+この講義では、マルチシグウォレットについて説明し、150行未満のコードで極めてシンプルなマルチシグウォレットコントラクトを作成しました。
+
+私はマルチシグウォレットとは深い縁があり、2021年にPeopleDAO国庫作成のためにGnosis Safeを学習し、中英文の[使用チュートリアル](https://peopledao.mirror.xyz/nFCBXda8B5ZxQVqSbbDOn2frFDpTxNVtdqVBXGIjj0s)を書き、その後幸運にも3つの国庫のマルチシグ者として資産安全を維持し、現在はSafeのガーディアンとしてガバナンスに深く参与しています。皆さんの資産がより安全になることを願っています。
\ No newline at end of file
diff --git a/Languages/ja/51_ERC4626_ja/readme.md b/Languages/ja/51_ERC4626_ja/readme.md
new file mode 100644
index 000000000..c7cd977e9
--- /dev/null
+++ b/Languages/ja/51_ERC4626_ja/readme.md
@@ -0,0 +1,482 @@
+---
+title: 51. ERC4626 トークン化金庫標準
+tags:
+ - solidity
+ - erc20
+ - erc4626
+ - defi
+ - vault
+ - openzepplin
+
+---
+
+# WTF Solidity 超シンプル入門: 51. ERC4626 トークン化金庫標準
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+DeFiはよく「マネーレゴ」と呼ばれ、複数のプロトコルを組み合わせて新しいプロトコルを作成できます。しかし、DeFiは標準が不足しているため、その組み合わせ可能性が深刻に影響を受けています。ERC4626は、ERC20トークン標準を拡張し、収益金庫の標準化を推進することを目的としています。今回は、DeFiの新世代標準ERC4626について紹介し、シンプルな金庫コントラクトを作成します。教学コードはopenzeppelinとsolmateのERC4626コントラクトを参考にしており、教学目的でのみ使用します。
+
+## 金庫
+
+金庫コントラクトはDeFiレゴの基盤であり、基礎資産(トークン)をコントラクトに質入れして一定の収益を得ることができます。以下の応用シナリオがあります:
+
+- 収益農場:Yearn Financeでは、`USDT`を質入れして利息を得ることができます。
+- 貸借:AAVEでは、`ETH`を貸し出して預金利息と貸出を得ることができます。
+- 質入れ:Lidoでは、`ETH`を質入れしてETH 2.0質入れに参加し、利息が付く`stETH`を得ることができます。
+
+## ERC4626
+
+
+
+金庫コントラクトは標準が不足しているため、書き方が多様で、一つの収益アグリゲーターが異なるDeFiプロジェクトに対接するために多くのインターフェースを書く必要があります。ERC4626トークン化金庫標準(Tokenized Vault Standard)が登場し、DeFiが簡単に拡張できるようになりました。以下の利点があります:
+
+1. トークン化:ERC4626はERC20を継承し、金庫に預金する際、同様にERC20標準に適合する金庫持分を得ます(例:ETHを質入れすると自動的にstETHを取得)。
+
+2. より良い流動性:トークン化により、基礎資産を取り戻すことなく、金庫持分を使って他のことができます。LidoのstETHを例にすると、UniswapでETHを取り出すことなく流動性を提供したり取引したりできます。
+
+3. より良い組み合わせ可能性:標準ができた後、一つのインターフェースですべてのERC4626金庫とやり取りでき、金庫ベースのアプリケーション、プラグイン、ツールの開発が簡単になります。
+
+総じて、ERC4626のDeFiに対する重要性は、ERC721のNFTに対する重要性に劣りません。
+
+### ERC4626 要点
+
+ERC4626標準は主に以下のロジックを実装します:
+
+1. ERC20:ERC4626はERC20を継承し、金庫持分はERC20トークンで表されます。ユーザーが特定のERC20基礎資産(例:WETH)を金庫に預けると、コントラクトは特定数量の金庫持分トークンを鋳造します。ユーザーが金庫から基礎資産を引き出すと、対応する数量の金庫持分トークンが破棄されます。`asset()`関数は金庫の基礎資産のトークンアドレスを返します。
+2. 預金ロジック:ユーザーが基礎資産を預け、対応する数量の金庫持分を鋳造できるようにします。関連関数は`deposit()`と`mint()`です。`deposit(uint assets, address receiver)`関数はユーザーが`assets`単位の資産を預け、対応する数量の金庫持分を`receiver`アドレスに鋳造します。`mint(uint shares, address receiver)`はそれと似ていますが、鋳造される金庫持分をパラメータとして使用します。
+3. 引き出しロジック:ユーザーが金庫持分を破棄し、金庫から対応する数量の基礎資産を引き出せるようにします。関連関数は`withdraw()`と`redeem()`で、前者は引き出す基礎資産数量をパラメータとし、後者は破棄する金庫持分をパラメータとします。
+4. 会計と限度ロジック:ERC4626標準の他の関数は、金庫内の資産統計、預金/引き出し限度、預金/引き出しの基礎資産と金庫持分数量を統計するためのものです。
+
+### IERC4626 インターフェースコントラクト
+
+IERC4626インターフェースコントラクトには合計`2`つのイベントが含まれます:
+- `Deposit`イベント:預金時にトリガー。
+- `Withdraw`イベント:引き出し時にトリガー。
+
+IERC4626インターフェースコントラクトには`16`の関数が含まれ、機能により`4`つの大カテゴリに分けられます:メタデータ、預金/引き出しロジック、会計ロジック、預金/引き出し限度ロジック。
+
+- メタデータ
+
+ - `asset()`:金庫の基礎資産トークンアドレスを返し、預金、引き出しに使用。
+- 預金/引き出しロジック
+ - `deposit()`:預金関数、ユーザーが金庫に`assets`単位の基礎資産を預け、その後コントラクトが`shares`単位の金庫持分を`receiver`アドレスに鋳造。`Deposit`イベントを発行。
+ - `mint()`:鋳造関数(預金関数でもある)、ユーザーが希望する`shares`単位の金庫持分を指定し、関数が計算後に必要な`assets`単位の基礎資産数量を得て、その後コントラクトがユーザーアカウントから`assets`単位の基礎資産を転送し、`receiver`アドレスに指定数量の金庫持分を鋳造。`Deposit`イベントを発行。
+ - `withdraw()`:引き出し関数、`owner`アドレスが`share`単位の金庫持分を破棄し、その後コントラクトが対応する数量の基礎資産を`receiver`アドレスに送信。
+ - `redeem()`:償還関数(引き出し関数でもある)、`owner`アドレスが`shares`数量の金庫持分を破棄し、その後コントラクトが対応する単位の基礎資産を`receiver`アドレスに送信。
+- 会計ロジック
+ - `totalAssets()`:金庫で管理されている基礎資産トークンの総額を返す。
+ - `convertToShares()`:一定数額の基礎資産で交換できる金庫持分を返す。
+ - `convertToAssets()`:一定数額の金庫持分で交換できる基礎資産を返す。
+ - `previewDeposit()`:現在のオンチェーン環境で一定数額の基礎資産を預金して得られる金庫持分をユーザーがシミュレートするため。
+ - `previewMint()`:現在のオンチェーン環境で一定数額の金庫持分を鋳造するのに必要な基礎資産数量をユーザーがシミュレートするため。
+ - `previewWithdraw()`:現在のオンチェーン環境で一定数額の基礎資産を引き出すのに必要な金庫持分をユーザーがシミュレートするため。
+ - `previewRedeem()`:現在のオンチェーン環境で一定数額の金庫持分を破棄して償還できる基礎資産数量をオンチェーンとオフチェーンユーザーがシミュレートするため。
+- 預金/引き出し限度ロジック
+ - `maxDeposit()`:あるユーザーアドレスの一回の預金で預けられる最大基礎資産数額を返す。
+ - `maxMint()`:あるユーザーアドレスの一回の鋳造で鋳造できる最大金庫持分を返す。
+ - `maxWithdraw()`:あるユーザーアドレスの一回の引き出しで引き出せる最大基礎資産持分を返す。
+ - `maxRedeem()`:あるユーザーアドレスの一回の償還で破棄できる最大金庫持分を返す。
+
+```solidity
+// SPDX-License-Identifier: MIT
+// Author: 0xAA from WTF Academy
+
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
+
+/**
+ * @dev ERC4626 "トークン化金庫標準"のインターフェースコントラクト
+ * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
+ */
+interface IERC4626 is IERC20, IERC20Metadata {
+ /*//////////////////////////////////////////////////////////////
+ イベント
+ //////////////////////////////////////////////////////////////*/
+ // 預金時にトリガー
+ event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
+
+ // 引き出し時にトリガー
+ event Withdraw(
+ address indexed sender,
+ address indexed receiver,
+ address indexed owner,
+ uint256 assets,
+ uint256 shares
+ );
+
+ /*//////////////////////////////////////////////////////////////
+ メタデータ
+ //////////////////////////////////////////////////////////////*/
+ /**
+ * @dev 金庫の基礎資産トークンアドレスを返す(預金、引き出しに使用)
+ * - ERC20トークンコントラクトアドレスである必要があります
+ * - revertしてはいけません
+ */
+ function asset() external view returns (address assetTokenAddress);
+
+ /*//////////////////////////////////////////////////////////////
+ 預金/引き出しロジック
+ //////////////////////////////////////////////////////////////*/
+ /**
+ * @dev 預金関数:ユーザーが金庫にassets単位の基礎資産を預け、その後コントラクトがshares単位の金庫持分をreceiverアドレスに鋳造
+ *
+ * - Depositイベントを発行する必要があります
+ * - 資産が預けられない場合、revertする必要があります(預金数額が上限を大幅に上回る場合など)
+ */
+ function deposit(uint256 assets, address receiver) external returns (uint256 shares);
+
+ /**
+ * @dev 鋳造関数:ユーザーがassets単位の基礎資産を預ける必要があり、その後コントラクトがreceiverアドレスにshare数量の金庫持分を鋳造
+ * - Depositイベントを発行する必要があります
+ * - すべての金庫持分が鋳造できない場合、revertする必要があります(鋳造数額が上限を大幅に上回る場合など)
+ */
+ function mint(uint256 shares, address receiver) external returns (uint256 assets);
+
+ /**
+ * @dev 引き出し関数:ownerアドレスがshare単位の金庫持分を破棄し、その後コントラクトがassets単位の基礎資産をreceiverアドレスに送信
+ * - Withdrawイベントを発行
+ * - すべての基礎資産が引き出せない場合、revert
+ */
+ function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
+
+ /**
+ * @dev 償還関数:ownerアドレスがshares数量の金庫持分を破棄し、その後コントラクトがassets単位の基礎資産をreceiverアドレスに送信
+ * - Withdrawイベントを発行
+ * - 金庫持分がすべて破棄できない場合、revert
+ */
+ function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
+
+ /*//////////////////////////////////////////////////////////////
+ 会計ロジック
+ //////////////////////////////////////////////////////////////*/
+
+ /**
+ * @dev 金庫で管理されている基礎資産トークンの総額を返す
+ * - 利息を含める必要があります
+ * - 手数料を含める必要があります
+ * - revertしてはいけません
+ */
+ function totalAssets() external view returns (uint256 totalManagedAssets);
+
+ /**
+ * @dev 一定数額の基礎資産で交換できる金庫持分を返す
+ * - 手数料を含めないでください
+ * - スリッページを含めません
+ * - revertしてはいけません
+ */
+ function convertToShares(uint256 assets) external view returns (uint256 shares);
+
+ /**
+ * @dev 一定数額の金庫持分で交換できる基礎資産を返す
+ * - 手数料を含めないでください
+ * - スリッページを含めません
+ * - revertしてはいけません
+ */
+ function convertToAssets(uint256 shares) external view returns (uint256 assets);
+
+ /**
+ * @dev 現在のオンチェーン環境で一定数額の基礎資産を預金して得られる金庫持分をオンチェーンとオフチェーンユーザーがシミュレートするため
+ * - 戻り値は同じトランザクションで預金して得られる金庫持分に近く、それを超えてはいけません
+ * - maxDepositなどの制限を考慮せず、ユーザーの預金トランザクションが成功すると仮定
+ * - 手数料を考慮してください
+ * - revertしてはいけません
+ * NOTE: convertToAssetsとpreviewDeposit戻り値の差でスリッページを計算できます
+ */
+ function previewDeposit(uint256 assets) external view returns (uint256 shares);
+
+ /**
+ * @dev 現在のオンチェーン環境でshares数額の金庫持分を鋳造するのに必要な基礎資産数量をオンチェーンとオフチェーンユーザーがシミュレートするため
+ * - 戻り値は同じトランザクションで一定数額の金庫持分を鋳造するのに必要な預金数量に近く、それを下回ってはいけません
+ * - maxMintなどの制限を考慮せず、ユーザーの預金トランザクションが成功すると仮定
+ * - 手数料を考慮してください
+ * - revertしてはいけません
+ */
+ function previewMint(uint256 shares) external view returns (uint256 assets);
+
+ /**
+ * @dev 現在のオンチェーン環境でassets数額の基礎資産を引き出すのに必要な金庫持分をオンチェーンとオフチェーンユーザーがシミュレートするため
+ * - 戻り値は同じトランザクションで一定数額の基礎資産を引き出すのに必要な償還金庫持分に近く、それを超えてはいけません
+ * - maxWithdrawなどの制限を考慮せず、ユーザーの引き出しトランザクションが成功すると仮定
+ * - 手数料を考慮してください
+ * - revertしてはいけません
+ */
+ function previewWithdraw(uint256 assets) external view returns (uint256 shares);
+
+ /**
+ * @dev 現在のオンチェーン環境でshares数額の金庫持分を破棄して償還できる基礎資産数量をオンチェーンとオフチェーンユーザーがシミュレートするため
+ * - 戻り値は同じトランザクションで一定数額の金庫持分を破棄して償還できる基礎資産数量に近く、それを下回ってはいけません
+ * - maxRedeemなどの制限を考慮せず、ユーザーの償還トランザクションが成功すると仮定
+ * - 手数料を考慮してください
+ * - revertしてはいけません
+ */
+ function previewRedeem(uint256 shares) external view returns (uint256 assets);
+
+ /*//////////////////////////////////////////////////////////////
+ 預金/引き出し限度ロジック
+ //////////////////////////////////////////////////////////////*/
+ /**
+ * @dev あるユーザーアドレスの一回の預金で預けられる最大基礎資産数額を返す
+ * - 預金上限がある場合、戻り値は有限値であるべきです
+ * - 戻り値は2 ** 256 - 1を超えてはいけません
+ * - revertしてはいけません
+ */
+ function maxDeposit(address receiver) external view returns (uint256 maxAssets);
+
+ /**
+ * @dev あるユーザーアドレスの一回の鋳造で鋳造できる最大金庫持分を返す
+ * - 鋳造上限がある場合、戻り値は有限値であるべきです
+ * - 戻り値は2 ** 256 - 1を超えてはいけません
+ * - revertしてはいけません
+ */
+ function maxMint(address receiver) external view returns (uint256 maxShares);
+
+ /**
+ * @dev あるユーザーアドレスの一回の引き出しで引き出せる最大基礎資産持分を返す
+ * - 戻り値は有限値であるべきです
+ * - revertしてはいけません
+ */
+ function maxWithdraw(address owner) external view returns (uint256 maxAssets);
+
+ /**
+ * @dev あるユーザーアドレスの一回の償還で破棄できる最大金庫持分を返す
+ * - 戻り値は有限値であるべきです
+ * - 他の制限がない場合、戻り値はbalanceOf(owner)であるべきです
+ * - revertしてはいけません
+ */
+ function maxRedeem(address owner) external view returns (uint256 maxShares);
+}
+```
+
+### ERC4626 コントラクト
+
+以下では、極簡版のトークン化金庫コントラクトを実装します:
+- コンストラクタは基礎資産のコントラクトアドレス、金庫持分のトークン名とシンボルを初期化します。注意:金庫持分のトークン名とシンボルは基礎資産と関連付ける必要があります(例:基礎資産が`WTF`の場合、金庫持分は`vWTF`が良い)。
+- 預金時、ユーザーが金庫に`x`単位の基礎資産を預けると、`x`単位(等量)の金庫持分が鋳造されます。
+- 引き出し時、ユーザーが`x`単位の金庫持分を破棄すると、`x`単位(等量)の基礎資産が引き出されます。
+
+**注意**:実際の使用時には、会計ロジック関連関数の計算が切り上げか切り下げか特に注意する必要があります。[openzeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol)と[solmate](https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)の実装を参考にできます。本節の教学例では考慮しません。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.0;
+
+import {IERC4626} from "./IERC4626.sol";
+import {ERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+/**
+ * @dev ERC4626 "トークン化金庫標準"コントラクト、教学用のみ、本番使用不可
+ */
+contract ERC4626 is ERC20, IERC4626 {
+ /*//////////////////////////////////////////////////////////////
+ 状態変数
+ //////////////////////////////////////////////////////////////*/
+ ERC20 private immutable _asset;
+ uint8 private immutable _decimals;
+
+ constructor(
+ ERC20 asset_,
+ string memory name_,
+ string memory symbol_
+ ) ERC20(name_, symbol_) {
+ _asset = asset_;
+ _decimals = asset_.decimals();
+
+ }
+
+ /** @dev See {IERC4626-asset}. */
+ function asset() public view virtual override returns (address) {
+ return address(_asset);
+ }
+
+ /**
+ * See {IERC20Metadata-decimals}.
+ */
+ function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
+ return _decimals;
+ }
+
+ /*//////////////////////////////////////////////////////////////
+ 預金/引き出しロジック
+ //////////////////////////////////////////////////////////////*/
+ /** @dev See {IERC4626-deposit}. */
+ function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
+ // previewDeposit()を利用して得られる金庫持分を計算
+ shares = previewDeposit(assets);
+
+ // 先transfer後mint、再入攻撃を防ぐ
+ _asset.transferFrom(msg.sender, address(this), assets);
+ _mint(receiver, shares);
+
+ // Depositイベントを発行
+ emit Deposit(msg.sender, receiver, assets, shares);
+ }
+
+ /** @dev See {IERC4626-mint}. */
+ function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
+ // previewMint()を利用して預金が必要な基礎資産数額を計算
+ assets = previewMint(shares);
+
+ // 先transfer後mint、再入攻撃を防ぐ
+ _asset.transferFrom(msg.sender, address(this), assets);
+ _mint(receiver, shares);
+
+ // Depositイベントを発行
+ emit Deposit(msg.sender, receiver, assets, shares);
+
+ }
+
+ /** @dev See {IERC4626-withdraw}. */
+ function withdraw(
+ uint256 assets,
+ address receiver,
+ address owner
+ ) public virtual returns (uint256 shares) {
+ // previewWithdraw()を利用して破棄される金庫持分を計算
+ shares = previewWithdraw(assets);
+
+ // 呼び出し者がownerでない場合、承認をチェックして更新
+ if (msg.sender != owner) {
+ _spendAllowance(owner, msg.sender, shares);
+ }
+
+ // 先破棄後transfer、再入攻撃を防ぐ
+ _burn(owner, shares);
+ _asset.transfer(receiver, assets);
+
+ // Withdrawイベントを発行
+ emit Withdraw(msg.sender, receiver, owner, assets, shares);
+ }
+
+ /** @dev See {IERC4626-redeem}. */
+ function redeem(
+ uint256 shares,
+ address receiver,
+ address owner
+ ) public virtual returns (uint256 assets) {
+ // previewRedeem()を利用して償還できる基礎資産数額を計算
+ assets = previewRedeem(shares);
+
+ // 呼び出し者がownerでない場合、承認をチェックして更新
+ if (msg.sender != owner) {
+ _spendAllowance(owner, msg.sender, shares);
+ }
+
+ // 先破棄後transfer、再入攻撃を防ぐ
+ _burn(owner, shares);
+ _asset.transfer(receiver, assets);
+
+ // Withdrawイベントを発行
+ emit Withdraw(msg.sender, receiver, owner, assets, shares);
+ }
+
+ /*//////////////////////////////////////////////////////////////
+ 会計ロジック
+ //////////////////////////////////////////////////////////////*/
+ /** @dev See {IERC4626-totalAssets}. */
+ function totalAssets() public view virtual returns (uint256){
+ // コントラクト内の基礎資産持有量を返す
+ return _asset.balanceOf(address(this));
+ }
+
+ /** @dev See {IERC4626-convertToShares}. */
+ function convertToShares(uint256 assets) public view virtual returns (uint256) {
+ uint256 supply = totalSupply();
+ // supplyが0の場合、1:1で金庫持分を鋳造
+ // supplyが0でない場合、比例して鋳造
+ return supply == 0 ? assets : assets * supply / totalAssets();
+ }
+
+ /** @dev See {IERC4626-convertToAssets}. */
+ function convertToAssets(uint256 shares) public view virtual returns (uint256) {
+ uint256 supply = totalSupply();
+ // supplyが0の場合、1:1で基礎資産を償還
+ // supplyが0でない場合、比例して償還
+ return supply == 0 ? shares : shares * totalAssets() / supply;
+ }
+
+ /** @dev See {IERC4626-previewDeposit}. */
+ function previewDeposit(uint256 assets) public view virtual returns (uint256) {
+ return convertToShares(assets);
+ }
+
+ /** @dev See {IERC4626-previewMint}. */
+ function previewMint(uint256 shares) public view virtual returns (uint256) {
+ return convertToAssets(shares);
+ }
+
+ /** @dev See {IERC4626-previewWithdraw}. */
+ function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
+ return convertToShares(assets);
+ }
+
+ /** @dev See {IERC4626-previewRedeem}. */
+ function previewRedeem(uint256 shares) public view virtual returns (uint256) {
+ return convertToAssets(shares);
+ }
+
+ /*//////////////////////////////////////////////////////////////
+ 預金/引き出し限度ロジック
+ //////////////////////////////////////////////////////////////*/
+ /** @dev See {IERC4626-maxDeposit}. */
+ function maxDeposit(address) public view virtual returns (uint256) {
+ return type(uint256).max;
+ }
+
+ /** @dev See {IERC4626-maxMint}. */
+ function maxMint(address) public view virtual returns (uint256) {
+ return type(uint256).max;
+ }
+
+ /** @dev See {IERC4626-maxWithdraw}. */
+ function maxWithdraw(address owner) public view virtual returns (uint256) {
+ return convertToAssets(balanceOf(owner));
+ }
+
+ /** @dev See {IERC4626-maxRedeem}. */
+ function maxRedeem(address owner) public view virtual returns (uint256) {
+ return balanceOf(owner);
+ }
+}
+```
+
+もちろん、本文の`ERC4626`コントラクトは教学デモンストレーション用のみで、実際の使用時には`Inflation Attack`、`Rounding Direction`などの問題も考慮する必要があります。本番では、`openzeppelin`の具体的な実装を使用することをお勧めします。
+
+## `Remix`デモ
+
+**注意:** 以下の実行例ではremixの第二アカウント、つまり`0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2`を使用してコントラクトをデプロイし、コントラクトメソッドを呼び出します。
+
+1. `ERC20`トークンコントラクトをデプロイし、トークン名とシンボルを共に`WTF`に設定し、自分に`10000`トークンを鋳造。
+
+
+
+2. `ERC4626`トークンコントラクトをデプロイし、基礎資産のコントラクトアドレスを`WTF`のアドレスに設定し、名前とシンボルを共に`vWTF`に設定。
+
+
+3. `ERC20`コントラクトの`approve()`関数を呼び出し、トークンを`ERC4626`コントラクトに承認。
+
+
+4. `ERC4626`コントラクトの`deposit()`関数を呼び出し、`1000`枚のトークンを預金。その後`balanceOf()`関数を呼び出し、自分の金庫持分が`1000`になったことを確認。
+
+
+5. `ERC4626`コントラクトの`mint()`関数を呼び出し、`1000`枚のトークンを預金。その後`balanceOf()`関数を呼び出し、自分の金庫持分が`2000`になったことを確認。
+
+
+6. `ERC4626`コントラクトの`withdraw()`関数を呼び出し、`1000`枚のトークンを引き出し。その後`balanceOf()`関数を呼び出し、自分の金庫持分が`1000`になったことを確認。
+
+
+7. `ERC4626`コントラクトの`redeem()`関数を呼び出し、`1000`枚のトークンを引き出し。その後`balanceOf()`関数を呼び出し、自分の金庫持分が`0`になったことを確認。
+
+
+## まとめ
+
+今回は、トークン化金庫標準ERC4626について紹介し、基礎資産を1:1で金庫持分トークンに変換できるシンプルな金庫コントラクトを作成しました。ERC4626はDeFiの流動性と組み合わせ可能性を向上させ、今後徐々に普及していくでしょう。あなたはERC4626でどのようなアプリケーションを作りますか?
\ No newline at end of file
diff --git a/Languages/ja/52_EIP712_ja/readme.md b/Languages/ja/52_EIP712_ja/readme.md
new file mode 100644
index 000000000..0386484a1
--- /dev/null
+++ b/Languages/ja/52_EIP712_ja/readme.md
@@ -0,0 +1,212 @@
+---
+title: 52. EIP712 型付きデータ署名
+tags:
+ - solidity
+ - erc20
+ - eip712
+ - openzepplin
+---
+
+# WTF Solidity 超シンプル入門: 52. EIP712 型付きデータ署名
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、より高度で安全な署名方法である EIP712 型付きデータ署名について紹介します。
+
+## EIP712
+
+以前、[EIP191 署名標準(personal sign)](https://github.com/AmazingAng/WTF-Solidity/blob/main/37_Signature/readme.md) について紹介しました。これはメッセージに署名することができますが、過度にシンプルです。署名データが複雑な場合、ユーザーは十六進文字列(データのハッシュ)しか見ることができず、署名内容が期待通りかどうかを確認することができません。
+
+
+
+[EIP712型付きデータ署名](https://eips.ethereum.org/EIPS/eip-712)は、より高度で安全な署名方法です。EIP712 に対応した Dapp が署名を要求すると、ウォレットは署名メッセージの生データを表示し、ユーザーはデータが期待通りであることを確認してから署名することができます。
+
+
+
+## EIP712 の使用方法
+
+EIP712 の応用は一般的にオフチェーン署名(フロントエンドまたはスクリプト)とオンチェーン検証(コントラクト)の2つの部分を含みます。以下では、シンプルな例 `EIP712Storage` を使って EIP712 の使用方法を紹介します。`EIP712Storage` コントラクトには状態変数 `number` があり、EIP712 署名を検証してから変更することができます。
+
+### オフチェーン署名
+
+1. EIP712 署名には必ず `EIP712Domain` 部分が含まれている必要があります。これにはコントラクトの name、version(一般的に「1」と決められています)、chainId、および verifyingContract(署名を検証するコントラクトアドレス)が含まれます。
+
+ ```js
+ EIP712Domain: [
+ { name: "name", type: "string" },
+ { name: "version", type: "string" },
+ { name: "chainId", type: "uint256" },
+ { name: "verifyingContract", type: "address" },
+ ]
+ ```
+
+ これらの情報はユーザーが署名する際に表示され、特定のチェーンの特定のコントラクトのみが署名を検証できることを保証します。スクリプトで対応するパラメータを渡す必要があります。
+
+ ```js
+ const domain = {
+ name: "EIP712Storage",
+ version: "1",
+ chainId: "1",
+ verifyingContract: "0xf8e81D47203A594245E36C48e151709F0C19fBe8",
+ };
+ ```
+
+2. 使用場面に応じて署名のデータ型をカスタマイズする必要があり、コントラクトとマッチする必要があります。`EIP712Storage` の例では、`Storage` 型を定義しました。これには2つのメンバーがあります:`address` 型の `spender`(変数を修正できる呼び出し者を指定)、`uint256` 型の `number`(変数修正後の値を指定)。
+
+ ```js
+ const types = {
+ Storage: [
+ { name: "spender", type: "address" },
+ { name: "number", type: "uint256" },
+ ],
+ };
+ ```
+
+3. `message` 変数を作成し、署名される型付きデータを渡します。
+
+ ```js
+ const message = {
+ spender: "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
+ number: "100",
+ };
+ ```
+
+ 
+
+4. ウォレットオブジェクトの `signTypedData()` メソッドを呼び出し、前の手順の `domain`、`types`、`message` 変数を渡して署名します(ここでは `ethersjs v6` を使用)。
+
+ ```js
+ // providerを取得
+ const provider = new ethers.BrowserProvider(window.ethereum)
+ // signerを取得してsignTypedDataメソッドを呼び出してeip712署名を行う
+ const signature = await signer.signTypedData(domain, types, message);
+ console.log("Signature:", signature);
+ ```
+
+ 
+
+### オンチェーン検証
+
+次は `EIP712Storage` コントラクトの部分です。署名を検証し、通過すれば `number` 状態変数を修正します。5つの状態変数があります。
+
+1. `EIP712DOMAIN_TYPEHASH`: `EIP712Domain` の型ハッシュ、定数。
+2. `STORAGE_TYPEHASH`: `Storage` の型ハッシュ、定数。
+3. `DOMAIN_SEPARATOR`: 各ドメイン(Dapp)の署名に混合されるユニークな値で、`EIP712DOMAIN_TYPEHASH` および `EIP712Domain`(name、version、chainId、verifyingContract)から構成され、`constructor()` で初期化されます。
+4. `number`: コントラクト内の保存値の状態変数で、`permitStore()` メソッドで修正できます。
+5. `owner`: コントラクトの所有者で、`constructor()` で初期化され、`permitStore()` メソッドで署名の有効性を検証します。
+
+また、`EIP712Storage` コントラクトには3つの関数があります。
+
+1. コンストラクタ: `DOMAIN_SEPARATOR` と `owner` を初期化します。
+2. `retrieve()`: `number` の値を読み取ります。
+3. `permitStore`: EIP712 署名を検証し、`number` の値を修正します。まず、署名を `r`、`s`、`v` に分解します。次に `DOMAIN_SEPARATOR`、`STORAGE_TYPEHASH`、呼び出し者アドレス、入力された `_num` パラメータを使って署名のメッセージテキスト `digest` を構築します。最後に `ECDSA` の `recover()` メソッドを使って署名者アドレスを復元し、署名が有効であれば `number` の値を更新します。
+
+```solidity
+// SPDX-License-Identifier: MIT
+// By 0xAA
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
+
+contract EIP712Storage {
+ using ECDSA for bytes32;
+
+ bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
+ bytes32 private constant STORAGE_TYPEHASH = keccak256("Storage(address spender,uint256 number)");
+ bytes32 private DOMAIN_SEPARATOR;
+ uint256 number;
+ address owner;
+
+ constructor(){
+ DOMAIN_SEPARATOR = keccak256(abi.encode(
+ EIP712DOMAIN_TYPEHASH, // 型ハッシュ
+ keccak256(bytes("EIP712Storage")), // name
+ keccak256(bytes("1")), // version
+ block.chainid, // chain id
+ address(this) // コントラクトアドレス
+ ));
+ owner = msg.sender;
+ }
+
+ /**
+ * @dev 変数に値を保存
+ */
+ function permitStore(uint256 _num, bytes memory _signature) public {
+ // 署名の長さをチェック、65は標準のr,s,v署名の長度
+ require(_signature.length == 65, "invalid signature length");
+ bytes32 r;
+ bytes32 s;
+ uint8 v;
+ // 現在、assembly(インラインアセンブリ)のみで署名からr,s,vの値を取得可能
+ assembly {
+ /*
+ 最初の32bytesは署名の長さを保存(動的配列の保存規則)
+ add(sig, 32) = sigのポインタ + 32
+ signatureの最初の32bytesをスキップすることと同等
+ mload(p) メモリアドレスpから開始する次の32bytesのデータをロード
+ */
+ // 長さデータ後の32bytesを読み取る
+ r := mload(add(_signature, 0x20))
+ // その後の32bytesを読み取る
+ s := mload(add(_signature, 0x40))
+ // 最後の1byteを読み取る
+ v := byte(0, mload(add(_signature, 0x60)))
+ }
+
+ // 署名メッセージハッシュを取得
+ bytes32 digest = keccak256(abi.encodePacked(
+ "\x19\x01",
+ DOMAIN_SEPARATOR,
+ keccak256(abi.encode(STORAGE_TYPEHASH, msg.sender, _num))
+ ));
+
+ address signer = digest.recover(v, r, s); // 署名者を復元
+ require(signer == owner, "EIP712Storage: Invalid signature"); // 署名をチェック
+
+ // 状態変数を修正
+ number = _num;
+ }
+
+ /**
+ * @dev 値を返す
+ * @return 'number'の値
+ */
+ function retrieve() public view returns (uint256){
+ return number;
+ }
+}
+```
+
+## デプロイと復現
+
+1. `Remix` で `EIP712Storage` コントラクトをデプロイします。
+
+2. `eip712storage.html` を実行します。ブラウザのコンテンツセキュリティポリシー([Content Security Policy](https://github.com/MetaMask/faq/blob/9257d7d52784afa957c12166aff20682cf692ae5/DEVELOPERS.md#requirements-nut_and_bolt))の要件により、MetaMaskはローカルファイル(file:// プロトコル)を開いてDAppと通信することができません。Node静的ファイルサーバー `http-server` を使用してローカルサービスを開始し、`eip712storage.html` ファイルが含まれるディレクトリで以下のコマンドを実行します:
+
+ ```sh
+ npm install -g http-server
+ http-server
+ ```
+
+ ブラウザで `http://127.0.0.1:8080` を開くとアクセスできます。その後、`Contract Address` をデプロイした `EIP712Storage` コントラクトアドレスに変更し、順番に `Connect Metamask` と `Sign Permit` ボタンをクリックして署名します。署名にはコントラクトをデプロイしたウォレットを使用する必要があります。例えば Remix テストウォレット:
+
+ ```js
+ public_key: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+ private_key: 503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb
+ ```
+
+3. コントラクトの `permitStore()` メソッドを呼び出し、対応する `_num` と署名を入力して `number` の値を修正します。
+
+4. コントラクトの `retrieve()` メソッドを呼び出すと、`number` の値が変更されていることがわかります。
+
+## まとめ
+
+今回は、EIP712 型付きデータ署名について紹介しました。これはより高度で安全な署名標準です。署名を要求する際、ウォレットは署名メッセージの生データを表示し、ユーザーはデータを検証してから署名することができます。この標準は広く応用されており、Metamask、Uniswap token pairs、DAI stablecoin などのシナリオで使用されています。皆さんによく習得していただきたいと思います。
\ No newline at end of file
diff --git a/Languages/ja/53_ERC20Permit_ja/readme.md b/Languages/ja/53_ERC20Permit_ja/readme.md
new file mode 100644
index 000000000..eb523e63b
--- /dev/null
+++ b/Languages/ja/53_ERC20Permit_ja/readme.md
@@ -0,0 +1,211 @@
+---
+title: 53. ERC-2612 ERC20Permit
+tags:
+ - solidity
+ - erc20
+ - eip712
+ - openzepplin
+---
+
+# WTF Solidity 超シンプル入門: 53. ERC-2612 ERC20Permit
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、ERC20 トークンの拡張である ERC20Permit について紹介します。これは署名を使用した承認をサポートし、ユーザーエクスペリエンスを改善します。EIP-2612 で提案され、イーサリアム標準に組み込まれ、`USDC`、`ARB` などのトークンで使用されています。
+
+## ERC20
+
+[31講](https://github.com/AmazingAng/WTF-Solidity/blob/main/31_ERC20/readme.md)でERC20について紹介しました。これはイーサリアムで最も人気のあるトークン標準です。人気の主な理由の一つは、`approve` と `transferFrom` の2つの関数を組み合わせて使用することで、トークンが外部所有アカウント(EOA)間での転送だけでなく、他のコントラクトでも使用できることです。
+
+しかし、ERC20の `approve` 関数はトークン所有者のみが呼び出すことができるという制限があります。これは、すべての `ERC20` トークンの初期操作が `EOA` によって実行される必要があることを意味します。例えば、ユーザーAが分散型取引所で `USDT` を `ETH` と交換する場合、2つのトランザクションを完了する必要があります:最初にユーザーAが `approve` を呼び出して `USDT` をコントラクトに承認し、次にユーザーAがコントラクトを呼び出して交換を行います。非常に面倒で、ユーザーはトランザクションのガス代を支払うために `ETH` を持っている必要があります。
+
+## ERC20Permit
+
+EIP-2612は ERC20Permit を提案し、ERC20標準を拡張して `permit` 関数を追加しました。これにより、ユーザーは `msg.sender` ではなく EIP-712 署名を通じて承認を修正できます。これには2つの利点があります:
+
+1. 承認のステップはユーザーのオフチェーン署名のみが必要で、1つのトランザクションを削減できます。
+2. 署名後、ユーザーは第三者に後続のトランザクションを委託でき、ETHを持つ必要がありません:ユーザーAは署名をガスを持つ第三者Bに送信し、Bに後続のトランザクションの実行を委託できます。
+
+
+
+## コントラクト
+
+### IERC20Permit インターフェースコントラクト
+
+まず、ERC20Permit のインターフェースコントラクトについて学びましょう。これは3つの関数を定義しています:
+
+- `permit()`: `owner` の署名に基づいて、`owner` のERC20トークン残高を `spender` に承認し、数量は `value` です。要件:
+
+ - `spender` はゼロアドレスであってはいけません。
+ - `deadline` は将来のタイムスタンプである必要があります。
+ - `v`、`r`、`s` は `owner` による関数パラメータのEIP712形式の有効な `keccak256` 署名である必要があります。
+ - 署名は `owner` の現在のnonceを使用する必要があります。
+
+- `nonces()`: `owner` の現在のnonceを返します。`permit()` 関数の署名を生成するたびに、この値を含める必要があります。`permit()` 関数の呼び出しが成功するたびに、`owner` のnonceが1増加し、同じ署名の再利用を防ぎます。
+
+- `DOMAIN_SEPARATOR()`: [EIP712](https://github.com/AmazingAng/WTF-Solidity/blob/main/52_EIP712/readme.md) で定義された通り、`permit()` 関数の署名をエンコードするために使用されるドメインセパレータを返します。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/**
+ * @dev ERC20 Permit拡張のインターフェース、https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]で定義された署名による承認を許可
+ */
+interface IERC20Permit {
+ /**
+ * @dev ownerの署名に基づいて、`owner`のERC20残高を`spender`に承認、数量は`value`
+ */
+ function permit(
+ address owner,
+ address spender,
+ uint256 value,
+ uint256 deadline,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) external;
+
+ /**
+ * @dev `owner`の現在のnonceを返す。{permit}の署名を生成する際に、この値を含める必要がある。
+ */
+ function nonces(address owner) external view returns (uint256);
+
+ /**
+ * @dev {permit}の署名をエンコードするために使用されるドメインセパレータを返す
+ */
+ // solhint-disable-next-line func-name-mixedcase
+ function DOMAIN_SEPARATOR() external view returns (bytes32);
+}
+```
+
+### ERC20Permit コントラクト
+
+次に、シンプルなERC20Permitコントラクトを書きます。これはIERC20Permitで定義されたすべてのインターフェースを実装します。コントラクトには2つの状態変数が含まれています:
+
+- `_nonces`: `address -> uint` のマッピング、すべてのユーザーの現在のnonce値を記録します。
+- `_PERMIT_TYPEHASH`: 定数、`permit()` 関数の型ハッシュを記録します。
+
+コントラクトには5つの関数が含まれています:
+
+- コンストラクタ: トークンの `name` と `symbol` を初期化します。
+- **`permit()`**: ERC20Permitの最も核心的な関数で、IERC20Permitの `permit()` を実装します。まず署名が期限切れかどうかをチェックし、次に `_PERMIT_TYPEHASH`、`owner`、`spender`、`value`、`nonce`、`deadline` を使って署名メッセージを復元し、署名が有効かどうかを検証します。署名が有効であれば、ERC20の `_approve()` 関数を呼び出して承認操作を行います。
+- `nonces()`: IERC20Permitの `nonces()` 関数を実装します。
+- `DOMAIN_SEPARATOR()`: IERC20Permitの `DOMAIN_SEPARATOR()` 関数を実装します。
+- `_useNonce()`: `nonce` を消費する関数で、ユーザーの現在の `nonce` を返し、1増加させます。
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.0;
+
+import "./IERC20Permit.sol";
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
+import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
+
+/**
+ * @dev ERC20 Permit拡張のインターフェース、https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]で定義された署名による承認を許可。
+ *
+ * {permit}メソッドを追加し、アカウント署名されたメッセージによってアカウントのERC20残高({IERC20-allowance}を参照)を変更可能。{IERC20-approve}に依存しないため、トークンホルダーのアカウントはトランザクションを送信する必要がなく、Etherを全く持つ必要がない。
+ */
+contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
+ mapping(address => uint) private _nonces;
+
+ bytes32 private constant _PERMIT_TYPEHASH =
+ keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
+
+ /**
+ * @dev EIP712のnameおよびERC20のnameとsymbolを初期化
+ */
+ constructor(string memory name, string memory symbol) EIP712(name, "1") ERC20(name, symbol){}
+
+ /**
+ * @dev {IERC20Permit-permit}を参照。
+ */
+ function permit(
+ address owner,
+ address spender,
+ uint256 value,
+ uint256 deadline,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) public virtual override {
+ // deadlineをチェック
+ require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
+
+ // ハッシュを構築
+ bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
+ bytes32 hash = _hashTypedDataV4(structHash);
+
+ // 署名とメッセージからsignerを計算し、署名を検証
+ address signer = ECDSA.recover(hash, v, r, s);
+ require(signer == owner, "ERC20Permit: invalid signature");
+
+ // 承認
+ _approve(owner, spender, value);
+ }
+
+ /**
+ * @dev {IERC20Permit-nonces}を参照。
+ */
+ function nonces(address owner) public view virtual override returns (uint256) {
+ return _nonces[owner];
+ }
+
+ /**
+ * @dev {IERC20Permit-DOMAIN_SEPARATOR}を参照。
+ */
+ function DOMAIN_SEPARATOR() external view override returns (bytes32) {
+ return _domainSeparatorV4();
+ }
+
+ /**
+ * @dev "nonceを消費": `owner`の現在の`nonce`を返し、1増加させる。
+ */
+ function _useNonce(address owner) internal virtual returns (uint256 current) {
+ current = _nonces[owner];
+ _nonces[owner] += 1;
+ }
+}
+```
+
+## Remix 復現
+
+1. `ERC20Permit` コントラクトをデプロイし、`name` と `symbol` をともに `WTFPermit` に設定します。
+
+2. `signERC20Permit.html` を実行し、`Contract Address` をデプロイした `ERC20Permit` コントラクトアドレスに変更し、その他の情報は以下で提供します。その後、順番に `Connect Metamask` と `Sign Permit` ボタンをクリックして署名し、コントラクト検証のために `r`、`s`、`v` を取得します。署名にはコントラクトをデプロイしたウォレットを使用する必要があります。例えば Remix テストウォレット:
+
+ ```js
+ owner: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 spender: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
+ value: 100
+ deadline: 115792089237316195423570985008687907853269984665640564039457584007913129639935
+ private_key: 503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb
+ ```
+
+
+
+3. コントラクトの `permit()` メソッドを呼び出し、対応するパラメータを入力して承認を行います。
+
+4. コントラクトの `allowance()` メソッドを呼び出し、対応する `owner` と `spender` を入力すると、承認が成功したことがわかります。
+
+## セキュリティに関する注意
+
+ERC20Permitはオフチェーン署名による承認でユーザーに利便性をもたらしましたが、同時にリスクも生じました。一部のハッカーはこの特性を利用してフィッシング攻撃を行い、ユーザーの署名を騙し取って資産を盗みます。2023年4月のUSDCに対する署名[フィッシング攻撃](https://twitter.com/0xAA_Science/status/1652880488095440897?s=20)では、あるユーザーが228万Uの資産を失いました。
+
+**署名時は、署名内容を慎重に読むことが重要です!**
+
+同時に、一部のコントラクトが`permit`を統合する際にも、DoS(サービス拒否)のリスクをもたらします。`permit`は実行時に現在の`nonce`値を使用するため、コントラクトの関数に`permit`操作が含まれている場合、攻撃者は先行実行で`permit`を実行し、`nonce`が占有されるため目標トランザクションがロールバックされることがあります。
+
+## まとめ
+
+今回は、ERC20PermitというERC20トークン標準の拡張について紹介しました。これにより、ユーザーはオフチェーン署名による承認操作を使用でき、ユーザーエクスペリエンスが改善され、多くのプロジェクトで採用されています。しかし同時に、より大きなリスクももたらし、一つの署名であなたの資産が持ち去られる可能性があります。署名時にはより慎重になることが重要です。
\ No newline at end of file
diff --git a/Languages/ja/54_CrossChainBridge_ja/readme.md b/Languages/ja/54_CrossChainBridge_ja/readme.md
new file mode 100644
index 000000000..f522710b1
--- /dev/null
+++ b/Languages/ja/54_CrossChainBridge_ja/readme.md
@@ -0,0 +1,196 @@
+---
+title: 54. クロスチェーンブリッジ
+tags:
+ - solidity
+ - erc20
+ - eip712
+ - openzepplin
+---
+
+# WTF Solidity 超シンプル入門: 54. クロスチェーンブリッジ
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、資産をあるブロックチェーンから別のブロックチェーンに転送できる基盤インフラであるクロスチェーンブリッジについて紹介し、シンプルなクロスチェーンブリッジを実装します。
+
+## 1. クロスチェーンブリッジとは
+
+クロスチェーンブリッジは、2つ以上のブロックチェーン間でデジタル資産と情報を移動できるブロックチェーンプロトコルです。例えば、イーサリアムメインネット上で動作するERC20トークンは、クロスチェーンブリッジを通じて他のイーサリアム互換サイドチェーンや独立チェーンに転送できます。
+
+同時に、クロスチェーンブリッジはブロックチェーンでネイティブにサポートされているわけではなく、クロスチェーン操作には信頼できる第三者が実行する必要があり、これもリスクをもたらします。近年、クロスチェーンブリッジに対する攻撃により、すでに**20億ドル**を超えるユーザー資産の損失が発生しています。
+
+## 2. クロスチェーンブリッジの種類
+
+クロスチェーンブリッジには主に以下の3つのタイプがあります:
+
+- **Burn/Mint**: ソースチェーンでトークンを燃焼(burn)し、ターゲットチェーンで同等の数量のトークンを作成(mint)します。この方法の利点は、トークンの総供給量が変わらないことですが、クロスチェーンブリッジがトークンの鋳造権限を持つ必要があり、プロジェクト側が独自のクロスチェーンブリッジを構築するのに適しています。
+
+ 
+
+- **Stake/Mint**: ソースチェーンでトークンをロック(stake)し、ターゲットチェーンで同等の数量のトークン(証明書)を作成(mint)します。ソースチェーンのトークンはロックされ、トークンがターゲットチェーンからソースチェーンに戻される際に再びアンロックされます。これは一般的なクロスチェーンブリッジで使用される方案で、権限は必要ありませんが、リスクも大きく、ソースチェーンの資産がハッカーに攻撃された場合、ターゲットチェーン上の証明書は価値のないものになります。
+
+ 
+
+- **Stake/Unstake**: ソースチェーンでトークンをロック(stake)し、ターゲットチェーンで同等の数量のトークンを解放(unstake)します。ターゲットチェーン上のトークンはいつでもソースチェーンのトークンと交換できます。この方法では、クロスチェーンブリッジが両方のチェーンでロックされたトークンを持つ必要があり、閾値が高く、一般的にユーザーがクロスチェーンブリッジでトークンをロックするインセンティブが必要です。
+
+ 
+
+## 3. シンプルなクロスチェーンブリッジの構築
+
+このクロスチェーンブリッジをより良く理解するため、シンプルなクロスチェーンブリッジを構築し、GoerliテストネットとSepoliaテストネット間でのERC20トークン転送を実装します。burn/mint方式を使用し、ソースチェーン上のトークンが破棄され、ターゲットチェーン上で作成されます。このクロスチェーンブリッジは、スマートコントラクト(両方のチェーンにデプロイ)とEthers.jsスクリプトで構成されます。
+
+> **注意してください**、これは非常にシンプルなクロスチェーンブリッジの実装で、教育目的のみです。トランザクション失敗、チェーンの再編成などの可能性のある問題を処理していません。本番環境では、専門的なクロスチェーンブリッジソリューションまたは他の十分にテストされ、監査されたフレームワークの使用を推奨します。
+
+### 3.1 クロスチェーントークンコントラクト
+
+まず、GoerliとSepoliaテストネットにERC20トークンコントラクト `CrossChainToken` をデプロイする必要があります。このコントラクトでは、トークンの名前、シンボル、総供給量を定義し、クロスチェーン転送用の `bridge()` 関数があります。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+contract CrossChainToken is ERC20, Ownable {
+
+ // Bridgeイベント
+ event Bridge(address indexed user, uint256 amount);
+ // Mintイベント
+ event Mint(address indexed to, uint256 amount);
+
+ /**
+ * @param name トークン名
+ * @param symbol トークンシンボル
+ * @param totalSupply トークン供給量
+ */
+ constructor(
+ string memory name,
+ string memory symbol,
+ uint256 totalSupply
+ ) payable ERC20(name, symbol) Ownable(msg.sender) {
+ _mint(msg.sender, totalSupply);
+ }
+
+ /**
+ * Bridge関数
+ * @param amount: 現在のチェーンでburnし、他のチェーンでmintするトークン数量
+ */
+ function bridge(uint256 amount) public {
+ _burn(msg.sender, amount);
+ emit Bridge(msg.sender, amount);
+ }
+
+ /**
+ * Mint関数
+ */
+ function mint(address to, uint amount) external onlyOwner {
+ _mint(to, amount);
+ emit Mint(to, amount);
+ }
+}
+```
+
+このコントラクトには3つの主要な関数があります:
+
+- `constructor()`: コンストラクタで、コントラクトデプロイ時に一度呼び出され、トークンの名前、シンボル、総供給量を初期化します。
+
+- `bridge()`: ユーザーがこの関数を呼び出してクロスチェーン転送を行います。指定された数量のトークンを破棄し、`Bridge`イベントを発行します。
+
+- `mint()`: コントラクトの所有者のみが呼び出せる関数で、クロスチェーンイベントを処理し、`Mint`イベントを発行します。ユーザーが別のチェーンで`bridge()`関数を呼び出してトークンを破棄すると、スクリプトが`Bridge`イベントを監視し、ユーザーにターゲットチェーンでトークンを鋳造します。
+
+### 3.2 クロスチェーンスクリプト
+
+トークンコントラクトの後、クロスチェーンイベントを処理するサーバーが必要です。ethers.jsスクリプト(v6版)を書いて`Bridge`イベントを監視し、イベントがトリガーされた際にターゲットチェーンで同数のトークンを作成できます。Ethers.jsについて詳しくない場合は、[WTF Ethers極簡教程](https://github.com/WTFAcademy/WTF-Ethers)を読むことができます。
+
+```javascript
+import { ethers } from "ethers";
+
+// 2つのチェーンのproviderを初期化
+const providerGoerli = new ethers.JsonRpcProvider("Goerli_Provider_URL");
+const providerSepolia = new ethers.JsonRpcProvider("Sepolia_Provider_URL://eth-sepolia.g.alchemy.com/v2/RgxsjQdKTawszh80TpJ-14Y8tY7cx5W2");
+
+// 2つのチェーンのsignerを初期化
+// privateKeyに管理者ウォレットの秘密鍵を入力
+const privateKey = "Your_Key";
+const walletGoerli = new ethers.Wallet(privateKey, providerGoerli);
+const walletSepolia = new ethers.Wallet(privateKey, providerSepolia);
+
+// コントラクトアドレスとABI
+const contractAddressGoerli = "0xa2950F56e2Ca63bCdbA422c8d8EF9fC19bcF20DD";
+const contractAddressSepolia = "0xad20993E1709ed13790b321bbeb0752E50b8Ce69";
+
+const abi = [
+ "event Bridge(address indexed user, uint256 amount)",
+ "function bridge(uint256 amount) public",
+ "function mint(address to, uint amount) external",
+];
+
+// コントラクトインスタンスを初期化
+const contractGoerli = new ethers.Contract(contractAddressGoerli, abi, walletGoerli);
+const contractSepolia = new ethers.Contract(contractAddressSepolia, abi, walletSepolia);
+
+const main = async () => {
+ try{
+ console.log(`クロスチェーンイベントの監視を開始`)
+
+ // chain SepoliaのBridgeイベントを監視し、Goerli上でmint操作を実行してクロスチェーンを完了
+ contractSepolia.on("Bridge", async (user, amount) => {
+ console.log(`Chain SepoliaでBridgeイベント: ユーザー ${user} が ${amount} トークンをburn`);
+
+ // Goerli上でmint操作を実行
+ let tx = await contractGoerli.mint(user, amount);
+ await tx.wait();
+
+ console.log(`Chain Goerliで ${user} に ${amount} トークンをmint`);
+ });
+
+ // chain GoerliのBridgeイベントを監視し、Sepolia上でmint操作を実行してクロスチェーンを完了
+ contractGoerli.on("Bridge", async (user, amount) => {
+ console.log(`Chain GoerliでBridgeイベント: ユーザー ${user} が ${amount} トークンをburn`);
+
+ // Sepolia上でmint操作を実行
+ let tx = await contractSepolia.mint(user, amount);
+ await tx.wait();
+
+ console.log(`Chain Sepoliaで ${user} に ${amount} トークンをmint`);
+ });
+ } catch(e) {
+ console.log(e);
+ }
+}
+
+main();
+```
+
+## Remix復現
+
+1. GoerliとSepoliaテストチェーンでそれぞれ`CrossChainToken`コントラクトをデプロイし、コントラクトが自動的に10000枚のトークンを鋳造します
+
+ 
+
+2. クロスチェーンスクリプト `crosschain.js` のRPCノードURLと管理者秘密鍵を補完し、GoerliとSepoliaにデプロイしたトークンコントラクトアドレスを対応する場所に記入し、スクリプトを実行します。
+
+3. Goerliチェーン上のトークンコントラクトの`bridge()`関数を呼び出し、100枚のトークンをクロスチェーンします。
+
+ 
+
+4. スクリプトがクロスチェーンイベントを監視し、Sepoliaチェーン上で100枚のトークンを鋳造します。
+
+ 
+
+5. Sepoliaチェーン上で`balance()`を呼び出して残高を確認すると、トークン残高が10100枚になり、クロスチェーンが成功しました!
+
+ 
+
+## まとめ
+
+今回はクロスチェーンブリッジについて紹介しました。これは2つ以上のブロックチェーン間でデジタル資産と情報を移動できるもので、ユーザーがマルチチェーンで資産を操作する際の利便性を提供します。同時に、大きなリスクもあり、近年クロスチェーンブリッジに対する攻撃により、すでに**20億ドル**を超えるユーザー資産の損失が発生しています。本チュートリアルでは、シンプルなクロスチェーンブリッジを構築し、GoerliテストネットとSepoliaテストネット間でのERC20トークン転送を実装しました。このチュートリアルを通じて、クロスチェーンブリッジについてより深く理解していただけると信じています。
\ No newline at end of file
diff --git a/Languages/ja/55_MultiCall_ja/readme.md b/Languages/ja/55_MultiCall_ja/readme.md
new file mode 100644
index 000000000..4be8c0236
--- /dev/null
+++ b/Languages/ja/55_MultiCall_ja/readme.md
@@ -0,0 +1,135 @@
+---
+title: 55. マルチコール
+tags:
+ - solidity
+ - erc20
+---
+
+# WTF Solidity 超シンプル入門: 55. マルチコール
+
+最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。
+
+僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+今回は、MultiCall マルチコールコントラクトについて紹介します。その設計目的は、1つのトランザクションで複数の関数呼び出しを実行することで、トランザクション手数料を大幅に削減し、効率性を向上させることです。
+
+## MultiCall
+
+Solidityにおいて、MultiCall(マルチコール)コントラクトの設計により、1つのトランザクションで複数の関数呼び出しを実行できます。その利点は以下の通りです:
+
+1. 利便性:MultiCallにより、1つのトランザクションで異なるコントラクトの異なる関数を異なるパラメータで呼び出すことができます。例えば、複数のアドレスのERC20トークン残高を一度にクエリできます。
+
+2. ガス節約:MultiCallは複数のトランザクションを1つのトランザクション内の複数の呼び出しに統合し、ガスを節約できます。
+
+3. 原子性:MultiCallにより、ユーザーは1つのトランザクションですべての操作を実行でき、すべての操作が成功するか、すべて失敗するかを保証し、原子性を維持します。例えば、特定の順序で一連のトークン取引を行うことができます。
+
+## MultiCall コントラクト
+
+次に、MultiCallコントラクトを一緒に研究しましょう。これは MakerDAO の [MultiCall](https://github.com/mds1/multicall/blob/main/src/Multicall3.sol) を簡略化したものです。
+
+MultiCall コントラクトは2つの構造体を定義しています:
+
+- `Call`: これは呼び出し構造体で、呼び出すターゲットコントラクト `target`、呼び出し失敗を許可するかどうかを示すフラグ `allowFailure`、呼び出すバイトコード `call data` を含みます。
+
+- `Result`: これは結果構造体で、呼び出しが成功したかどうかを示すフラグ `success` と呼び出しが返すバイトコード `return data` を含みます。
+
+このコントラクトにはマルチコールを実行する関数が1つだけ含まれています:
+
+- `multicall()`: この関数のパラメータはCall構造体で構成される配列で、targetとdataの長さが一致することを保証します。関数はループを通じて複数の呼び出しを実行し、呼び出しが失敗した際にトランザクションをロールバックします。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+contract Multicall {
+ // Call構造体、ターゲットコントラクトtarget、呼び出し失敗を許可するかallowFailure、call dataを含む
+ struct Call {
+ address target;
+ bool allowFailure;
+ bytes callData;
+ }
+
+ // Result構造体、呼び出しが成功したかとreturn dataを含む
+ struct Result {
+ bool success;
+ bytes returnData;
+ }
+
+ /// @notice 複数の呼び出し(異なるコントラクト/異なるメソッド/異なるパラメータに対応)を1つの呼び出しに統合
+ /// @param calls Call構造体で構成される配列
+ /// @return returnData Result構造体で構成される配列
+ function multicall(Call[] calldata calls) public returns (Result[] memory returnData) {
+ uint256 length = calls.length;
+ returnData = new Result[](length);
+ Call calldata calli;
+
+ // ループで順次呼び出し
+ for (uint256 i = 0; i < length; i++) {
+ Result memory result = returnData[i];
+ calli = calls[i];
+ (result.success, result.returnData) = calli.target.call(calli.callData);
+ // calli.allowFailureとresult.successがともにfalseの場合、revert
+ if (!(calli.allowFailure || result.success)){
+ revert("Multicall: call failed");
+ }
+ }
+ }
+}
+```
+
+## Remix復現
+
+1. まず、非常にシンプルなERC20トークンコントラクト `MCERC20` をデプロイし、コントラクトアドレスを記録します。
+
+ ```solidity
+ // SPDX-License-Identifier: MIT
+ pragma solidity ^0.8.19;
+ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+ contract MCERC20 is ERC20{
+ constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){}
+
+ function mint(address to, uint amount) external {
+ _mint(to, amount);
+ }
+ }
+ ```
+
+2. `MultiCall` コントラクトをデプロイします。
+
+3. 呼び出す`calldata`を取得します。2つのアドレスにそれぞれ50と100単位のトークンを鋳造します。remixの呼び出しページで`mint()`のパラメータを入力し、**Calldata** ボタンをクリックして、エンコードされたcalldataをコピーできます。例:
+
+ ```solidity
+ to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
+ amount: 50
+ calldata: 0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032
+ ```
+
+ 
+
+ `calldata`について詳しくない場合は、WTF Solidityの[第29講]を読むことができます。
+
+4. `MultiCall` の `multicall()` 関数を使用してERC20トークンコントラクトの `mint()` 関数を呼び出し、2つのアドレスにそれぞれ50と100単位のトークンを鋳造します。例:
+
+ ```solidity
+ calls: [["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x40c10f19000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb20000000000000000000000000000000000000000000000000000000000000064"]]
+ ```
+
+5. `MultiCall` の `multicall()` 関数を使用してERC20トークンコントラクトの `balanceOf()` 関数を呼び出し、先ほど鋳造した2つのアドレスの残高をクエリします。`balanceOf()`関数のselectorは`0x70a08231`です。例:
+
+ ```solidity
+ [["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x70a08231000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2"]]
+ ```
+
+ `decoded output`で呼び出しの戻り値を確認できます。2つのアドレスの残高はそれぞれ `0x0000000000000000000000000000000000000000000000000000000000000032` と `0x0000000000000000000000000000000000000000000000000000000000000064`、つまり50と100で、呼び出し成功です!
+ 
+
+## まとめ
+
+今回は、MultiCall マルチコールコントラクトについて紹介しました。これにより、1つのトランザクションで複数の関数呼び出しを実行できます。注意すべきは、異なるMultiCallコントラクトでは、パラメータと実行ロジックに多少の違いがあるため、使用時にはソースコードを注意深く読む必要があることです。
\ No newline at end of file
diff --git a/Languages/ja/56_DEX_ja/SimpleSwap.sol b/Languages/ja/56_DEX_ja/SimpleSwap.sol
new file mode 100644
index 000000000..db55392eb
--- /dev/null
+++ b/Languages/ja/56_DEX_ja/SimpleSwap.sol
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract SimpleSwap is ERC20 {
+ // トークンコントラクト
+ IERC20 public token0;
+ IERC20 public token1;
+
+ // トークン準備金
+ uint public reserve0;
+ uint public reserve1;
+
+ // イベント
+ event Mint(address indexed sender, uint amount0, uint amount1);
+ event Burn(address indexed sender, uint amount0, uint amount1);
+ event Swap(
+ address indexed sender,
+ uint amountIn,
+ address tokenIn,
+ uint amountOut,
+ address tokenOut
+ );
+
+ // コンストラクタ、トークンアドレスを初期化
+ constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") {
+ token0 = _token0;
+ token1 = _token1;
+ }
+
+ // 2つの数の最小値を取得
+ function min(uint x, uint y) internal pure returns (uint z) {
+ z = x < y ? x : y;
+ }
+
+ // 平方根を計算 babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
+ function sqrt(uint y) internal pure returns (uint z) {
+ if (y > 3) {
+ z = y;
+ uint x = y / 2 + 1;
+ while (x < z) {
+ z = x;
+ x = (y / x + x) / 2;
+ }
+ } else if (y != 0) {
+ z = 1;
+ }
+ }
+
+ // 流動性を追加、トークンを転送、LPをミント
+ // 初回追加の場合、ミントされるLP数量 = sqrt(amount0 * amount1)
+ // 初回以外の場合、ミントされるLP数量 = min(amount0/reserve0, amount1/reserve1)* totalSupply_LP
+ // @param amount0Desired 追加するtoken0の数量
+ // @param amount1Desired 追加するtoken1の数量
+ function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){
+ // 追加する流動性をSwapコントラクトに転送、事前にSwapコントラクトに承認が必要
+ token0.transferFrom(msg.sender, address(this), amount0Desired);
+ token1.transferFrom(msg.sender, address(this), amount1Desired);
+ // 追加する流動性を計算
+ uint _totalSupply = totalSupply();
+ if (_totalSupply == 0) {
+ // 初回流動性追加の場合、L = sqrt(x * y) 単位のLP(流動性プロバイダー)トークンをミント
+ liquidity = sqrt(amount0Desired * amount1Desired);
+ } else {
+ // 初回以外の場合、追加するトークン数量の比率でLPをミント、2つのトークンのうち小さい方の比率を取る
+ liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1);
+ }
+
+ // ミントされるLP数量をチェック
+ require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED');
+
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ // 流動性プロバイダーにLPトークンをミント、提供した流動性を表す
+ _mint(msg.sender, liquidity);
+
+ emit Mint(msg.sender, amount0Desired, amount1Desired);
+ }
+
+ // 流動性を除去、LPを焼却、トークンを転送
+ // 転送数量 = (liquidity / totalSupply_LP) * reserve
+ // @param liquidity 除去する流動性数量
+ function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) {
+ // 残高を取得
+ uint balance0 = token0.balanceOf(address(this));
+ uint balance1 = token1.balanceOf(address(this));
+ // LPの比率に応じて転送するトークン数量を計算
+ uint _totalSupply = totalSupply();
+ amount0 = liquidity * balance0 / _totalSupply;
+ amount1 = liquidity * balance1 / _totalSupply;
+ // トークン数量をチェック
+ require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED');
+ // LPを焼却
+ _burn(msg.sender, liquidity);
+ // トークンを転送
+ token0.transfer(msg.sender, amount0);
+ token1.transfer(msg.sender, amount1);
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ emit Burn(msg.sender, amount0, amount1);
+ }
+
+ // アセットの数量とトークンペアの準備金が与えられた場合、交換する他のトークンの数量を計算
+ // 積が一定のため
+ // 交換前: k = x * y
+ // 交換後: k = (x + delta_x) * (y + delta_y)
+ // delta_y = - delta_x * y / (x + delta_x) が得られる
+ // 正/負号は転入/転出を表す
+ function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
+ require(amountIn > 0, 'INSUFFICIENT_AMOUNT');
+ require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY');
+ amountOut = amountIn * reserveOut / (reserveIn + amountIn);
+ }
+
+ // トークンをswap
+ // @param amountIn 交換に使用するトークン数量
+ // @param tokenIn 交換に使用するトークンコントラクトアドレス
+ // @param amountOutMin 交換して得られる他のトークンの最小数量
+ function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){
+ require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN');
+
+ uint balance0 = token0.balanceOf(address(this));
+ uint balance1 = token1.balanceOf(address(this));
+
+ if(tokenIn == token0){
+ // token0をtoken1に交換する場合
+ tokenOut = token1;
+ // 交換できるtoken1数量を計算
+ amountOut = getAmountOut(amountIn, balance0, balance1);
+ require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ // 交換を実行
+ tokenIn.transferFrom(msg.sender, address(this), amountIn);
+ tokenOut.transfer(msg.sender, amountOut);
+ }else{
+ // token1をtoken0に交換する場合
+ tokenOut = token0;
+ // 交換できるtoken1数量を計算
+ amountOut = getAmountOut(amountIn, balance1, balance0);
+ require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ // 交換を実行
+ tokenIn.transferFrom(msg.sender, address(this), amountIn);
+ tokenOut.transfer(msg.sender, amountOut);
+ }
+
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut));
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/56_DEX_ja/img/56-1.png b/Languages/ja/56_DEX_ja/img/56-1.png
new file mode 100644
index 000000000..3189e4f52
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-1.png differ
diff --git a/Languages/ja/56_DEX_ja/img/56-10.jpg b/Languages/ja/56_DEX_ja/img/56-10.jpg
new file mode 100644
index 000000000..6b97924d3
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-10.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-11.jpg b/Languages/ja/56_DEX_ja/img/56-11.jpg
new file mode 100644
index 000000000..b2a989aca
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-11.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-2.jpg b/Languages/ja/56_DEX_ja/img/56-2.jpg
new file mode 100644
index 000000000..244c0cbb0
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-2.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-3.jpg b/Languages/ja/56_DEX_ja/img/56-3.jpg
new file mode 100644
index 000000000..6fad30735
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-3.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-4.jpg b/Languages/ja/56_DEX_ja/img/56-4.jpg
new file mode 100644
index 000000000..d1129fc04
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-4.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-5.jpg b/Languages/ja/56_DEX_ja/img/56-5.jpg
new file mode 100644
index 000000000..f6f731527
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-5.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-6.jpg b/Languages/ja/56_DEX_ja/img/56-6.jpg
new file mode 100644
index 000000000..e30fce018
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-6.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-7.jpg b/Languages/ja/56_DEX_ja/img/56-7.jpg
new file mode 100644
index 000000000..593d3f32c
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-7.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-8.jpg b/Languages/ja/56_DEX_ja/img/56-8.jpg
new file mode 100644
index 000000000..174967259
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-8.jpg differ
diff --git a/Languages/ja/56_DEX_ja/img/56-9.jpg b/Languages/ja/56_DEX_ja/img/56-9.jpg
new file mode 100644
index 000000000..8475035b1
Binary files /dev/null and b/Languages/ja/56_DEX_ja/img/56-9.jpg differ
diff --git a/Languages/ja/56_DEX_ja/readme.md b/Languages/ja/56_DEX_ja/readme.md
new file mode 100644
index 000000000..c66df02f1
--- /dev/null
+++ b/Languages/ja/56_DEX_ja/readme.md
@@ -0,0 +1,474 @@
+---
+title: 56. 分散型取引所
+tags:
+ - solidity
+ - erc20
+ - defi
+---
+
+# WTF Solidity極簡入門: 56. 分散型取引所
+
+私は最近Solidityを学び直して、細かい部分を固めており、「WTF Solidity極簡入門」を書いて初心者の皆さんに提供しています(プログラミング上級者は他のチュートリアルを探してください)。毎週1-3講ずつ更新しています。
+
+Twitter:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはgithubでオープンソース化されています:[github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+この講義では、恒定積自動マーケットメーカー(Constant Product Automated Market Maker, CPAMM)について説明します。これは分散型取引所の核心メカニズムであり、Uniswap、PancakeSwapなど一連のDEXで採用されています。教育用コントラクトは[Uniswap-v2](https://github.com/Uniswap/v2-core)コントラクトを簡素化したもので、CPAMMの最も核心的な機能を含んでいます。
+
+## 自動マーケットメーカー
+
+自動マーケットメーカー(Automated Market Maker、略してAMM)は、ブロックチェーン上で動作するアルゴリズム、またはスマートコントラクトの一種であり、デジタル資産間の分散型取引を可能にします。AMMの導入により、従来の買い手と売り手によるオーダーマッチングを必要とせず、予め設定された数学的公式(例:恒定積公式)によって流動性プール(りゅうどうせいプール)を作成し、ユーザーがいつでも取引できる全く新しい取引方法が開拓されました。
+
+
+
+次に、コーラ($COLA)と米ドル($USD)の市場を例に、AMMについて説明します。便宜上、記号を定義します:$x$ と $y$ はそれぞれ市場のコーラと米ドルの総量を表し、$\Delta x$ と $\Delta y$ は一回の取引におけるコーラと米ドルの変化量を表し、$L$ と $\Delta L$ は総流動性と流動性の変化量を表します。
+
+### 恒定和自動マーケットメーカー
+
+恒定和自動マーケットメーカー(Constant Sum Automated Market Maker, CSAMM)は最もシンプルな自動マーケットメーカーモデルで、ここから始めます。取引時の制約は以下の通りです:
+
+$$k=x+y$$
+
+ここで $k$ は定数です。つまり、取引前後で市場のコーラと米ドル数量の合計が不変に保たれます。例として、市場の流動性が10本のコーラと10ドルの場合、この時 $k=20$ で、コーラの価格は1ドル/本です。私がとても喉が渇いて、2ドルでコーラを交換したいとします。取引後、市場の米ドル総量は12となり、制約 $k=20$ により、取引後市場には8本のコーラがあり、価格は1ドル/本です。私の取引では2本のコーラを得て、価格は1ドル/本でした。
+
+CSAMMの利点は、トークンの相対価格を不変に保てることで、これはステーブルコイン交換において重要です。誰もが1 USDTで常に1 USDCと交換できることを望みます。しかし欠点も明白で、流動性が容易に枯渇してしまうことです:私が10ドルあれば、市場のコーラの流動性を完全に枯渇させ、他のコーラを飲みたいユーザーが取引できなくなってしまいます。
+
+次に、「無限」の流動性を持つ恒定積自動マーケットメーカーを紹介します。
+
+### 恒定積自動マーケットメーカー
+
+恒定積自動マーケットメーカー(CPAMM)は最も人気のある自動マーケットメーカーモデルで、最初にUniswapで採用されました。取引時の制約は以下の通りです:
+
+$$k=x*y$$
+
+ここで $k$ は定数です。つまり、取引前後で市場のコーラと米ドル数量の積が不変に保たれます。同じ例で、市場の流動性が10本のコーラと10ドルの場合、この時 $k=100$ で、コーラの価格は1ドル/本です。私がとても喉が渇いて、10ドルでコーラを交換したいとします。CSAMMの場合、私の取引は10本のコーラと交換され、市場のコーラ流動性を枯渇させます。しかしCPAMMでは、取引後市場の米ドル総量は20となり、制約 $k=100$ により、取引後市場には5本のコーラがあり、価格は $20/5 = 4$ ドル/本です。私の取引では5本のコーラを得て、価格は $10/5 = 2$ ドル/本でした。
+
+CPAMMの利点は「無限」の流動性を持つことです:トークンの相対価格は売買に応じて変化し、より希少なトークンの相対価格はより高くなり、流動性の枯渇を回避します。上の例では、取引によってコーラが1ドル/本から4ドル/本に上昇し、市場のコーラが買い占められることを防ぎました。
+
+以下、CPAMMベースの極簡分散型取引所を構築してみましょう。
+
+## 分散型取引所
+
+以下、スマートコントラクトで分散型取引所 `SimpleSwap` を作成し、ユーザーが一対のトークンを取引できるようにします。
+
+`SimpleSwap` はERC20トークン標準を継承し、流動性プロバイダーが提供した流動性を記録しやすくしています。コンストラクタで、一対のトークンアドレス `token0` と `token1` を指定し、取引所はこのトークンペアのみをサポートします。`reserve0` と `reserve1` はコントラクト内のトークン準備量を記録します。
+
+```solidity
+contract SimpleSwap is ERC20 {
+ // トークンコントラクト
+ IERC20 public token0;
+ IERC20 public token1;
+
+ // トークン準備金
+ uint public reserve0;
+ uint public reserve1;
+
+ // コンストラクタ、トークンアドレスを初期化
+ constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") {
+ token0 = _token0;
+ token1 = _token1;
+ }
+}
+```
+
+取引所には主に2種類の参加者がいます:流動性プロバイダー(Liquidity Provider, LP)とトレーダー(Trader)です。以下、これら2つの部分の機能をそれぞれ実装します。
+
+### 流動性提供
+
+流動性プロバイダーは市場に流動性を提供し、トレーダーがより良い価格と流動性を得られるようにし、一定の手数料を受け取ります。
+
+まず、流動性追加機能を実装する必要があります。ユーザーがトークンプールに流動性を追加する際、コントラクトは追加されたLP持分を記録する必要があります。Uniswap V2によると、LP持分は以下のように計算されます:
+
+1. トークンプールに初回流動性追加時、LP持分 $\Delta{L}$ は追加トークン数量積の平方根で決定されます:
+
+ $$\Delta{L}=\sqrt{\Delta{x} *\Delta{y}}$$
+
+2. 初回以外の流動性追加時、LP持分は追加トークン数量がプールトークン準備量に占める比率で決定されます(2つのトークンの比率のうち小さい方を取る):
+
+ $$\Delta{L}=L*\min{(\frac{\Delta{x}}{x}, \frac{\Delta{y}}{y})}$$
+
+`SimpleSwap` コントラクトはERC20トークン標準を継承しているため、LP持分を計算後、持分をトークン形式でユーザーにミントできます。
+
+以下の `addLiquidity()` 関数は流動性追加機能を実装し、主な手順は以下の通りです:
+
+1. ユーザーが追加したトークンをコントラクトに転送します。ユーザーは事前にコントラクトに承認を与える必要があります。
+2. 公式に基づいて追加された流動性持分を計算し、ミントされるLP数量をチェックします。
+3. コントラクトのトークン準備量を更新します。
+4. 流動性プロバイダーにLPトークンをミントします。
+5. `Mint` イベントを発行します。
+
+```solidity
+event Mint(address indexed sender, uint amount0, uint amount1);
+
+// 流動性を追加、トークンを転送、LPをミント
+// @param amount0Desired 追加するtoken0数量
+// @param amount1Desired 追加するtoken1数量
+function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){
+ // 追加する流動性をSwapコントラクトに転送、事前にSwapコントラクトに承認が必要
+ token0.transferFrom(msg.sender, address(this), amount0Desired);
+ token1.transferFrom(msg.sender, address(this), amount1Desired);
+ // 追加する流動性を計算
+ uint _totalSupply = totalSupply();
+ if (_totalSupply == 0) {
+ // 初回流動性追加の場合、L = sqrt(x * y) 単位のLP(流動性プロバイダー)トークンをミント
+ liquidity = sqrt(amount0Desired * amount1Desired);
+ } else {
+ // 初回以外の場合、追加するトークン数量の比率でLPをミント、2つのトークンのうち小さい方の比率を取る
+ liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1);
+ }
+
+ // ミントされるLP数量をチェック
+ require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED');
+
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ // 流動性プロバイダーにLPトークンをミント、提供した流動性を表す
+ _mint(msg.sender, liquidity);
+
+ emit Mint(msg.sender, amount0Desired, amount1Desired);
+}
+```
+
+次に、流動性除去機能を実装する必要があります。ユーザーがプールから流動性 $\Delta{L}$ を除去する際、コントラクトはLP持分トークンを焼却し、比率に応じてトークンをユーザーに返還する必要があります。返還トークンの計算公式は以下の通りです:
+
+$$\Delta{x}={\frac{\Delta{L}}{L} * x}$$
+
+$$\Delta{y}={\frac{\Delta{L}}{L} * y}$$
+
+以下の `removeLiquidity()` 関数は流動性除去機能を実装し、主な手順は以下の通りです:
+
+1. コントラクト内のトークン残高を取得します。
+2. LPの比率に応じて転送するトークン数量を計算します。
+3. トークン数量をチェックします。
+4. LP持分を焼却します。
+5. 対応するトークンをユーザーに転送します。
+6. 準備量を更新します。
+7. `Burn` イベントを発行します。
+
+```solidity
+// 流動性を除去、LPを焼却、トークンを転送
+// 転送数量 = (liquidity / totalSupply_LP) * reserve
+// @param liquidity 除去する流動性数量
+function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) {
+ // 残高を取得
+ uint balance0 = token0.balanceOf(address(this));
+ uint balance1 = token1.balanceOf(address(this));
+ // LPの比率に応じて転送するトークン数量を計算
+ uint _totalSupply = totalSupply();
+ amount0 = liquidity * balance0 / _totalSupply;
+ amount1 = liquidity * balance1 / _totalSupply;
+ // トークン数量をチェック
+ require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED');
+ // LPを焼却
+ _burn(msg.sender, liquidity);
+ // トークンを転送
+ token0.transfer(msg.sender, amount0);
+ token1.transfer(msg.sender, amount1);
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ emit Burn(msg.sender, amount0, amount1);
+}
+```
+
+ここまでで、コントラクト内の流動性プロバイダー関連機能が完成しました。次は取引の部分です。
+
+### 取引
+
+Swapコントラクトでは、ユーザーは一種のトークンで他の種類を取引できます。では、$\Delta{x}$ 単位のtoken0で、何単位のtoken1と交換できるでしょうか?以下で簡単に導出してみましょう。
+
+恒定積公式により、取引前:
+
+$$k=x*y$$
+
+取引後:
+
+$$k=(x+\Delta{x})*(y+\Delta{y})$$
+
+取引前後で $k$ 値は不変なので、上記等式を連立すると:
+
+$$\Delta{y}=-\frac{\Delta{x}*y}{x+\Delta{x}}$$
+
+したがって、交換できるトークン数量 $\Delta{y}$ は $\Delta{x}$、$x$、および $y$ によって決定されます。注意すべきは、$\Delta{x}$ と $\Delta{y}$ の符号が逆であることです。これは、転入がトークン準備量を増加させ、転出が減少させるためです。
+
+以下の `getAmountOut()` は、アセットの数量とトークンペアの準備金が与えられた場合、交換する他のトークンの数量を計算する実装です。
+
+```solidity
+// アセットの数量とトークンペアの準備金が与えられた場合、交換する他のトークンの数量を計算
+function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
+ require(amountIn > 0, 'INSUFFICIENT_AMOUNT');
+ require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY');
+ amountOut = amountIn * reserveOut / (reserveIn + amountIn);
+}
+```
+
+この核心公式により、取引機能の実装に着手できます。以下の `swap()` 関数はトークン取引機能を実装し、主な手順は以下の通りです:
+
+1. ユーザーは関数呼び出し時に交換に使用するトークン数量、交換するトークンアドレス、および交換により得られる他のトークンの最小数量を指定します。
+2. token0をtoken1に交換するか、token1をtoken0に交換するかを判断します。
+3. 上記公式を利用して、交換により得られるトークンの数量を計算します。
+4. 交換により得られるトークンがユーザー指定の最小数量に達しているかを判断します。これは取引のスリッページに類似しています。
+5. ユーザーのトークンをコントラクトに転送します。
+6. 交換されたトークンをコントラクトからユーザーに転送します。
+7. コントラクトのトークン準備量を更新します。
+8. `Swap` イベントを発行します。
+
+```solidity
+// トークンをswap
+// @param amountIn 交換に使用するトークン数量
+// @param tokenIn 交換に使用するトークンコントラクトアドレス
+// @param amountOutMin 交換して得られる他のトークンの最小数量
+function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){
+ require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN');
+
+ uint balance0 = token0.balanceOf(address(this));
+ uint balance1 = token1.balanceOf(address(this));
+
+ if(tokenIn == token0){
+ // token0をtoken1に交換する場合
+ tokenOut = token1;
+ // 交換できるtoken1数量を計算
+ amountOut = getAmountOut(amountIn, balance0, balance1);
+ require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ // 交換を実行
+ tokenIn.transferFrom(msg.sender, address(this), amountIn);
+ tokenOut.transfer(msg.sender, amountOut);
+ }else{
+ // token1をtoken0に交換する場合
+ tokenOut = token0;
+ // 交換できるtoken1数量を計算
+ amountOut = getAmountOut(amountIn, balance1, balance0);
+ require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ // 交換を実行
+ tokenIn.transferFrom(msg.sender, address(this), amountIn);
+ tokenOut.transfer(msg.sender, amountOut);
+ }
+
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut));
+}
+```
+
+## Swapコントラクト
+
+`SimpleSwap` の完全なコードは以下の通りです:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract SimpleSwap is ERC20 {
+ // トークンコントラクト
+ IERC20 public token0;
+ IERC20 public token1;
+
+ // トークン準備金
+ uint public reserve0;
+ uint public reserve1;
+
+ // イベント
+ event Mint(address indexed sender, uint amount0, uint amount1);
+ event Burn(address indexed sender, uint amount0, uint amount1);
+ event Swap(
+ address indexed sender,
+ uint amountIn,
+ address tokenIn,
+ uint amountOut,
+ address tokenOut
+ );
+
+ // コンストラクタ、トークンアドレスを初期化
+ constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") {
+ token0 = _token0;
+ token1 = _token1;
+ }
+
+ // 2つの数の最小値を取得
+ function min(uint x, uint y) internal pure returns (uint z) {
+ z = x < y ? x : y;
+ }
+
+ // 平方根を計算 babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
+ function sqrt(uint y) internal pure returns (uint z) {
+ if (y > 3) {
+ z = y;
+ uint x = y / 2 + 1;
+ while (x < z) {
+ z = x;
+ x = (y / x + x) / 2;
+ }
+ } else if (y != 0) {
+ z = 1;
+ }
+ }
+
+ // 流動性を追加、トークンを転送、LPをミント
+ // 初回追加の場合、ミントされるLP数量 = sqrt(amount0 * amount1)
+ // 初回以外の場合、ミントされるLP数量 = min(amount0/reserve0, amount1/reserve1)* totalSupply_LP
+ // @param amount0Desired 追加するtoken0数量
+ // @param amount1Desired 追加するtoken1数量
+ function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){
+ // 追加する流動性をSwapコントラクトに転送、事前にSwapコントラクトに承認が必要
+ token0.transferFrom(msg.sender, address(this), amount0Desired);
+ token1.transferFrom(msg.sender, address(this), amount1Desired);
+ // 追加する流動性を計算
+ uint _totalSupply = totalSupply();
+ if (_totalSupply == 0) {
+ // 初回流動性追加の場合、L = sqrt(x * y) 単位のLP(流動性プロバイダー)トークンをミント
+ liquidity = sqrt(amount0Desired * amount1Desired);
+ } else {
+ // 初回以外の場合、追加するトークン数量の比率でLPをミント、2つのトークンのうち小さい方の比率を取る
+ liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1);
+ }
+
+ // ミントされるLP数量をチェック
+ require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED');
+
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ // 流動性プロバイダーにLPトークンをミント、提供した流動性を表す
+ _mint(msg.sender, liquidity);
+
+ emit Mint(msg.sender, amount0Desired, amount1Desired);
+ }
+
+ // 流動性を除去、LPを焼却、トークンを転送
+ // 転送数量 = (liquidity / totalSupply_LP) * reserve
+ // @param liquidity 除去する流動性数量
+ function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) {
+ // 残高を取得
+ uint balance0 = token0.balanceOf(address(this));
+ uint balance1 = token1.balanceOf(address(this));
+ // LPの比率に応じて転送するトークン数量を計算
+ uint _totalSupply = totalSupply();
+ amount0 = liquidity * balance0 / _totalSupply;
+ amount1 = liquidity * balance1 / _totalSupply;
+ // トークン数量をチェック
+ require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED');
+ // LPを焼却
+ _burn(msg.sender, liquidity);
+ // トークンを転送
+ token0.transfer(msg.sender, amount0);
+ token1.transfer(msg.sender, amount1);
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ emit Burn(msg.sender, amount0, amount1);
+ }
+
+ // アセットの数量とトークンペアの準備金が与えられた場合、交換する他のトークンの数量を計算
+ // 積が一定のため
+ // 交換前: k = x * y
+ // 交換後: k = (x + delta_x) * (y + delta_y)
+ // delta_y = - delta_x * y / (x + delta_x) が得られる
+ // 正/負号は転入/転出を表す
+ function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
+ require(amountIn > 0, 'INSUFFICIENT_AMOUNT');
+ require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY');
+ amountOut = amountIn * reserveOut / (reserveIn + amountIn);
+ }
+
+ // トークンをswap
+ // @param amountIn 交換に使用するトークン数量
+ // @param tokenIn 交換に使用するトークンコントラクトアドレス
+ // @param amountOutMin 交換して得られる他のトークンの最小数量
+ function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){
+ require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN');
+
+ uint balance0 = token0.balanceOf(address(this));
+ uint balance1 = token1.balanceOf(address(this));
+
+ if(tokenIn == token0){
+ // token0をtoken1に交換する場合
+ tokenOut = token1;
+ // 交換できるtoken1数量を計算
+ amountOut = getAmountOut(amountIn, balance0, balance1);
+ require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ // 交換を実行
+ tokenIn.transferFrom(msg.sender, address(this), amountIn);
+ tokenOut.transfer(msg.sender, amountOut);
+ }else{
+ // token1をtoken0に交換する場合
+ tokenOut = token0;
+ // 交換できるtoken1数量を計算
+ amountOut = getAmountOut(amountIn, balance1, balance0);
+ require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT');
+ // 交換を実行
+ tokenIn.transferFrom(msg.sender, address(this), amountIn);
+ tokenOut.transfer(msg.sender, amountOut);
+ }
+
+ // 準備金を更新
+ reserve0 = token0.balanceOf(address(this));
+ reserve1 = token1.balanceOf(address(this));
+
+ emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut));
+ }
+}
+```
+
+## Remix復現
+
+1. 2つのERC20トークンコントラクト(token0とtoken1)をデプロイし、そのコントラクトアドレスを記録します。
+
+ 
+
+2. `SimpleSwap` コントラクトをデプロイし、上記のトークンアドレスを入力します。
+
+ 
+
+3. 2つのERC20トークンの `approve()` 関数を呼び出し、それぞれ `SimpleSwap` コントラクトに1000単位のトークンを承認します。
+
+ 
+
+4. `SimpleSwap` コントラクトの `addLiquidity()` 関数を呼び出して取引所に流動性を追加し、token0とtoken1をそれぞれ100単位追加します。
+
+ 
+
+5. `SimpleSwap` コントラクトの `balanceOf()` 関数を呼び出してユーザーのLP持分を確認します。これは100になるはずです。($\sqrt{100*100}=100$)
+
+ 
+
+6. `SimpleSwap` コントラクトの `swap()` 関数を呼び出してトークン取引を行い、100単位のtoken0を使用します。
+
+ 
+
+7. `SimpleSwap` コントラクトの `reserve0` と `reserve1` 関数を呼び出してコントラクト内のトークン準備量を確認します。200と50になるはずです。前のステップで100単位のtoken0を使用して50単位のtoken1と交換しました($\frac{100*100}{100+100}=50$)。
+
+ 
+
+## まとめ
+
+この講義では、恒定積自動マーケットメーカーについて説明し、極簡分散型取引所を作成しました。極簡Swapコントラクトでは、取引手数料やガバナンス部分など、考慮していない部分が多くあります。分散型取引所に興味がある場合は、[Programming DeFi: Uniswap V2](https://jeiwan.net/posts/programming-defi-uniswapv2-1/)と[Uniswap v3 book](https://y1cunhui.github.io/uniswapV3-book-zh-cn/)の閲読をお勧めします。また、[WTF-Dapp](https://github.com/WTFAcademy/WTF-Dapp)コースの学習を続けることもできます。これには分散型取引所の実戦内容が含まれており、より深い学習ができます。
+
+## 重要な概念の補足説明
+
+### インパーマネントロス(無常損失)
+流動性プロバイダーが知っておくべき重要なリスクとして、インパーマネントロス(無常損失)があります。これは、流動性プールに預けたトークンの価格比率が変化した際に発生する損失です。例えば、1:1の比率でトークンを預けた後、片方のトークンの価格が大幅に上昇した場合、単純にトークンを保有していた場合と比較して損失が発生します。
+
+### LPトークン
+LPトークン(流動性プロバイダートークン)は、ユーザーが流動性プールに提供した流動性の持分を表すトークンです。このトークンは:
+- 流動性除去時に必要
+- 流動性プールでの持分比率を表現
+- 取引手数料の分配基準となる(本実装では省略)
+
+### スリッページ
+スリッページは、期待した価格と実際の取引価格の差です。大きな取引や流動性が少ないプールでは、スリッページが大きくなる傾向があります。本コントラクトの `amountOutMin` パラメータは、このスリッページ制御のための機能です。
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/img/57-1.png b/Languages/ja/57_Flashloan_ja/img/57-1.png
new file mode 100644
index 000000000..7199c4c7a
Binary files /dev/null and b/Languages/ja/57_Flashloan_ja/img/57-1.png differ
diff --git a/Languages/ja/57_Flashloan_ja/readme.md b/Languages/ja/57_Flashloan_ja/readme.md
new file mode 100644
index 000000000..9e1d0fe5d
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/readme.md
@@ -0,0 +1,498 @@
+---
+title: 57. フラッシュローン
+tags:
+ - solidity
+ - flashloan
+ - defi
+ - uniswap
+ - aave
+---
+
+# WTF Solidity極簡入門: 57. フラッシュローン
+
+私は最近Solidityを再学習しており、基礎を固めるために「WTF Solidity極簡入門」を執筆しています。これは初心者向けのガイドです(プログラミング上級者は他のチュートリアルをご参照ください)。毎週1-3レッスンを更新します。
+
+Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_)
+
+コミュニティ: [Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy)
+
+すべてのコードとチュートリアルはGitHubでオープンソース化されています: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+---
+
+「フラッシュローン攻撃」という言葉をきっと聞いたことがあるでしょうが、フラッシュローンとは何でしょうか?フラッシュローンコントラクトをどのように作成するのでしょうか?このレッスンでは、ブロックチェーンにおけるフラッシュローンについて紹介し、Uniswap V2、Uniswap V3、およびAAVE V3をベースとしたフラッシュローンコントラクトを実装し、Foundryを使用してテストします。
+
+## フラッシュローン
+
+「フラッシュローン」という概念を初めて聞いたのはきっとWeb3でしょう。Web2にはこのような仕組みは存在しないからです。フラッシュローン(Flashloan)はDeFiの革新的な仕組みで、ユーザーが1つのトランザクション内で資金を借り入れて迅速に返済することを可能にし、担保を提供する必要がありません。
+
+例えば、市場で突然裁定取引(アービトラージ)の機会を発見したとしましょう。しかし、裁定取引を完了するには100万USDの資金が必要です。Web2では銀行にローンを申請する必要がありますが、審査が必要で、裁定取引の機会を逃してしまう可能性があります。また、裁定取引が失敗した場合、利息を支払うだけでなく、損失した元本も返済する必要があります。
+
+一方、Web3では、DeFiプラットフォーム(Uniswap、AAVE、Dodo)でフラッシュローンを利用して資金を調達できます。無担保で100万USDのトークンを借り、オンチェーン裁定取引を実行し、最後にローンと利息を返済することができます。
+
+フラッシュローンはイーサリアムトランザクションのアトミック性を利用しています。1つのトランザクション(その中のすべての操作を含む)は完全に実行されるか、完全に実行されないかのどちらかです。ユーザーがフラッシュローンを使用しようとして、同じトランザクション内で資金を返済しなかった場合、トランザクション全体が失敗してロールバックされ、まるで何も起こらなかったかのようになります。そのため、DeFiプラットフォームは借り手が返済できないことを心配する必要がありません。返済できない場合は、お金が借り出されなかったことを意味するからです。同時に、借り手も裁定取引の失敗を心配する必要がありません。裁定取引が成功しなければ返済できず、それは借入が成功しなかったことを意味するからです。
+
+
+
+## フラッシュローン実装
+
+以下では、Uniswap V2、Uniswap V3、およびAAVE V3でのフラッシュローンコントラクトの実装方法をそれぞれ紹介します。
+
+### 1. Uniswap V2フラッシュローン
+
+[Uniswap V2 Pair](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159)コントラクトの`swap()`関数はフラッシュローンをサポートしています。フラッシュローンビジネスに関連するコードは以下の通りです:
+
+```solidity
+function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
+ // その他のロジック...
+
+ // 楽観的にトークンをtoアドレスに送信
+ if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
+ if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
+
+ // toアドレスのコールバック関数uniswapV2Callを呼び出し
+ if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
+
+ // その他のロジック...
+
+ // k=x*y公式を通じて、フラッシュローンが正常に返済されたかをチェック
+ require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
+}
+```
+
+`swap()`関数では:
+
+1. まずプールからトークンを楽観的に`to`アドレスに転送します。
+2. 渡された`data`の長さが`0`より大きい場合、`to`アドレスのコールバック関数`uniswapV2Call`を呼び出し、フラッシュローンロジックを実行します。
+3. 最後に`k=x*y`でフラッシュローンが正常に返済されたかをチェックし、成功しなかった場合はトランザクションをロールバックします。
+
+以下では、フラッシュローンコントラクト`UniswapV2Flashloan.sol`を完成させます。`IUniswapV2Callee`を継承し、フラッシュローンのコアロジックをコールバック関数`uniswapV2Call`に記述します。
+
+全体のロジックは非常にシンプルです。フラッシュローン関数`flashloan()`では、Uniswap V2の`WETH-DAI`プールから`WETH`を借ります。フラッシュローンがトリガーされた後、コールバック関数`uniswapV2Call`がPairコントラクトによって呼び出されます。裁定取引は行わず、利息を計算した後にフラッシュローンを返済します。Uniswap V2フラッシュローンの利息は1回あたり`0.3%`です。
+
+**注意**:コールバック関数では適切な権限制御を行い、UniswapのPairコントラクトのみが呼び出せるようにしてください。そうしないと、コントラクト内の資金がハッカーに盗まれる可能性があります。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Lib.sol";
+
+// UniswapV2フラッシュローンコールバックインターフェース
+interface IUniswapV2Callee {
+ function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
+}
+
+// UniswapV2フラッシュローンコントラクト
+contract UniswapV2Flashloan is IUniswapV2Callee {
+ address private constant UNISWAP_V2_FACTORY =
+ 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
+
+ address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+ IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY);
+
+ IERC20 private constant weth = IERC20(WETH);
+
+ IUniswapV2Pair private immutable pair;
+
+ constructor() {
+ pair = IUniswapV2Pair(factory.getPair(DAI, WETH));
+ }
+
+ // フラッシュローン関数
+ function flashloan(uint wethAmount) external {
+ // calldataの長さが1より大きい場合にフラッシュローンコールバック関数をトリガー
+ bytes memory data = abi.encode(WETH, wethAmount);
+
+ // amount0Outは借りるDAI、amount1Outは借りるWETH
+ pair.swap(0, wethAmount, address(this), data);
+ }
+
+ // フラッシュローンコールバック関数、DAI/WETH pairコントラクトのみが呼び出し可能
+ function uniswapV2Call(
+ address sender,
+ uint amount0,
+ uint amount1,
+ bytes calldata data
+ ) external {
+ // DAI/WETH pairコントラクトからの呼び出しであることを確認
+ address token0 = IUniswapV2Pair(msg.sender).token0(); // token0アドレスを取得
+ address token1 = IUniswapV2Pair(msg.sender).token1(); // token1アドレスを取得
+ assert(msg.sender == factory.getPair(token0, token1)); // msg.senderがV2ペアであることを確認
+
+ // calldataをデコード
+ (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256));
+
+ // フラッシュローンロジック、ここでは省略
+ require(tokenBorrow == WETH, "token borrow != WETH");
+
+ // フラッシュローン手数料を計算
+ // fee / (amount + fee) = 3/1000
+ // 切り上げ
+ uint fee = (amount1 * 3) / 997 + 1;
+ uint amountToRepay = amount1 + fee;
+
+ // フラッシュローンを返済
+ weth.transfer(address(pair), amountToRepay);
+ }
+}
+```
+
+Foundryテストコントラクト`UniswapV2Flashloan.t.sol`:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "forge-std/Test.sol";
+import "../src/UniswapV2Flashloan.sol";
+
+address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+contract UniswapV2FlashloanTest is Test {
+ IWETH private weth = IWETH(WETH);
+
+ UniswapV2Flashloan private flashloan;
+
+ function setUp() public {
+ flashloan = new UniswapV2Flashloan();
+ }
+
+ function testFlashloan() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e18);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ flashloan.flashloan(amountToBorrow);
+ }
+
+ // 手数料が不足している場合、リバートする
+ function testFlashloanFail() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 3e17);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ // 手数料不足
+ vm.expectRevert();
+ flashloan.flashloan(amountToBorrow);
+ }
+}
+```
+
+テストコントラクトでは、手数料が充分な場合と不足している場合をそれぞれテストしています。Foundryインストール後、以下のコマンドラインでテストできます(RPCを他のイーサリアムRPCに変更できます):
+
+```shell
+FORK_URL=https://singapore.rpc.blxrbdn.com
+forge test --fork-url $FORK_URL --match-path test/UniswapV2Flashloan.t.sol -vv
+```
+
+### 2. Uniswap V3フラッシュローン
+
+Uniswap V2が`swap()`交換関数でフラッシュローンを間接的にサポートするのとは異なり、Uniswap V3は[Poolプールコントラクト](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L791C1-L835C1)に`flash()`関数を追加してフラッシュローンを直接サポートしています。コアコードは以下の通りです:
+
+```solidity
+function flash(
+ address recipient,
+ uint256 amount0,
+ uint256 amount1,
+ bytes calldata data
+) external override lock noDelegateCall {
+ // その他のロジック...
+
+ // 楽観的にトークンをtoアドレスに送信
+ if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0);
+ if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1);
+
+ // toアドレスのコールバック関数uniswapV3FlashCallbackを呼び出し
+ IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data);
+
+ // フラッシュローンが正常に返済されたかをチェック
+ uint256 balance0After = balance0();
+ uint256 balance1After = balance1();
+ require(balance0Before.add(fee0) <= balance0After, 'F0');
+ require(balance1Before.add(fee1) <= balance1After, 'F1');
+
+ // sub is safe because we know balanceAfter is gt balanceBefore by at least fee
+ uint256 paid0 = balance0After - balance0Before;
+ uint256 paid1 = balance1After - balance1Before;
+
+ // その他のロジック...
+}
+```
+
+以下では、フラッシュローンコントラクト`UniswapV3Flashloan.sol`を完成させます。`IUniswapV3FlashCallback`を継承し、フラッシュローンのコアロジックをコールバック関数`uniswapV3FlashCallback`に記述します。
+
+全体のロジックはV2と類似しており、フラッシュローン関数`flashloan()`では、Uniswap V3の`WETH-DAI`プールから`WETH`を借ります。フラッシュローンがトリガーされた後、コールバック関数`uniswapV3FlashCallback`がPoolコントラクトによって呼び出されます。裁定取引は行わず、利息を計算した後にフラッシュローンを返済します。Uniswap V3のフラッシュローン手数料は取引手数料と同じです。
+
+**注意**:コールバック関数では適切な権限制御を行い、UniswapのPairコントラクトのみが呼び出せるようにしてください。そうしないと、コントラクト内の資金がハッカーに盗まれる可能性があります。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Lib.sol";
+
+// UniswapV3フラッシュローンコールバックインターフェース
+// uniswapV3FlashCallback()関数を実装・オーバーライドする必要があります
+interface IUniswapV3FlashCallback {
+ /// 実装では、flashで送信されたトークンと計算された手数料を
+ /// プールに返済する必要があります。
+ /// このメソッドを呼び出すコントラクトは、公式UniswapV3Factoryで
+ /// デプロイされたUniswapV3Poolによってチェックされる必要があります。
+ /// @param fee0 フラッシュローン終了時にプールに支払うtoken0の手数料
+ /// @param fee1 フラッシュローン終了時にプールに支払うtoken1の手数料
+ /// @param data IUniswapV3PoolActions#flash呼び出しで呼び出し元から渡された任意のデータ
+ function uniswapV3FlashCallback(
+ uint256 fee0,
+ uint256 fee1,
+ bytes calldata data
+ ) external;
+}
+
+// UniswapV3フラッシュローンコントラクト
+contract UniswapV3Flashloan is IUniswapV3FlashCallback {
+ address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
+
+ address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+ uint24 private constant poolFee = 3000;
+
+ IERC20 private constant weth = IERC20(WETH);
+ IUniswapV3Pool private immutable pool;
+
+ constructor() {
+ pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee));
+ }
+
+ function getPool(
+ address _token0,
+ address _token1,
+ uint24 _fee
+ ) public pure returns (address) {
+ PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey(
+ _token0,
+ _token1,
+ _fee
+ );
+ return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey);
+ }
+
+ // フラッシュローン関数
+ function flashloan(uint wethAmount) external {
+ bytes memory data = abi.encode(WETH, wethAmount);
+ IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data);
+ }
+
+ // フラッシュローンコールバック関数、DAI/WETH pairコントラクトのみが呼び出し可能
+ function uniswapV3FlashCallback(
+ uint fee0,
+ uint fee1,
+ bytes calldata data
+ ) external {
+ // DAI/WETH pairコントラクトからの呼び出しであることを確認
+ require(msg.sender == address(pool), "not authorized");
+
+ // calldataをデコード
+ (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256));
+
+ // フラッシュローンロジック、ここでは省略
+ require(tokenBorrow == WETH, "token borrow != WETH");
+
+ // フラッシュローンを返済
+ weth.transfer(address(pool), wethAmount + fee1);
+ }
+}
+```
+
+Foundryテストコントラクト`UniswapV3Flashloan.t.sol`:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {Test, console2} from "forge-std/Test.sol";
+import "../src/UniswapV3Flashloan.sol";
+
+address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+contract UniswapV3FlashloanTest is Test {
+ IWETH private weth = IWETH(WETH);
+
+ UniswapV3Flashloan private flashloan;
+
+ function setUp() public {
+ flashloan = new UniswapV3Flashloan();
+ }
+
+ function testFlashloan() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e18);
+
+ uint balBefore = weth.balanceOf(address(flashloan));
+ console2.logUint(balBefore);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 1 * 1e18;
+ flashloan.flashloan(amountToBorrow);
+ }
+
+ // 手数料が不足している場合、リバートする
+ function testFlashloanFail() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e17);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ // 手数料不足
+ vm.expectRevert();
+ flashloan.flashloan(amountToBorrow);
+ }
+}
+```
+
+テストコントラクトでは、手数料が充分な場合と不足している場合をそれぞれテストしています。Foundryインストール後、以下のコマンドラインでテストできます(RPCを他のイーサリアムRPCに変更できます):
+
+```shell
+FORK_URL=https://singapore.rpc.blxrbdn.com
+forge test --fork-url $FORK_URL --match-path test/UniswapV3Flashloan.t.sol -vv
+```
+
+### 3. AAVE V3フラッシュローン
+
+AAVEは分散型貸出プラットフォームで、その[Poolコントラクト](https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/pool/Pool.sol#L424)は`flashLoan()`と`flashLoanSimple()`の2つの関数を通じて単一資産と複数資産のフラッシュローンをサポートしています。ここでは、`flashLoanSimple()`を利用して単一資産(`WETH`)のフラッシュローンを実装します。
+
+以下では、フラッシュローンコントラクト`AaveV3Flashloan.sol`を完成させます。`IFlashLoanSimpleReceiver`を継承し、フラッシュローンのコアロジックをコールバック関数`executeOperation`に記述します。
+
+全体のロジックはV2と類似しており、フラッシュローン関数`flashloan()`では、AAVE V3の`WETH`プールから`WETH`を借ります。フラッシュローンがトリガーされた後、コールバック関数`executeOperation`がPoolコントラクトによって呼び出されます。裁定取引は行わず、利息を計算した後にフラッシュローンを返済します。AAVE V3フラッシュローンの手数料はデフォルトで1回あたり`0.05%`で、Uniswapより低くなっています。
+
+**注意**:コールバック関数では適切な権限制御を行い、AAVEのPoolコントラクトのみが呼び出し、開始者がこのコントラクトであることを確認してください。そうしないと、コントラクト内の資金がハッカーに盗まれる可能性があります。
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Lib.sol";
+
+interface IFlashLoanSimpleReceiver {
+ /**
+ * @notice フラッシュローン資産受信後の操作実行
+ * @dev コントラクトが債務+追加手数料を返済できることを確認してください。
+ * 例:十分な資金を持ち、プールから総額を引き出すための承認を行っている
+ * @param asset フラッシュローン資産のアドレス
+ * @param amount フラッシュローン資産の数量
+ * @param premium フラッシュローン資産の手数料
+ * @param initiator フラッシュローンを開始したアドレス
+ * @param params フラッシュローン初期化時に渡されたバイトエンコードパラメータ
+ * @return 操作実行が成功した場合はTrue、失敗した場合はFalseを返す
+ */
+ function executeOperation(
+ address asset,
+ uint256 amount,
+ uint256 premium,
+ address initiator,
+ bytes calldata params
+ ) external returns (bool);
+}
+
+// AAVE V3フラッシュローンコントラクト
+contract AaveV3Flashloan {
+ address private constant AAVE_V3_POOL =
+ 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
+
+ address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+ ILendingPool public aave;
+
+ constructor() {
+ aave = ILendingPool(AAVE_V3_POOL);
+ }
+
+ // フラッシュローン関数
+ function flashloan(uint256 wethAmount) external {
+ aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0);
+ }
+
+ // フラッシュローンコールバック関数、poolコントラクトのみが呼び出し可能
+ function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata)
+ external
+ returns (bool)
+ {
+ // poolコントラクトからの呼び出しであることを確認
+ require(msg.sender == AAVE_V3_POOL, "not authorized");
+ // フラッシュローン開始者がこのコントラクトであることを確認
+ require(initiator == address(this), "invalid initiator");
+
+ // フラッシュローンロジック、ここでは省略
+
+ // フラッシュローン手数料を計算
+ // fee = 5/1000 * amount
+ uint fee = (amount * 5) / 10000 + 1;
+ uint amountToRepay = amount + fee;
+
+ // フラッシュローンを返済
+ IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay);
+
+ return true;
+ }
+}
+```
+
+Foundryテストコントラクト`AaveV3Flashloan.t.sol`:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "forge-std/Test.sol";
+import "../src/AaveV3Flashloan.sol";
+
+address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+contract AaveV3FlashloanTest is Test {
+ IWETH private weth = IWETH(WETH);
+
+ AaveV3Flashloan private flashloan;
+
+ function setUp() public {
+ flashloan = new AaveV3Flashloan();
+ }
+
+ function testFlashloan() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e18);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ flashloan.flashloan(amountToBorrow);
+ }
+
+ // 手数料が不足している場合、リバートする
+ function testFlashloanFail() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 4e16);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ // 手数料不足
+ vm.expectRevert();
+ flashloan.flashloan(amountToBorrow);
+ }
+}
+```
+
+テストコントラクトでは、手数料が充分な場合と不足している場合をそれぞれテストしています。Foundryインストール後、以下のコマンドラインでテストできます(RPCを他のイーサリアムRPCに変更できます):
+
+```shell
+FORK_URL=https://singapore.rpc.blxrbdn.com
+forge test --fork-url $FORK_URL --match-path test/AaveV3Flashloan.t.sol -vv
+```
+
+## まとめ
+
+このレッスンでは、フラッシュローンについて紹介しました。フラッシュローンは、ユーザーが1つのトランザクション内で資金を借り入れて迅速に返済することを可能にし、担保を提供する必要がない仕組みです。そして、Uniswap V2、Uniswap V3、およびAAVEのフラッシュローンコントラクトをそれぞれ実装しました。
+
+フラッシュローンを通じて、私たちは無担保で大量の資金を活用してリスクフリーの裁定取引や脆弱性攻撃を行うことができます。あなたはフラッシュローンで何をする予定ですか?
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/src/AaveV3Flashloan.sol b/Languages/ja/57_Flashloan_ja/src/AaveV3Flashloan.sol
new file mode 100644
index 000000000..d62cafae0
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/src/AaveV3Flashloan.sol
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Lib.sol";
+
+interface IFlashLoanSimpleReceiver {
+ /**
+ * @notice フラッシュローン資産受信後の操作実行
+ * @dev コントラクトが債務+追加手数料を返済できることを確認してください。
+ * 例:十分な資金を持ち、プールから総額を引き出すための承認を行っている
+ * @param asset フラッシュローン資産のアドレス
+ * @param amount フラッシュローン資産の数量
+ * @param premium フラッシュローン資産の手数料
+ * @param initiator フラッシュローンを開始したアドレス
+ * @param params フラッシュローン初期化時に渡されたバイトエンコードパラメータ
+ * @return 操作実行が成功した場合はTrue、失敗した場合はFalseを返す
+ */
+ function executeOperation(
+ address asset,
+ uint256 amount,
+ uint256 premium,
+ address initiator,
+ bytes calldata params
+ ) external returns (bool);
+}
+
+// AAVE V3フラッシュローンコントラクト
+contract AaveV3Flashloan {
+ address private constant AAVE_V3_POOL =
+ 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
+
+ address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+ ILendingPool public aave;
+
+ constructor() {
+ aave = ILendingPool(AAVE_V3_POOL);
+ }
+
+ // フラッシュローン関数
+ function flashloan(uint256 wethAmount) external {
+ aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0);
+ }
+
+ // フラッシュローンコールバック関数、poolコントラクトのみが呼び出し可能
+ function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata)
+ external
+ returns (bool)
+ {
+ // poolコントラクトからの呼び出しであることを確認
+ require(msg.sender == AAVE_V3_POOL, "not authorized");
+ // フラッシュローン開始者がこのコントラクトであることを確認
+ require(initiator == address(this), "invalid initiator");
+
+ // フラッシュローンロジック、ここでは省略
+
+ // フラッシュローン手数料を計算
+ // fee = 5/1000 * amount
+ uint fee = (amount * 5) / 10000 + 1;
+ uint amountToRepay = amount + fee;
+
+ // フラッシュローンを返済
+ IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/src/Lib.sol b/Languages/ja/57_Flashloan_ja/src/Lib.sol
new file mode 100644
index 000000000..57c13816d
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/src/Lib.sol
@@ -0,0 +1,113 @@
+pragma solidity >=0.5.0;
+
+// ERC20トークンインターフェース
+interface IERC20 {
+ event Approval(address indexed owner, address indexed spender, uint value);
+ event Transfer(address indexed from, address indexed to, uint value);
+
+ function name() external view returns (string memory);
+ function symbol() external view returns (string memory);
+ function decimals() external view returns (uint8);
+ function totalSupply() external view returns (uint);
+ function balanceOf(address owner) external view returns (uint);
+ function allowance(address owner, address spender) external view returns (uint);
+
+ function approve(address spender, uint value) external returns (bool);
+ function transfer(address to, uint value) external returns (bool);
+ function transferFrom(address from, address to, uint value) external returns (bool);
+}
+
+// Uniswap V2ペアインターフェース
+interface IUniswapV2Pair {
+ function swap(
+ uint amount0Out,
+ uint amount1Out,
+ address to,
+ bytes calldata data
+ ) external;
+
+ function token0() external view returns (address);
+ function token1() external view returns (address);
+}
+
+// Uniswap V2ファクトリーインターフェース
+interface IUniswapV2Factory {
+ function getPair(
+ address tokenA,
+ address tokenB
+ ) external view returns (address pair);
+}
+
+// WETHインターフェース
+interface IWETH is IERC20 {
+ function deposit() external payable;
+
+ function withdraw(uint amount) external;
+}
+
+// Uniswap V3プールアドレス計算ライブラリ
+library PoolAddress {
+ bytes32 internal constant POOL_INIT_CODE_HASH =
+ 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
+
+ struct PoolKey {
+ address token0;
+ address token1;
+ uint24 fee;
+ }
+
+ function getPoolKey(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) internal pure returns (PoolKey memory) {
+ if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
+ return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
+ }
+
+ function computeAddress(
+ address factory,
+ PoolKey memory key
+ ) internal pure returns (address pool) {
+ require(key.token0 < key.token1);
+ pool = address(
+ uint160(
+ uint(
+ keccak256(
+ abi.encodePacked(
+ hex"ff",
+ factory,
+ keccak256(abi.encode(key.token0, key.token1, key.fee)),
+ POOL_INIT_CODE_HASH
+ )
+ )
+ )
+ )
+ );
+ }
+}
+
+// Uniswap V3プールインターフェース
+interface IUniswapV3Pool {
+ function flash(
+ address recipient,
+ uint amount0,
+ uint amount1,
+ bytes calldata data
+ ) external;
+}
+
+// AAVE V3プールインターフェース
+interface ILendingPool {
+ // 単一資産のフラッシュローン
+ function flashLoanSimple(
+ address receiverAddress,
+ address asset,
+ uint256 amount,
+ bytes calldata params,
+ uint16 referralCode
+ ) external;
+
+ // フラッシュローン手数料を取得、デフォルトは0.05%
+ function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128);
+}
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/src/UniswapV2Flashloan.sol b/Languages/ja/57_Flashloan_ja/src/UniswapV2Flashloan.sol
new file mode 100644
index 000000000..c9ace7064
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/src/UniswapV2Flashloan.sol
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Lib.sol";
+
+// UniswapV2フラッシュローンコールバックインターフェース
+interface IUniswapV2Callee {
+ function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
+}
+
+// UniswapV2フラッシュローンコントラクト
+contract UniswapV2Flashloan is IUniswapV2Callee {
+ address private constant UNISWAP_V2_FACTORY =
+ 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
+
+ address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+ IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY);
+
+ IERC20 private constant weth = IERC20(WETH);
+
+ IUniswapV2Pair private immutable pair;
+
+ constructor() {
+ pair = IUniswapV2Pair(factory.getPair(DAI, WETH));
+ }
+
+ // フラッシュローン関数
+ function flashloan(uint wethAmount) external {
+ // calldataの長さが1より大きい場合にフラッシュローンコールバック関数をトリガー
+ bytes memory data = abi.encode(WETH, wethAmount);
+
+ // amount0Outは借りるDAI、amount1Outは借りるWETH
+ pair.swap(0, wethAmount, address(this), data);
+ }
+
+ // フラッシュローンコールバック関数、DAI/WETH pairコントラクトのみが呼び出し可能
+ function uniswapV2Call(
+ address sender,
+ uint amount0,
+ uint amount1,
+ bytes calldata data
+ ) external {
+ // DAI/WETH pairコントラクトからの呼び出しであることを確認
+ address token0 = IUniswapV2Pair(msg.sender).token0(); // token0アドレスを取得
+ address token1 = IUniswapV2Pair(msg.sender).token1(); // token1アドレスを取得
+ assert(msg.sender == factory.getPair(token0, token1)); // msg.senderがV2ペアであることを確認
+
+ // calldataをデコード
+ (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256));
+
+ // フラッシュローンロジック、ここでは省略
+ require(tokenBorrow == WETH, "token borrow != WETH");
+
+ // フラッシュローン手数料を計算
+ // fee / (amount + fee) = 3/1000
+ // 切り上げ
+ uint fee = (amount1 * 3) / 997 + 1;
+ uint amountToRepay = amount1 + fee;
+
+ // フラッシュローンを返済
+ weth.transfer(address(pair), amountToRepay);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/src/UniswapV3Flashloan.sol b/Languages/ja/57_Flashloan_ja/src/UniswapV3Flashloan.sol
new file mode 100644
index 000000000..25e3c1a45
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/src/UniswapV3Flashloan.sol
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Lib.sol";
+
+// UniswapV3フラッシュローンコールバックインターフェース
+// uniswapV3FlashCallback()関数を実装・オーバーライドする必要があります
+interface IUniswapV3FlashCallback {
+ /// 実装では、flashで送信されたトークンと計算された手数料を
+ /// プールに返済する必要があります。
+ /// このメソッドを呼び出すコントラクトは、公式UniswapV3Factoryで
+ /// デプロイされたUniswapV3Poolによってチェックされる必要があります。
+ /// @param fee0 フラッシュローン終了時にプールに支払うtoken0の手数料
+ /// @param fee1 フラッシュローン終了時にプールに支払うtoken1の手数料
+ /// @param data IUniswapV3PoolActions#flash呼び出しで呼び出し元から渡された任意のデータ
+ function uniswapV3FlashCallback(
+ uint256 fee0,
+ uint256 fee1,
+ bytes calldata data
+ ) external;
+}
+
+// UniswapV3フラッシュローンコントラクト
+contract UniswapV3Flashloan is IUniswapV3FlashCallback {
+ address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
+
+ address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+ uint24 private constant poolFee = 3000;
+
+ IERC20 private constant weth = IERC20(WETH);
+ IUniswapV3Pool private immutable pool;
+
+ constructor() {
+ pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee));
+ }
+
+ function getPool(
+ address _token0,
+ address _token1,
+ uint24 _fee
+ ) public pure returns (address) {
+ PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey(
+ _token0,
+ _token1,
+ _fee
+ );
+ return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey);
+ }
+
+ // フラッシュローン関数
+ function flashloan(uint wethAmount) external {
+ bytes memory data = abi.encode(WETH, wethAmount);
+ IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data);
+ }
+
+ // フラッシュローンコールバック関数、DAI/WETH pairコントラクトのみが呼び出し可能
+ function uniswapV3FlashCallback(
+ uint fee0,
+ uint fee1,
+ bytes calldata data
+ ) external {
+ // DAI/WETH pairコントラクトからの呼び出しであることを確認
+ require(msg.sender == address(pool), "not authorized");
+
+ // calldataをデコード
+ (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256));
+
+ // フラッシュローンロジック、ここでは省略
+ require(tokenBorrow == WETH, "token borrow != WETH");
+
+ // フラッシュローンを返済
+ weth.transfer(address(pool), wethAmount + fee1);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/test/AaveV3Flashloan.t.sol b/Languages/ja/57_Flashloan_ja/test/AaveV3Flashloan.t.sol
new file mode 100644
index 000000000..bd514e27b
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/test/AaveV3Flashloan.t.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "forge-std/Test.sol";
+import "../src/AaveV3Flashloan.sol";
+
+address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+contract AaveV3FlashloanTest is Test {
+ IWETH private weth = IWETH(WETH);
+
+ AaveV3Flashloan private flashloan;
+
+ function setUp() public {
+ flashloan = new AaveV3Flashloan();
+ }
+
+ function testFlashloan() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e18);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ flashloan.flashloan(amountToBorrow);
+ }
+
+ // 手数料が不足している場合、リバートする
+ function testFlashloanFail() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 4e16);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ // 手数料不足
+ vm.expectRevert();
+ flashloan.flashloan(amountToBorrow);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/test/UniswapV2Flashloan.t.sol b/Languages/ja/57_Flashloan_ja/test/UniswapV2Flashloan.t.sol
new file mode 100644
index 000000000..e32d21820
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/test/UniswapV2Flashloan.t.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "forge-std/Test.sol";
+import "../src/UniswapV2Flashloan.sol";
+
+address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+contract UniswapV2FlashloanTest is Test {
+ IWETH private weth = IWETH(WETH);
+
+ UniswapV2Flashloan private flashloan;
+
+ function setUp() public {
+ flashloan = new UniswapV2Flashloan();
+ }
+
+ function testFlashloan() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e18);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ flashloan.flashloan(amountToBorrow);
+ }
+
+ // 手数料が不足している場合、リバートする
+ function testFlashloanFail() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 3e17);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ // 手数料不足
+ vm.expectRevert();
+ flashloan.flashloan(amountToBorrow);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/57_Flashloan_ja/test/UniswapV3Flashloan.t.sol b/Languages/ja/57_Flashloan_ja/test/UniswapV3Flashloan.t.sol
new file mode 100644
index 000000000..aa5d08235
--- /dev/null
+++ b/Languages/ja/57_Flashloan_ja/test/UniswapV3Flashloan.t.sol
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {Test, console2} from "forge-std/Test.sol";
+import "../src/UniswapV3Flashloan.sol";
+
+address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
+
+contract UniswapV3FlashloanTest is Test {
+ IWETH private weth = IWETH(WETH);
+
+ UniswapV3Flashloan private flashloan;
+
+ function setUp() public {
+ flashloan = new UniswapV3Flashloan();
+ }
+
+ function testFlashloan() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e18);
+
+ uint balBefore = weth.balanceOf(address(flashloan));
+ console2.logUint(balBefore);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 1 * 1e18;
+ flashloan.flashloan(amountToBorrow);
+ }
+
+ // 手数料が不足している場合、リバートする
+ function testFlashloanFail() public {
+ // WETHに交換し、フラッシュローンコントラクトに転送して手数料として使用
+ weth.deposit{value: 1e18}();
+ weth.transfer(address(flashloan), 1e17);
+ // フラッシュローン借入金額
+ uint amountToBorrow = 100 * 1e18;
+ // 手数料不足
+ vm.expectRevert();
+ flashloan.flashloan(amountToBorrow);
+ }
+}
\ No newline at end of file
diff --git a/Languages/ja/README.md b/Languages/ja/README.md
new file mode 100644
index 000000000..ab5bcc4c0
--- /dev/null
+++ b/Languages/ja/README.md
@@ -0,0 +1,195 @@
+
+
+**[中文](https://github.com/AmazingAng/WTF-Solidity) / [English](../en/README.md) / [Español](../es/README.md) / [Português Brasileiro](../pt-br/README.md) / [日本語](../ja/README.md)**
+
+# WTF Solidity
+
+最近、Solidityを再学習しており、詳細を確認しながら「WTF Solidity 超シンプル入門」を執筆しています。これは初心者向けのガイドで、毎週1〜3レッスンのペースで更新していきます。
+
+Twitter: [@WTFAcademy\_](https://twitter.com/WTFAcademy_) | [@0xAA_Science](https://twitter.com/0xAA_Science)
+
+コミュニティ: [Discord](https://discord.gg/5akcruXrsk) | [ウェブサイト: wtf.academy](https://wtf.academy)
+
+チュートリアルとコードはGitHubで公開されています: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity)
+
+**注記: WTF Solidityの日本語版は完全版であり、コミュニティによる継続的なレビューを受けています。**
+
+## 入門編
+
+**第1回: HelloWeb3 (3行のコード)**:[コード](./01_HelloWeb3_ja) | [チュートリアル](./01_HelloWeb3_ja/readme.md)
+
+**第2回: 値型**:[コード](./02_ValueTypes_ja) | [チュートリアル](./02_ValueTypes_ja/readme.md)
+
+**第3回: 関数 (external/internal/public/private, pure/view, payable)**:[コード](./03_Function_ja) | [チュートリアル](./03_Function_ja/readme.md)
+
+**第4回: 関数の返り値 (returns/return)**:[コード](./04_Return_ja) | [チュートリアル](./04_Return_ja/readme.md)
+
+**第5回: データの場所 (storage/memory/calldata)**:[コード](./05_DataStorage_ja) | [チュートリアル](./05_DataStorage_ja/readme.md)
+
+**第6回: 配列と構造体**:[コード](./06_ArrayAndStruct_ja) | [チュートリアル](./06_ArrayAndStruct_ja/readme.md)
+
+**第7回: マッピング**:[コード](./07_Mapping_ja) | [チュートリアル](./07_Mapping_ja/readme.md)
+
+**第8回: デフォルト値**:[コード](./08_InitialValue_ja) | [チュートリアル](./08_InitialValue_ja/readme.md)
+
+**第9回: 定数 (constant/immutable)**:[コード](./09_Constant_ja) | [チュートリアル](./09_Constant_ja/readme.md)
+
+**第10回: 制御フロー**:[コード](./10_InsertionSort_ja) | [チュートリアル](./10_InsertionSort_ja/readme.md)
+
+**第11回: 修飾子**:[コード](./11_Modifier_ja) | [チュートリアル](./11_Modifier_ja/readme.md)
+
+**第12回: イベント**:[コード](./12_Event_ja) | [チュートリアル](./12_Event_ja/readme.md)
+
+**第13回: 継承**:[コード](./13_Inheritance_ja) | [チュートリアル](./13_Inheritance_ja/readme.md)
+
+**第14回: インターフェース**:[コード](./14_Interface_ja) | [チュートリアル](./14_Interface_ja/readme.md)
+
+**第15回: エラー**:[コード](./15_Errors_ja) | [チュートリアル](./15_Errors_ja/readme.md)
+
+## 中級編
+
+**第16回: 関数のオーバーロード**:[コード](./16_Overloading_ja) | [チュートリアル](./16_Overloading_ja/readme.md)
+
+**第17回: ライブラリ**:[コード](./17_Library_ja) | [チュートリアル](./17_Library_ja/readme.md)
+
+**第18回: インポート**:[コード](./18_Import_ja) | [チュートリアル](./18_Import_ja/readme.md)
+
+**第19回: ETHの受信 (fallback/receive)**:[コード](./19_Fallback_ja) | [チュートリアル](./19_Fallback_ja/readme.md)
+
+**第20回: ETHの送信 (transfer/send/call)**:[コード](./20_SendETH_ja) | [チュートリアル](./20_SendETH_ja/readme.md)
+
+**第21回: 他のコントラクトの呼び出し**:[コード](./21_CallContract_ja) | [チュートリアル](./21_CallContract_ja/readme.md)
+
+**第22回: Call**:[コード](./22_Call_ja) | [チュートリアル](./22_Call_ja/readme.md)
+
+**第23回: Delegatecall**:[コード](./23_Delegatecall_ja) | [チュートリアル](./23_Delegatecall_ja/readme.md)
+
+**第24回: 他のコントラクト内でのコントラクト作成**:[コード](./24_Create_ja) | [チュートリアル](./24_Create_ja/readme.md)
+
+**第25回: Create2**:[コード](./25_Create2_ja) | [チュートリアル](./25_Create2_ja/readme.md)
+
+**第26回: コントラクトの削除**:[コード](./26_DeleteContract_ja) | [チュートリアル](./26_DeleteContract_ja/readme.md)
+
+**第27回: ABIエンコード/デコード**:[コード](./27_ABIEncode_ja) | [チュートリアル](./27_ABIEncode_ja/readme.md)
+
+**第28回: ハッシュ**:[コード](./28_Hash_ja) | [チュートリアル](./28_Hash_ja/readme.md)
+
+**第29回: 関数セレクタ**:[コード](./29_Selector_ja) | [チュートリアル](./29_Selector_ja/readme.md)
+
+**第30回: Try-Catch**:[コード](./30_TryCatch_ja) | [チュートリアル](./30_TryCatch_ja/readme.md)
+
+## 応用編
+
+**第31回: ERC20**:[コード](./31_ERC20_ja/) | [チュートリアル](./31_ERC20_ja/readme.md)
+
+**第32回: トークンフォーセット**:[コード](./32_Faucet_ja/) | [チュートリアル](./32_Faucet_ja/readme.md)
+
+**第33回: エアドロップ**:[コード](./33_Airdrop_ja/) | [チュートリアル](./33_Airdrop_ja/readme.md)
+
+**第34回: ERC721**:[コード](./34_ERC721_ja/) | [チュートリアル](./34_ERC721_ja/readme.md)
+
+**第35回: ダッチオークション**:[コード](./35_DutchAuction_ja/) | [チュートリアル](./35_DutchAuction_ja/readme.md)
+
+**第36回: マークルツリー**:[コード](./36_MerkleTree_ja/) | [チュートリアル](./36_MerkleTree_ja/readme.md)
+
+**第37回: デジタル署名**:[コード](./37_Signature_ja/) | [チュートリアル](./37_Signature_ja/readme.md)
+
+**第38回: NFT取引所**:[コード](./38_NFTSwap_ja/) | [チュートリアル](./38_NFTSwap_ja/readme.md)
+
+**第39回: ランダム数**:[コード](./39_Random_ja/) | [チュートリアル](./39_Random_ja/readme.md)
+
+**第40回: ERC1155**:[コード](./40_ERC1155_ja/) | [チュートリアル](./40_ERC1155_ja/readme.md)
+
+**第41回: WETH**:[コード](./41_WETH_ja/) | [チュートリアル](./41_WETH_ja/readme.md)
+
+**第42回: 支払い分割**:[コード](./42_PaymentSplit_ja/) | [チュートリアル](./42_PaymentSplit_ja/readme.md)
+
+**第43回: 線形ベスティング**:[コード](./43_TokenVesting_ja/) | [チュートリアル](./43_TokenVesting_ja/readme.md)
+
+**第44回: トークンロック**:[コード](./44_TokenLocker_ja/) | [チュートリアル](./44_TokenLocker_ja/readme.md)
+
+**第45回: タイムロック**:[コード](./45_Timelock_ja/) | [チュートリアル](./45_Timelock_ja/readme.md)
+
+## 上級編
+
+**第46回: プロキシコントラクト**:[コード](./46_ProxyContract_ja/) | [チュートリアル](./46_ProxyContract_ja/readme.md)
+
+**第47回: アップグレード可能コントラクト**:[コード](./47_Upgrade_ja/) | [チュートリアル](./47_Upgrade_ja/readme.md)
+
+**第48回: 透明プロキシ**:[コード](./48_TransparentProxy_ja/) | [チュートリアル](./48_TransparentProxy_ja/readme.md)
+
+**第49回: UUPS**:[コード](./49_UUPS_ja/) | [チュートリアル](./49_UUPS_ja/readme.md)
+
+**第50回: マルチシグウォレット**:[コード](./50_MultisigWallet_ja/) | [チュートリアル](./50_MultisigWallet_ja/readme.md)
+
+**第51回: ERC4626 トークン化ボールト**:[コード](./51_ERC4626_ja/) | [チュートリアル](./51_ERC4626_ja/readme.md)
+
+**第52回: EIP712 型付きデータ署名**:[コード](./52_EIP712_ja/) | [チュートリアル](./52_EIP712_ja/readme.md)
+
+**第53回: ERC20Permit**:[コード](./53_ERC20Permit_ja/) | [チュートリアル](./53_ERC20Permit_ja/readme.md)
+
+**第54回: クロスチェーンブリッジ**:[コード](./54_CrossChainBridge_ja/) | [チュートリアル](./54_CrossChainBridge_ja/readme.md)
+
+**第55回: マルチコール**:[コード](./55_MultiCall_ja/) | [チュートリアル](./55_MultiCall_ja/readme.md)
+
+**第56回: DEX(分散型取引所)**:[コード](./56_DEX_ja/) | [チュートリアル](./56_DEX_ja/readme.md)
+
+**第57回: フラッシュローン**:[コード](./57_Flashloan_ja/) | [チュートリアル](./57_Flashloan_ja/readme.md)
+
+## セキュリティ
+
+**第S1回: リエントランシー攻撃**:[コード](./S01_ReentrancyAttack_ja/) | [チュートリアル](./S01_ReentrancyAttack_ja/readme.md)
+
+**第S2回: セレクタークラッシュ**:[コード](./S02_SelectorClash_ja/) | [チュートリアル](./S02_SelectorClash_ja/readme.md)
+
+**第S3回: 中央集権化**:[コード](./S03_Centralization_ja/) | [チュートリアル](./S03_Centralization_ja/readme.md)
+
+**第S4回: アクセス制御の悪用**:[コード](./S04_AccessControlExploit_ja/) | [チュートリアル](./S04_AccessControlExploit_ja/readme.md)
+
+**第S5回: 整数オーバーフロー**:[コード](./S05_Overflow_ja/) | [チュートリアル](./S05_Overflow_ja/readme.md)
+
+**第S6回: 署名リプレイ**:[コード](./S06_SignatureReplay_ja/) | [チュートリアル](./S06_SignatureReplay_ja/readme.md)
+
+**第S7回: 悪質な乱数**:[コード](./S07_BadRandomness_ja/) | [チュートリアル](./S07_BadRandomness_ja/readme.md)
+
+**第S8回: コントラクト長チェックの回避**:[コード](./S08_ContractCheck_ja/) | [チュートリアル](./S08_ContractCheck_ja/readme.md)
+
+**第S9回: サービス拒否攻撃 (DoS)**:[コード](./S09_DoS_ja/) | [チュートリアル](./S09_DoS_ja/readme.md)
+
+**第S10回: ハニーポット**:[コード](./S10_Honeypot_ja/) | [チュートリアル](./S10_Honeypot_ja/readme.md)
+
+**第S11回: フロントランニング**:[コード](./S11_Frontrun_ja/) | [チュートリアル](./S11_Frontrun_ja/readme.md)
+
+**第S12回: tx.originフィッシング攻撃**:[コード](./S12_TxOrigin_ja/) | [チュートリアル](./S12_TxOrigin_ja/readme.md)
+
+**第S13回: 未チェック低レベル呼び出し**:[コード](./S13_UncheckedCall_ja/) | [チュートリアル](./S13_UncheckedCall_ja/readme.md)
+
+**第S14回: ブロックタイムスタンプ操作**:[コード](./S14_TimeManipulation_ja/) | [チュートリアル](./S14_TimeManipulation_ja/readme.md)
+
+**第S15回: オラクル操作**:[コード](./S15_OracleManipulation_ja/) | [チュートリアル](./S15_OracleManipulation_ja/readme.md)
+
+**第S16回: NFTリエントランシー攻撃**:[コード](./S16_NFTReentrancy_ja/) | [チュートリアル](./S16_NFTReentrancy_ja/readme.md)
+
+**第S17回: クロスリエントランシー**:[コード](./S17_CrossReentrancy_ja/) | [チュートリアル](./S17_CrossReentrancy_ja/readme.md)
+
+## WTF貢献者
+
+