はじめに
今回は「SolidityスマートコントラクトをOpenAPI/Swagger形式で仕様書化する方法」についてまとめていきます。
Solidityスマートコントラクトの開発において、ABI(Application Binary Interface)だけでは関数の目的やエラーの発生条件、イベントの意味を十分に伝えることが難しい場面があります。 特に、チーム開発やクライアントへの説明、フロントエンド開発者との連携において、より詳細で構造化されたドキュメントが求められます。
この記事では、SolidityスマートコントラクトをOpenAPI 3.0(YAML)およびSwagger 2.0(JSON)形式で仕様書化する方法を提案しています。 さらに、この仕様書生成を自動化するClaude Code Subagentの使い方も紹介します。
最終的には以下のようなサイト作成まで行ってくれます。
OpenAPI/Swagger形式で仕様書化するメリット
OpenAPI/Swagger形式で仕様書化することで、以下のメリットがあります。
- 標準化されたフォーマット APIドキュメントの業界標準であるOpenAPI/Swagger形式を使用することで、多くの開発者にとって馴染みのある形式で仕様を共有できる
- インタラクティブなドキュメント Swagger UIやRedocusaurusを使って、視覚的で操作可能なドキュメントサイトを構築可能
- 型情報の明確化 Solidityの型(address、uint256、bytes32など)をパターン付きで表現し、バリデーションルールを明示
- エラーハンドリングの可視化 カスタムエラーやrevertの条件を構造化して記述できる
- イベント・構造体の文書化 コントラクトの全要素を網羅的にドキュメント化可能
設計方針
目的と制約
この仕様書はHTTP API設計ではなく、Solidityコントラクトのインターフェイス定義をOpenAPI/Swagger形式で記述するためのものです。 HTTPのベストプラクティスに合わせるのではなく、Solidityの挙動を正確かつコンパクトに表現することを最優先にしています。
実際にHTTPで呼び出すことを前提としていません。 Swagger/OpenAPIの制約上、便宜的にパスとHTTPメソッドを割り当てています。
基本ルール
| 項目 | ルール |
|---|---|
| 成功レスポンス | 常にHTTP 200を使用 |
| エラーレスポンス | 常にHTTP 500を使用(4xx系や他の5xx系は使わない) |
| 説明文 | すべて日本語で記述 |
| 識別子の表記 | 変数名、関数名、エラー名などはバッククォート(`)で囲む |
| extensionフィールド | x- で始まるフィールドは使用しない |
識別子の表記ルール
description内で以下の要素に言及する時は、必ずバッククォート(`)で囲みます。
| 対象 | 例 |
|---|---|
| 関数名 | transfer、balanceOf |
| 変数名・引数名 | _to、amount、owner |
| エラー名 | InsufficientBalance、InvalidAddress |
| イベント名 | Transfer、Approval |
| 型名 | address、uint256 |
| modifier名 | onlyOwner、whenNotPaused |
| 定数名 | DECIMALS、MAX_SUPPLY |
# 正しい例
description: |
`_to` で指定されたアドレスに `amount` 分のトークンを転送します。
残高が不足している場合は `InsufficientBalance` エラーが発生します。
# 間違った例
description: |
_to で指定されたアドレスに amount 分のトークンを転送します。
残高が不足している場合は InsufficientBalance エラーが発生します。パス設計ルール
パスには関数名やイベント名をそのまま使用し、分かりやすい構造を維持します。
| 要素 | パス形式 | HTTPメソッド |
|---|---|---|
| view/pure関数 | /{functionName} |
GET |
| nonpayable/payable関数 | /{functionName} |
POST |
| 変数/定数/Mapping | /{variableName} |
GET |
| イベント | /events/{EventName} |
GET |
| エラー | /errors/{ErrorName} |
GET |
| 構造体 | /structs/{StructName} |
GET |
| Modifier | /modifiers/{ModifierName} |
GET |
summaryの書き方
summary フィールドには関数名をそのまま記載します。
「getter」や「を取得」といった冗長な表現は避けます。
# 正しい例
summary: "balanceOf"
summary: "transfer"
summary: "totalSupply"
# 間違った例(これらは使用しない)
summary: "balanceOf getter"
summary: "残高を取得する"
summary: "Get balance"パラメータの配置ルール
パラメータは常に query または path で定義し、requestBody は使用しません。
# 正しい例
parameters:
- name: owner
in: query
required: true
# 間違った例(requestBodyは使用しない)
requestBody:
content:
application/json:
schema:
properties:
owner:
type: string継承関係の表現
コントラクトの継承関係は info.description に記載します。
継承しているコントラクト名を箇条書きで列挙します。
info:
title: "MyToken Solidity Interface"
version: "1.0.0"
description: |
MyToken コントラクトの Solidity インターフェイスを OpenAPI 形式で表現した仕様です。
このコントラクトは以下のコントラクトを継承しています。
ERC20。
Ownable。
Pausable。継承元コントラクトの関数やイベント、エラーも仕様書に含めることで、コントラクトの全体像を把握できるようにします。
OpenAPI/Swagger形式でのコントラクト仕様書の構造
スマートコントラクトをOpenAPI/Swagger形式で表現する際の基本構造を説明します。
全体構成
openapi: 3.0.3
info:
title: "コントラクト名 Solidity Interface"
version: "1.0.0"
description: |
コントラクトの説明。
継承関係やネットワーク情報などをここに記載。
tags:
- name: Read Functions
description: Functions that retrieve information from the contract without modifying state.
- name: Write Functions
description: Functions that modify state.
- name: Variables
- name: Constants
- name: Mapping
- name: Events
- name: Errors
- name: Structs
- name: Modifier
x-tagGroups:
- name: Functions
tags:
- Read Functions
- Write Functions
- name: Storage
tags:
- Variables
- Constants
- Mapping
- name: Others
tags:
- Events
- Errors
- Structs
- Modifier
paths:
# 関数、イベント、エラー、構造体のパス定義
components:
schemas:
# ErrorResponse、イベントスキーマ、構造体スキーマタグによるカテゴリ分類
仕様書では以下の9つのタグでコントラクトの要素を分類します。
| タグ | 説明 |
|---|---|
| Read Functions | view/pure関数。ステートを変更せず情報を取得 |
| Write Functions | nonpayable/payable関数。ステートを変更 |
| Variables | publicなstate variable |
| Constants | constant/immutableな値 |
| Mapping | publicなmapping |
| Events | コントラクトで定義されたevent |
| Errors | カスタムエラー |
| Structs | struct定義 |
| Modifier | modifier定義 |
タグ名は英語で統一されています。
Read Functions と Write Functions のみ description が設定され、その他のタグは名前のみです。
各要素の表現方法
関数の表現
読み取り関数(view/pure)
読み取り関数はGETメソッドで表現し、引数はparameters(query/path)で定義します。
/balanceOf:
get:
summary: "balanceOf"
description: |
指定されたアカウントのトークン残高を取得します。
ERC20標準関数です。
operationId: balanceOf
tags:
- Mapping
parameters:
- name: owner
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "所有者のアドレスを指定します。"
example: "0x1234567890123456789012345678901234567890"
responses:
"200":
description: "正常終了時のレスポンスです。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "アカウントの残高を返します。"
example: "1000000000000000000"各フィールドの意味は以下の通りです。
| フィールド | 説明 |
|---|---|
summary |
関数名を指定。Swagger UIのサイドバーに表示される |
description |
関数の詳細な説明。マルチライン文字列(|)で複数行記述可能 |
operationId |
一意の識別子。通常は関数名をそのまま使用 |
tags |
関数の分類。Swagger UIでグループ化される |
parameters |
関数の引数。in: query でクエリパラメータとして表現 |
responses |
戻り値とエラー。成功時は 200、エラー時は 500 |
address 型は pattern: "^0x[0-9a-fA-F]{40}$" を指定することで、40桁の16進数文字列であることをバリデーションできます。
uint256 型はJavaScriptの数値範囲を超える可能性があるため、type: string として表現します。
読み取り関数でエラーが明示的に定義されていない場合は、200レスポンスのみを定義します。
書き込み関数(nonpayable/payable)
書き込み関数はPOSTメソッドで表現します。
/transfer:
post:
summary: "transfer"
description: |
指定されたアドレスにトークンを転送します。
ERC20標準関数です。
operationId: transfer
tags:
- 書き込み関数
parameters:
- name: to
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "送金先のアドレスを指定します。"
example: "0x1234567890123456789012345678901234567890"
- name: amount
in: query
required: true
schema:
type: string
description: "トークン量を指定します(wei単位)。"
example: "1000000000000000000"
responses:
"200":
description: "正常終了時のレスポンスです。"
content:
application/json:
schema:
type: object
properties:
result0:
type: string
description: "送金が成功した場合trueを返します。"
example: "true"
"500":
description: "この関数で発生しうるエラーの一覧です。\n\n・InsufficientBalance\n\n・InvalidAddress"
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
InsufficientBalance:
summary: "残高不足"
value:
message: "InsufficientBalance"
data: {}
InvalidAddress:
summary: "無効なアドレス"
value:
message: "InvalidAddress"
data: {}書き込み関数特有のフィールドは以下の通りです。
| フィールド | 説明 |
|---|---|
responses["500"] |
エラーレスポンス。description にエラー一覧を箇条書きで記載 |
examples |
各エラーの具体例。エラー名をキーに summary と value を指定 |
$ref |
共通スキーマの参照。#/components/schemas/ErrorResponse で形式を統一 |
エラーはすべてHTTP 500で表現し、200と500以外のステータスコードは使用しません。 これはSolidityのrevertやカスタムエラーを統一的に表現するためです。
エラーレスポンスの詳細
ErrorResponseスキーマ
すべてのエラーは共通の ErrorResponse スキーマを使用します。
components:
schemas:
ErrorResponse:
type: object
description: "コントラクト呼び出し時のエラー情報を表現する共通スキーマです。"
properties:
message:
type: string
description: "カスタムエラー名またはrevert/requireのメッセージ文字列。"
data:
type: object
description: "追加情報(任意)。"500レスポンスのdescription書き方
500レスポンスの description には、発生しうるエラーを ・ で始まる箇条書きで記載します。
"500":
description: "この関数で発生しうるエラーの一覧です。\n\n・InsufficientBalance\n\n・InvalidAddress\n\n・AmountZero"箇条書きの各項目の間には空行(\n\n)を入れます。
これにより、Swagger UIで見やすく表示されます。
examplesの書き方
各エラーの具体例は examples フィールドで定義します。
キーにはエラー名を使用し、summary には日本語の簡潔な説明を記載します。
examples:
InsufficientBalance:
summary: "残高不足"
value:
message: "InsufficientBalance"
data: {}
InvalidAddress:
summary: "無効なアドレス"
value:
message: "InvalidAddress"
data:
address: "0x0000000000000000000000000000000000000000"エラーに引数がある場合は、data にその値を含めます。
イベントの表現
イベントは/events/{EventName}パスで表現します。
/events/Transfer:
get:
summary: "Transfer"
description: |
トークンが転送された時に発行されるイベントです。
**パラメータ:**
- `from` (address) - indexed
- `to` (address) - indexed
- `value` (uint256)
operationId: event_Transfer
tags:
- イベント
responses:
"200":
description: "トークン転送イベント"
content:
application/json:
schema:
$ref: '#/components/schemas/TransferEvent'イベントのスキーマは components/schemas に定義します。
components:
schemas:
TransferEvent:
type: object
description: "Transfer イベント"
properties:
from:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "インデックス付き。送金元のアドレス。"
example: "0x1234567890123456789012345678901234567890"
to:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "インデックス付き。送金先のアドレス。"
example: "0x1234567890123456789012345678901234567890"
value:
type: string
description: "転送量(wei単位)。"
example: "1000000000000000000"イベントスキーマのフィールドは以下の通りです。
| フィールド | 説明 |
|---|---|
type |
スキーマの型。イベントは object を使用 |
description |
イベントの説明。どのような状況で発行されるかを記述 |
properties |
イベントの各パラメータ。indexed はdescriptionに明記 |
Solidityの indexed パラメータは、descriptionに「インデックス付き」と明記することで、ログのトピックとして検索可能であることを示します。
エラーの表現
カスタムエラーは/errors/{ErrorName}パスで表現します。
/errors/InsufficientBalance:
get:
summary: "InsufficientBalance"
description: |
残高が不足している時に返されるエラーです。
操作に必要な残高を確保してください。
operationId: error_InsufficientBalance
tags:
- エラー
responses:
"200":
description: "残高不足エラー"
content:
application/json:
schema:
type: object
properties:
name:
type: string
example: "InsufficientBalance"変数・定数の表現
publicな変数や定数は、自動生成されるgetterとして表現します。
descriptionには必ず変数宣言そのものをコードブロックで含め、その後に説明を記述します。
/totalSupply:
get:
summary: "totalSupply"
description: |
```solidity
uint256 public totalSupply;
```
トークンの総供給量を取得します。
ERC20標準関数です。
operationId: totalSupply
tags:
- 変数
responses:
"200":
description: "正常終了時のレスポンスです。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "総供給量を返します。"
example: "1000000000000000000"変数宣言を含めることで、型や可視性が一目で分かるようになります。
定数(constant)やイミュータブル変数(immutable)の場合も同様に宣言を含めます。
description: |
```solidity
uint8 public constant DECIMALS = 18;
```
トークンの小数点以下の桁数です。Mappingの表現
Mappingも変数と同様に、descriptionには型定義をコードブロックで含めます。
/allowance:
get:
summary: "allowance"
description: |
```solidity
mapping(address owner => mapping(address spender => uint256)) public allowance;
```
`spender` が `owner` から使用を許可されているトークン量を取得します。
ERC20標準関数です。
operationId: allowance
tags:
- Mapping
parameters:
- name: owner
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "所有者のアドレスを指定します。"
example: "0x1234567890123456789012345678901234567890"
- name: spender
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "トークンの使用を許可するアドレスを指定します。"
example: "0x1234567890123456789012345678901234567890"
responses:
"200":
description: "正常終了時のレスポンスです。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "許可量を返します。"
example: "1000000000000000000"構造体の表現
/structs/ERC20Storage:
get:
summary: "ERC20Storage"
description: |
ERC20のストレージ構造体です。
**フィールド:**
- _name (string)
- _symbol (string)
operationId: struct_ERC20Storage
tags:
- 構造体
responses:
"200":
description: "ERC20Storage構造体"
content:
application/json:
schema:
$ref: '#/components/schemas/ERC20StorageStruct'構造体のスキーマは components/schemas に定義します。
components:
schemas:
ERC20StorageStruct:
type: object
description: "ERC20Storage 構造体"
properties:
_name:
type: string
description: "トークン名"
example: "Example Token"
_symbol:
type: string
description: "トークンシンボル"
example: "EXT"構造体スキーマのフィールドは以下の通りです。
| フィールド | 説明 |
|---|---|
type |
スキーマの型。構造体は object を使用 |
description |
構造体の用途や役割の説明 |
properties |
構造体の各フィールド。Solidityの変数名をキー名として使用 |
Modifierの表現
Modifierは /modifiers/{ModifierName} パスで定義します。
/modifiers/onlyOwner:
get:
summary: "onlyOwner"
description: |
```solidity
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
```
このmodifierは、関数の実行者がコントラクトのオーナーであることを確認します。
オーナー以外が実行した場合はrevertします。
operationId: modifier_onlyOwner
tags:
- Modifier
responses:
"200":
description: "onlyOwner modifier"Modifierが付与された関数のdescription
関数にmodifierが付与されている場合、その効果を関数の description に追記します。
/mint:
post:
summary: "mint"
description: |
新しいトークンを発行します。
:::note info
**Modifier**: この関数は `onlyOwner` modifierにより保護されています。
コントラクトのオーナーのみが実行可能です。
:::
operationId: mint
tags:
- 書き込み関数複数のmodifierが付与されている場合は、すべてのmodifierの効果を記載します。
Solidityの型マッピング
| Solidity型 | OpenAPI表現 |
|---|---|
address |
type: string, pattern: "^0x[0-9a-fA-F]{40}$" |
uint256 / int256 |
type: string(大きな数値のため) |
bytes32 |
type: string, pattern: "^0x[0-9a-fA-F]{64}$" |
bytes4 |
type: string, pattern: "^0x[0-9a-fA-F]{8}$" |
bool |
type: boolean |
string |
type: string |
bytes |
type: string |
| 配列 | type: array, items: {...} |
サンプル仕様書(ERC20)
以下は、ERC20トークンコントラクトの完全なOpenAPI仕様書サンプルです。
# file: docs/contract/ERC20/ERC20.openapi.yaml
# Generated by: contract-spec-generator skill
openapi: 3.0.3
info:
title: "ERC20 Solidityインターフェース"
version: "1.0.0"
description: |
ERC20コントラクトのSolidityインターフェースをOpenAPI形式で表現した仕様書です。
このコントラクトは以下のコントラクトを継承しています。
Context, IERC20, IERC20Metadata, IERC20Errors
ERC20トークン標準の実装です。転送、承認、残高追跡などの代替可能トークンの基本機能を提供します。
tags:
- name: Read Functions
description: コントラクトの状態を変更せずに情報を取得する関数です。
- name: Write Functions
description: コントラクトの状態を変更する関数です。
- name: Events
- name: Errors
x-tagGroups:
- name: 関数
tags:
- Read Functions
- Write Functions
- name: イベント・エラー
tags:
- Events
- Errors
paths:
/name:
get:
tags:
- Read Functions
summary: "name"
description: |
トークンの名前を返します。
ERC20Metadata標準関数です。
operationId: name
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "トークン名 (Solidity: string)。"
example: "MyToken"
/symbol:
get:
tags:
- Read Functions
summary: "symbol"
description: |
トークンのシンボルを返します。通常は名前の短縮版です。
ERC20Metadata標準関数です。
operationId: symbol
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "トークンシンボル (Solidity: string)。"
example: "MTK"
/decimals:
get:
tags:
- Read Functions
summary: "decimals"
description: |
ユーザー表示用の小数点桁数を返します。
例えば、`decimals`が`2`の場合、`505`トークンの残高はユーザーに`5.05`(`505 / 10 ** 2`)として表示されるべきです。
トークンは通常18の値を選択し、EtherとWeiの関係を模倣しています。これがこの関数が返すデフォルト値です。
この情報は表示目的のみに使用され、コントラクトの算術には一切影響しません。
operationId: decimals
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: integer
minimum: 0
maximum: 255
description: "小数点桁数 (Solidity: uint8)。"
example: 18
/totalSupply:
get:
tags:
- Read Functions
summary: "totalSupply"
description: |
存在するトークンの総供給量を返します。
ERC20標準関数です。
operationId: totalSupply
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "トークン総供給量 (Solidity: uint256)。"
example: "1000000000000000000000000"
/balanceOf:
get:
tags:
- Read Functions
summary: "balanceOf"
description: |
指定されたアカウントのトークン残高を返します。
ERC20標準関数です。
operationId: balanceOf
parameters:
- name: account
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "残高を照会するアドレス (Solidity: address)。"
example: "0x1234567890123456789012345678901234567890"
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "トークン残高 (Solidity: uint256)。"
example: "1000000000000000000"
/allowance:
get:
tags:
- Read Functions
summary: "allowance"
description: |
transferFromを通じて`spender`が`owner`の代わりに使用できる残りのトークン数を返します。
ERC20標準関数です。
operationId: allowance
parameters:
- name: owner
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "トークンを所有するアドレス (Solidity: address)。"
example: "0x1234567890123456789012345678901234567890"
- name: spender
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "トークンの使用を承認されたアドレス (Solidity: address)。"
example: "0x0987654321098765432109876543210987654321"
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: string
description: "残りの許可量 (Solidity: uint256)。"
example: "500000000000000000000"
/transfer:
post:
tags:
- Write Functions
summary: "transfer"
description: |
呼び出し元のアカウントから`to`に`value`量のトークンを移動します。
操作が成功したかどうかを示すブール値を返します。
この関数はTransferを発行する可能性があります。
**要件:**
- `to`はゼロアドレスであってはなりません。
- 呼び出し元は少なくとも`value`の残高を持っている必要があります。
operationId: transfer
parameters:
- name: to
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "受信者アドレス (Solidity: address)。"
example: "0x0987654321098765432109876543210987654321"
- name: value
in: query
required: true
schema:
type: string
description: "転送するトークン量 (Solidity: uint256)。"
example: "1000000000000000000"
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: boolean
description: "転送が成功した場合trueを返す (Solidity: bool)。"
example: true
"500":
description: |
この関数で発生する可能性のあるエラー一覧。
・ERC20InvalidSender
・ERC20InvalidReceiver
・ERC20InsufficientBalance
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
ERC20InvalidSender:
summary: "ERC20InvalidSender"
description: "送信者アドレスが`address(0)`の場合に返されます。送信者が無効であることを示します。"
value:
message: "ERC20InvalidSender"
data:
sender: "0x0000000000000000000000000000000000000000"
ERC20InvalidReceiver:
summary: "ERC20InvalidReceiver"
description: "`to`が`address(0)`の場合に返されます。受信者が無効であることを示します。"
value:
message: "ERC20InvalidReceiver"
data:
receiver: "0x0000000000000000000000000000000000000000"
ERC20InsufficientBalance:
summary: "ERC20InsufficientBalance"
description: "呼び出し元の残高が`value`未満の場合に返されます。転送に必要な残高が不足していることを示します。"
value:
message: "ERC20InsufficientBalance"
data:
sender: "0x1234567890123456789012345678901234567890"
balance: "100"
needed: "1000"
/approve:
post:
tags:
- Write Functions
summary: "approve"
description: |
呼び出し元のトークンに対する`spender`の許可量として`value`量のトークンを設定します。
操作が成功したかどうかを示すブール値を返します。
`value`が最大の`uint256`の場合、transferFromで許可量は更新されません。これは無限の承認と意味的に同等です。
この関数はApprovalを発行する可能性があります。
**要件:**
- `spender`はゼロアドレスであってはなりません。
operationId: approve
parameters:
- name: spender
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "トークンの使用を承認するアドレス (Solidity: address)。"
example: "0x0987654321098765432109876543210987654321"
- name: value
in: query
required: true
schema:
type: string
description: "承認するトークン量 (Solidity: uint256)。"
example: "1000000000000000000"
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: boolean
description: "承認が成功した場合trueを返す (Solidity: bool)。"
example: true
"500":
description: |
この関数で発生する可能性のあるエラー一覧。
・ERC20InvalidApprover
・ERC20InvalidSpender
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
ERC20InvalidApprover:
summary: "ERC20InvalidApprover"
description: "承認者(所有者)が`address(0)`の場合に返されます。承認者が無効であることを示します。"
value:
message: "ERC20InvalidApprover"
data:
approver: "0x0000000000000000000000000000000000000000"
ERC20InvalidSpender:
summary: "ERC20InvalidSpender"
description: "`spender`が`address(0)`の場合に返されます。spenderが無効であることを示します。"
value:
message: "ERC20InvalidSpender"
data:
spender: "0x0000000000000000000000000000000000000000"
/transferFrom:
post:
tags:
- Write Functions
summary: "transferFrom"
description: |
許可メカニズムを使用して`from`から`to`に`value`量のトークンを移動します。`value`は呼び出し元の許可量から差し引かれます。
操作が成功したかどうかを示すブール値を返します。
現在の許可量が最大の`uint256`の場合、許可量は更新されません。
この関数はTransferを発行する可能性があります。
**要件:**
- `from`と`to`はゼロアドレスであってはなりません。
- `from`は少なくとも`value`の残高を持っている必要があります。
- 呼び出し元は`from`のトークンに対して少なくとも`value`の許可量を持っている必要があります。
operationId: transferFrom
parameters:
- name: from
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "トークンの転送元アドレス (Solidity: address)。"
example: "0x1234567890123456789012345678901234567890"
- name: to
in: query
required: true
schema:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "受信者アドレス (Solidity: address)。"
example: "0x0987654321098765432109876543210987654321"
- name: value
in: query
required: true
schema:
type: string
description: "転送するトークン量 (Solidity: uint256)。"
example: "1000000000000000000"
responses:
"200":
description: "正常完了時のレスポンス。"
content:
application/json:
schema:
type: object
properties:
result:
type: boolean
description: "転送が成功した場合trueを返す (Solidity: bool)。"
example: true
"500":
description: |
この関数で発生する可能性のあるエラー一覧。
・ERC20InvalidSender
・ERC20InvalidReceiver
・ERC20InsufficientBalance
・ERC20InsufficientAllowance
・ERC20InvalidApprover
・ERC20InvalidSpender
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
ERC20InvalidSender:
summary: "ERC20InvalidSender"
description: "`from`が`address(0)`の場合に返されます。送信者が無効であることを示します。"
value:
message: "ERC20InvalidSender"
data:
sender: "0x0000000000000000000000000000000000000000"
ERC20InvalidReceiver:
summary: "ERC20InvalidReceiver"
description: "`to`が`address(0)`の場合に返されます。受信者が無効であることを示します。"
value:
message: "ERC20InvalidReceiver"
data:
receiver: "0x0000000000000000000000000000000000000000"
ERC20InsufficientBalance:
summary: "ERC20InsufficientBalance"
description: "`from`の残高が`value`未満の場合に返されます。転送に必要な残高が不足していることを示します。"
value:
message: "ERC20InsufficientBalance"
data:
sender: "0x1234567890123456789012345678901234567890"
balance: "100"
needed: "1000"
ERC20InsufficientAllowance:
summary: "ERC20InsufficientAllowance"
description: "呼び出し元の許可量が`value`未満の場合に返されます。許可量が不足していることを示します。"
value:
message: "ERC20InsufficientAllowance"
data:
spender: "0xabcdef1234567890abcdef1234567890abcdef12"
allowance: "100"
needed: "1000"
ERC20InvalidApprover:
summary: "ERC20InvalidApprover"
description: "内部承認更新時に承認者(所有者)が`address(0)`の場合に返されます。承認者が無効であることを示します。"
value:
message: "ERC20InvalidApprover"
data:
approver: "0x0000000000000000000000000000000000000000"
ERC20InvalidSpender:
summary: "ERC20InvalidSpender"
description: "内部承認更新時に`spender`が`address(0)`の場合に返されます。spenderが無効であることを示します。"
value:
message: "ERC20InvalidSpender"
data:
spender: "0x0000000000000000000000000000000000000000"
/events/Transfer:
get:
tags:
- Events
summary: "Transfer"
description: |
`value`トークンが1つのアカウント(`from`)から別のアカウント(`to`)に移動されたときに発行されます。
`value`がゼロの場合もあります。
ERC20標準イベントです。
operationId: event_Transfer
responses:
"200":
description: "イベントスキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/TransferEvent"
/events/Approval:
get:
tags:
- Events
summary: "Approval"
description: |
approveの呼び出しによって`owner`の`spender`に対する許可量が設定されたときに発行されます。`value`は新しい許可量です。
ERC20標準イベントです。
operationId: event_Approval
responses:
"200":
description: "イベントスキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/ApprovalEvent"
/errors/ERC20InsufficientBalance:
get:
tags:
- Errors
summary: "ERC20InsufficientBalance"
description: |
送信者の現在の残高に関連するエラーを示します。転送で使用されます。
operationId: error_ERC20InsufficientBalance
responses:
"200":
description: "エラースキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/ERC20InsufficientBalanceError"
/errors/ERC20InvalidSender:
get:
tags:
- Errors
summary: "ERC20InvalidSender"
description: |
トークン送信者の失敗を示します。転送で使用されます。
operationId: error_ERC20InvalidSender
responses:
"200":
description: "エラースキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/ERC20InvalidSenderError"
/errors/ERC20InvalidReceiver:
get:
tags:
- Errors
summary: "ERC20InvalidReceiver"
description: |
トークン受信者の失敗を示します。転送で使用されます。
operationId: error_ERC20InvalidReceiver
responses:
"200":
description: "エラースキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/ERC20InvalidReceiverError"
/errors/ERC20InsufficientAllowance:
get:
tags:
- Errors
summary: "ERC20InsufficientAllowance"
description: |
spenderの許可量の失敗を示します。転送で使用されます。
operationId: error_ERC20InsufficientAllowance
responses:
"200":
description: "エラースキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/ERC20InsufficientAllowanceError"
/errors/ERC20InvalidApprover:
get:
tags:
- Errors
summary: "ERC20InvalidApprover"
description: |
承認されるトークンの承認者の失敗を示します。承認で使用されます。
operationId: error_ERC20InvalidApprover
responses:
"200":
description: "エラースキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/ERC20InvalidApproverError"
/errors/ERC20InvalidSpender:
get:
tags:
- Errors
summary: "ERC20InvalidSpender"
description: |
承認されるspenderの失敗を示します。承認で使用されます。
operationId: error_ERC20InvalidSpender
responses:
"200":
description: "エラースキーマ"
content:
application/json:
schema:
$ref: "#/components/schemas/ERC20InvalidSpenderError"
components:
schemas:
ErrorResponse:
type: object
description: "コントラクト呼び出し時のエラー情報を表現する共通スキーマ。"
properties:
message:
type: string
description: "カスタムエラー名またはrevert/requireメッセージ文字列をそのまま格納します。"
data:
type: object
description: "追加情報(オプション)。"
TransferEvent:
type: object
description: "トークンが転送されたときに発行されます。ゼロ値の転送を含みます。"
properties:
from:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "indexed: true。トークンの転送元アドレス (Solidity: address)。"
to:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "indexed: true。トークンの転送先アドレス (Solidity: address)。"
value:
type: string
description: "indexed: false。転送されたトークン量 (Solidity: uint256)。"
ApprovalEvent:
type: object
description: "所有者のspenderに対する許可量が設定されたときに発行されます。"
properties:
owner:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "indexed: true。トークンを所有するアドレス (Solidity: address)。"
spender:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "indexed: true。使用を承認されたアドレス (Solidity: address)。"
value:
type: string
description: "indexed: false。新しい許可量 (Solidity: uint256)。"
ERC20InsufficientBalanceError:
type: object
description: "送信者の現在の残高に関連するエラーを示します。"
properties:
sender:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "残高不足のアドレス (Solidity: address)。"
balance:
type: string
description: "現在の残高 (Solidity: uint256)。"
needed:
type: string
description: "必要な残高 (Solidity: uint256)。"
ERC20InvalidSenderError:
type: object
description: "トークン送信者の失敗を示します。"
properties:
sender:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "無効な送信者アドレス (Solidity: address)。"
ERC20InvalidReceiverError:
type: object
description: "トークン受信者の失敗を示します。"
properties:
receiver:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "無効な受信者アドレス (Solidity: address)。"
ERC20InsufficientAllowanceError:
type: object
description: "spenderの許可量の失敗を示します。"
properties:
spender:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "許可量不足のspenderアドレス (Solidity: address)。"
allowance:
type: string
description: "現在の許可量 (Solidity: uint256)。"
needed:
type: string
description: "必要な許可量 (Solidity: uint256)。"
ERC20InvalidApproverError:
type: object
description: "トークンの承認者の失敗を示します。"
properties:
approver:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "無効な承認者アドレス (Solidity: address)。"
ERC20InvalidSpenderError:
type: object
description: "承認されるspenderの失敗を示します。"
properties:
spender:
type: string
pattern: "^0x[0-9a-fA-F]{40}$"
description: "無効なspenderアドレス (Solidity: address)。"Contract Spec Generator Subagent
ここまで説明した仕様書を手動で作成するのは大変です。 Claude Codeで、この作業を自動化するContract Spec Generator Subagentというsubagentを作成しました。
このSubagentを使用すると、プロンプトを渡すだけで仕様書の生成からドキュメントサイトの構築まで自動で実行されます。 ユーザーはSubagentからの質問に答えるだけで、複雑な設定やコマンド実行を意識する必要はありません。
AgentとSkillの構成
Contract Spec Generatorは、AgentとSkillの2つのファイルで構成されています。
.claude/
├── agents/
│ └── contract-spec-generator.md # Agent定義(ワークフロー制御)
└── skills/
└── contract-spec-generator/
├── SKILL.md # Skill定義(生成ルール・仕様)
└── references/
├── generate-openapi-specs.js # スケルトン生成スクリプト
├── generate-contract-docs.js # Markdownドキュメント生成スクリプト
├── spec-prompt.md # 仕様書生成ルール
├── docusaurus-setup.md # サイト構築ガイド
├── sidebars.template.js # サイドバーテンプレート
└── custom.css # CSSテンプレートAgent 対話的なワークフローを制御します。 ユーザーへの確認、フェーズ遷移、コマンド実行などを担当します。
Skill 仕様書生成のルールと手順を定義します。 タグ分類、型マッピング、エラーハンドリングなどの詳細な仕様を記述しています。
機能概要
Contract Spec Generator Subagentは、SolidityのABIとソースコードからOpenAPI/Swagger仕様書を自動生成します。 以下の3フェーズで構成される対話的なワークフローを実行します。
| フェーズ | 内容 | ユーザー確認 |
|---|---|---|
| Phase 1 | 仕様書生成(スケルトン生成 + AI拡張) | なし |
| Phase 2 | Docusaurusサイト構築 + ローカル起動 | あり |
| Phase 3 | Vercelデプロイ | あり |
各フェーズの完了後にSubagentがユーザーに確認を行い、承認された場合のみ次のフェーズに進みます。 仕様書ファイルのみが必要な場合はPhase 1で終了し、サイト構築やデプロイが必要な場合は続行できます。
3フェーズワークフロー
Phase 1: 仕様書生成
仕様書生成は2つのステージで構成されています。
Stage 1: スケルトン生成
Node.jsスクリプトを使用して、すべてのコントラクトの基本構造を一括生成します。
ERC20標準関数やPausable、AccessControlなどの一般的なパターンには説明が自動で付与されます。
型マッピングも自動で行われ、address 型には正規表現パターンが、uint256 型には適切な例が設定されます。
書き込み関数には共通エラーが自動追加され、イベント、エラー、構造体のパスと基本スキーマも生成されます。
Stage 2: AI拡張
スケルトン生成後、Subagentがソースコードを詳細に解析し、以下の拡張を行います。
- 内部関数呼び出しを追跡して発生しうるエラーを500レスポンスに追加
- 関数説明にmodifierの効果を追記
- 汎用的な説明をビジネスロジックに基づいた詳細な日本語説明に置換
- パラメータに型情報だけでなく意味のある説明を追加
Phase 2: サイト構築
Phase 1完了後、Subagentが「ドキュメントサイトを構築しますか?」と確認します。 承認すると以下の処理が自動実行されます。
- Docusaurusプロジェクトの作成(既存の場合は更新)
- Redocusaurusプラグインのインストール
generate-contract-docs.jsによるMarkdownドキュメント生成- ローカル開発サーバーの起動(
http://localhost:3000)
Phase 3: Vercelデプロイ
ローカル確認後、Subagentが「Vercelにデプロイしますか?」と確認します。 3つの選択肢から選べます。
- CLI Vercel CLIでデプロイを自動実行
- 手動 Vercelダッシュボードからのデプロイ手順を出力
- いいえ ローカル環境のみで終了
使い方
Subagentの呼び出し
Claude Codeで以下のようにSubagentを呼び出します。 呼び出し時には、ABIファイルのパスとコントラクトソースコードのパスを指定します。
ABIは contract/out/MyToken.sol/MyToken.json、コントラクトは contract/src/MyToken.sol にあります。contract-spec-generator agentを使用して仕様書を作成してください。もしくは以下のようにディレクトリのパスを渡します。
ABIは contract/out、コントラクトは contract/src にあります。contract-spec-generator agentを使用して仕様書を作成してください。プロンプトを渡すと、Subagentが質問しながら以下の処理を自動で実行します。 ユーザーは質問に答えるだけで、複雑なコマンド実行を意識する必要はありません。
必要な入力情報
Subagentを呼び出す時は、以下の情報を渡す必要があります。
| 情報 | 説明 | 例 |
|---|---|---|
| ABIファイルのパス | コンパイル済みのABI(JSON形式) | contract/out/MyToken.sol/MyToken.json |
| コントラクトソースのパス | Solidityソースコード | contract/src/MyToken.sol |
ABIファイルはビルドツールによって出力先が異なります。 事前にコントラクトをビルドしてABIを生成しておいてください。
自動実行される処理の詳細
Step 1: コントラクトのビルドとABI生成(事前準備)
Subagentを呼び出す前に、コントラクトをビルドしてABIを生成しておく必要があります。 ABIにはコントラクトの関数シグネチャ、イベント、エラー、構造体の情報が含まれており、これが仕様書生成の基礎データとなります。
使用するビルドツールに応じて、以下のコマンドでABIを生成します。
Foundry(forge)の場合
cd contract
forge build生成されたABIは out/{ContractName}.sol/{ContractName}.json に配置されます。
Hardhatの場合
cd contract
npx hardhat compile生成されたABIは artifacts/contracts/{ContractName}.sol/{ContractName}.json に配置されます。
Step 2: スケルトン生成スクリプトの実行
generate-openapi-specs.js スクリプトがABIとソースコードを解析し、OpenAPI/Swagger仕様書の骨格を一括生成します。
node generate-openapi-specs.jsこのスクリプトは以下の処理を行います。
| 処理 | 内容 |
|---|---|
| ABI解析 | forge inspect コマンドでABIを取得し、関数・イベント・エラーを抽出 |
| ソースコード解析 | 継承関係、構造体、modifier、状態変数、定数を正規表現で抽出 |
| 型マッピング | address → pattern: "^0x[0-9a-fA-F]{40}$"、uint256 → type: string など自動変換 |
| 説明文生成 | ERC20標準関数やAccessControlなど一般的なパターンには日本語説明を自動付与 |
| エラー追加 | 書き込み関数には InvalidAddress、AmountZero などの共通エラーを自動追加 |
出力は docs/contract/{ContractName}/{ContractName}.openapi.yaml と .swagger.json に配置されます。
Step 3: AI拡張による詳細情報の追加
スケルトン生成後、Subagentがソースコードを詳細に解析し、以下の拡張を行います。
内部関数エラー追跡
外部関数が内部で呼び出す関数を追跡し、発生しうるエラーを500レスポンスに追加します。
たとえば transferFrom() が内部で _spendAllowance() を呼び出し、そこで InsufficientAllowance エラーが発生する場合、そのエラーを transferFrom() の500レスポンスに追加します。
Modifier情報の追記
関数に付与されているmodifierの効果を説明に追記します。
onlyRole(ADMIN_ROLE) が付いている関合、「この関数はADMIN_ROLEを持つアカウントのみ実行可能です。」といった説明が追加されます。
コントラクト固有の説明
汎用的な説明をビジネスロジックに基づいた詳細な日本語説明に置換します。 たとえば「トークンを転送します」という説明を「指定されたアドレスにステーブルコインを転送します。送金元の残高が不足している場合はInsufficientBalanceエラーが発生します。」のように具体化します。
Step 4: Docusaurusサイトの構築
仕様書の作成が完了すると、Subagentから以下のような確認メッセージが表示されます。
ドキュメントサイトを構築しますか?「はい」と答えると、サイト構築が開始されます。 「いいえ」と答えると、仕様書ファイルのみが生成された状態で処理が終了します。
サイト構築を承認すると、以下の処理が自動実行されます。
Docusaurusプロジェクトが存在しない場合は新規作成し、Redocusaurusプラグインをインストールします。
docusaurus.config.js には、docs/contract/ 配下のすべてのコントラクトを自動検出して登録するヘルパー関数が設定されます。
さらに generate-contract-docs.js スクリプトが実行され、各コントラクトのMarkdownドキュメントが生成されます。
これにより、Redocによるインタラクティブな仕様書表示と、人間が読みやすいMarkdownドキュメントの両方が揃います。
Step 5: ローカルプレビューの起動
サイト構築後、npm run start で開発サーバーが起動し、http://localhost:3000 でドキュメントサイトをプレビューできます。
サイトは以下の構成になっています。
| ページ | パス | 内容 |
|---|---|---|
| トップページ | / |
カテゴリ別コントラクトカード一覧 |
| 仕様書 | /api/{ContractName} |
Redocによるインタラクティブな仕様書表示 |
| ドキュメント | /docs/contracts/{ContractName} |
Markdown形式の詳細ドキュメント |
Step 6: Vercelデプロイ(オプション)
プレビュー確認後、Subagentから以下のような確認メッセージが表示されます。
Vercelにデプロイしますか?
- はい(CLI): Vercel CLIでデプロイ
- はい(手動): 手順を出力(CLIを使いたくない場合)
- いいえ: ローカル環境のみで終了CLIでデプロイする場合
「はい(CLI)」と答えると、以下の処理が自動実行されます。
- 仕様書ファイルを
docs-site/specs/にコピー docusaurus.config.jsのパスを更新vercel --prodコマンドでデプロイ
デプロイ完了後、https://{project-name}.vercel.app/ でサイトが公開されます。
手動でデプロイする場合
「はい(手動)」と答えると、Vercelダッシュボードからデプロイする手順が出力されます。 GitHubリポジトリからのインポートや、フォルダのアップロードによるデプロイが可能です。
生成される成果物
Subagentは以下の成果物を生成します。
仕様書ファイル
各コントラクトについて、OpenAPI 3.0形式のYAMLファイルとSwagger 2.0形式のJSONファイルが生成されます。
これらは docs/contract/{ContractName}/ ディレクトリに配置されます。
ドキュメントサイト
Docusaurusベースのドキュメントサイトが docs-site/ ディレクトリに構築されます。
Redocusaurusプラグインにより、生成した仕様書がインタラクティブなAPIドキュメントとして表示されます。
各コントラクトのMarkdownドキュメントも docs-site/docs/contracts/ に生成され、概要・継承関係・主要機能・関数一覧が記載されます。
サイトの構成
生成されたドキュメントサイトは以下のようになります。
ドキュメントページには、コントラクトごとに主要関数の説明などが記載されています。
そして、各コントラクトごとに仕様書ページがあります。
ページ構成
| ページ | パス | 内容 |
|---|---|---|
| トップページ | / |
カテゴリ別コントラクトカード一覧 |
| 概要ページ | /overview |
システム全体の説明、コントラクト一覧 |
| ドキュメント | /docs/contracts/* |
各コントラクトの詳細ドキュメント(Markdown) |
| 仕様書 | /api/* |
OpenAPI/Swagger(Redoc表示) |
ヘッダー構成
| 位置 | 要素 | リンク先 |
|---|---|---|
| 左端 | Logo + プロジェクト名 | / |
| 2番目 | Docs | /overview(システム概要) |
| 3番目以降 | カテゴリドロップダウン | /api/{ContractName}(ドキュメント) |
| 右端 | GitHub | リポジトリURL |
トップページ
カテゴリ別にコントラクトカードが表示され、「仕様書を見る」ボタンから各コントラクトの詳細仕様にアクセスできます。
ヒーロー部分にはタイトルとサブタイトルのみを表示し、ボタンは配置しません。 カテゴリ名は中央寄せで表示されます。
API仕様ページ
Redocusaurusによるインタラクティブな仕様書表示。 サイドバーに関数、イベント、エラー、構造体が分類表示されます。
ドキュメントページ
Markdown形式のドキュメント。 概要、継承関係、主要機能、関数一覧などが記載されます。
各ドキュメントページの先頭には、対応するAPI仕様書へのリンクが配置されます。
> **[📋 API仕様書を見る](/api/{ContractName})**概要ページ
システム全体の説明と、すべてのコントラクトの一覧を表示します。 各コントラクトについて、ドキュメントと仕様書の両方へのリンクが提供されます。
最後に
今回は「SolidityスマートコントラクトをOpenAPI/Swagger形式で仕様書化する方法」についてまとめてきました。



