# Solidityの仕様書をOpenAPI+Swagger形式で定義してみる

> この仕様書はHTTP API設計ではなく、Solidityコントラクトのインターフェイス定義をOpenAPI/Swagger形式で記述するためのものです。

- URL: https://cardene.dev/articles/solidity-openapi-spec
- 公開日: 2026-01-09
- 更新日: 2026-01-09
- トピック: Blockchain
- タグ: solidity, openapi, swagger, claude

# はじめに

初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。

https://www.amazon.co.jp/dp/B0DQDWM1YK

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください！

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://twitter.com/cardene777

https://qiita.com/cardene

https://cardene.substack.com/

https://mirror.xyz/0xACAc709e21e7a4A5a4458BDeeCCA0fc98A6a8A4D

今回は「**SolidityスマートコントラクトをOpenAPI/Swagger形式で仕様書化する方法**」についてまとめていきます。

Solidityスマートコントラクトの開発において、ABI（Application Binary Interface）だけでは関数の目的やエラーの発生条件、イベントの意味を十分に伝えることが難しい場面があります。
特に、チーム開発やクライアントへの説明、フロントエンド開発者との連携において、より詳細で構造化されたドキュメントが求められます。

この記事では、SolidityスマートコントラクトをOpenAPI 3.0（YAML）形式で仕様書化する方法を提案しています。

## OpenAPI/Swagger形式で仕様書化するメリット

OpenAPI/Swagger形式で仕様書化することで、以下のメリットがあります。

- **標準化されたフォーマット**
  APIドキュメントの業界標準であるOpenAPI/Swagger形式を使用することで、多くの開発者にとって馴染みのある形式で仕様を共有できます。
- **インタラクティブなドキュメント**
  Swagger UI Reactコンポーネントを使って、視覚的で操作可能なドキュメントサイトを構築可能です。
- **型情報の明確化**
  Solidityの型（address、uint256、bytes32など）をパターン付きで表現し、バリデーションルールを明示できます。
- **エラーハンドリングの可視化**
  カスタムエラーやrevertの条件を構造化して記述できます。
- **イベント・構造体の文書化**
  コントラクトの全要素を網羅的にドキュメント化可能です。

# 設計方針

### 目的と制約

この仕様書はHTTP API設計ではなく、Solidityコントラクトのインターフェイス定義をOpenAPI/Swagger形式で記述するためのものです。
HTTPのベストプラクティスに合わせるのではなく、Solidityの挙動を正確かつコンパクトに表現することを最優先にしています。

:::message
実際にHTTPで呼び出すことを前提としていません。
Swagger/OpenAPIの制約上、便宜的にパスとHTTPメソッドを割り当てています。
:::

### 基本ルール

| 項目 | ルール |
|------|--------|
| 成功レスポンス | 常にHTTP 200を使用 |
| エラーレスポンス | 常にHTTP 500を使用（4xx系や他の5xx系は使わない） |
| 説明文 | すべて日本語で記述 |
| 識別子の表記 | 変数名、関数名、エラー名などはバッククォート（`` ` ``）で囲む |
| extensionフィールド | `x-` で始まるフィールドは使用しない |

### パス設計ルール

パスには関数名やイベント名をそのまま使用し、分かりやすい構造を維持します。

| 要素 | パス形式 | 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 |

### パラメータの配置ルール

パラメータは常に `query` または `path` で定義し、`requestBody` は使用しません。

```yaml
# 正しい例
parameters:
  - name: owner
    in: query
    required: true

# 間違った例（requestBodyは使用しない）
requestBody:
  content:
    application/json:
      schema:
        properties:
          owner:
            type: string
```

### 継承関係の表現

コントラクトの継承関係は `info.description` に記載します。
継承しているコントラクト名を箇条書きで列挙します。

```yaml
info:
  title: "MyToken Solidity Interface"
  version: "1.0.0"
  description: |
    MyToken コントラクトの Solidity インターフェイスを OpenAPI 形式で表現した仕様です。
    このコントラクトは以下のコントラクトを継承しています。

    ・ERC20
    ・Ownable
    ・Pausable
```

継承元コントラクトの関数やイベント、エラーも仕様書に含めることで、コントラクトの全体像を把握できるようにします。

# コントラクト仕様書の構造

スマートコントラクトをOpenAPI/Swagger形式で表現する際の基本構造を説明します。

## 全体構成

```yaml
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定義 |

:::message
タグ名は英語で統一されています。
`Read Functions` と `Write Functions` のみ `description` が設定され、その他のタグは名前のみです。
:::

# 各要素の表現方法

スマートコントラクトには、関数、イベント、エラー、変数など、さまざまな要素が含まれています。
これらをOpenAPI/Swagger形式で正確に表現することで、開発者がコントラクトの仕様を理解しやすくなり、フロントエンドやバックエンドとの連携もスムーズになります。

このセクションでは、Solidityの主要な要素それぞれについて、なぜその表現方法を採用するのか、どのように記述するのかを具体例とともに解説していきます。

**表現する要素一覧**
- 関数（読み取り関数・書き込み関数）
- イベント
- エラー
- 変数・定数
- Mapping
- 構造体
- Modifier
- Solidity型のマッピング

各要素には、その性質に合ったHTTPメソッド（GET、POST）とパス構造を割り当てます。
例えば、ステートを変更しない読み取り関数は `GET` メソッドで、ステートを変更する書き込み関数は `POST` メソッドで表現します。
これは、HTTPの設計原則（RESTful API）に従った設計であり、OpenAPI/Swaggerを使い慣れたWeb開発者にとっても理解しやすい形式です。

## 関数の表現

関数はスマートコントラクトの中核となる要素です。
Solidityの関数には、ステートを変更しない読み取り専用関数（`view`/`pure`）と、ステートを変更する書き込み関数（`nonpayable`/`payable`）があります。
OpenAPI/Swagger形式では、これらをHTTPメソッドで区別して表現します。

### 読み取り関数

読み取り関数は、ブロックチェーンのステートを読み取るだけで変更を加えない関数です。
`view` 修飾子が付いた関数はステート変数の読み取りが可能で、`pure` 修飾子が付いた関数はステート変数にすらアクセスせず、引数のみから結果を計算します。

これらの関数は、HTTP `GET` メソッドで表現します。
なぜなら、GETメソッドはデータの取得のみを目的とし、サーバー（この場合はブロックチェーン）のステートを変更しないという性質が、読み取り関数の特性と一致するためです。

引数は `parameters` フィールドで定義し、`in: query` または `in: path` で指定します。

- **基本的な例（引数なし）**

```yaml
/totalSupply:
  get:
    summary: "totalSupply"
    description: |
      トークンの総供給量を返します。

      この関数はERC20標準で定義されており、現在発行されているトークンの総量を確認できます。
      ガス代は発生しません。
    operationId: totalSupply
    tags:
      - Read Functions
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "トークンの総供給量（wei単位）。"
                  example: "1000000000000000000000000"
```

- **引数ありの例**

```yaml
/balanceOf:
  get:
    summary: "balanceOf"
    description: |
      指定されたアカウントのトークン残高を取得します。

      ERC20標準関数です。この関数を呼び出すことで、任意のアドレスが保有しているトークンの量を確認できます。
      ブロックチェーンのステートを変更しないため、ガス代は発生しません。
    operationId: balanceOf
    tags:
      - Read Functions
    parameters:
      - name: owner
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "所有者のアドレスを指定します。40桁の16進数文字列である必要があります。"
        example: "0x1234567890123456789012345678901234567890"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "指定されたアドレスのトークン残高（wei単位）を返します。"
                  example: "1000000000000000000"
```

- **複数の引数を持つ例**

```yaml
/allowance:
  get:
    summary: "allowance"
    description: |
      `spender` が `owner` から使用を許可されているトークン量を取得します。

      ERC20標準関数です。この関数により、あるアドレス（`owner`）が別のアドレス（`spender`）に対して、
      どれだけのトークンを使用することを許可しているかを確認できます。
      `approve` 関数で設定された許可量が返されます。
    operationId: allowance
    tags:
      - Read Functions
    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: "0x0987654321098765432109876543210987654321"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "`spender` が使用できる残りのトークン量（wei単位）を返します。"
                  example: "500000000000000000"
```

各フィールドの意味は以下です。

| フィールド | 説明 | 注意点 |
|-----------|------|--------|
| `summary` | 関数名を指定。<br>Swagger UIのサイドバーに表示される | 冗長な表現（「を取得」など）は避け、関数名のみを記載 |
| `description` | 関数の詳細な説明。<br>マルチライン文字列（`\|`）で複数行記述可能 | 関数の目的、使用場面、注意事項を記載。識別子は必ずバッククォートで囲む |
| `operationId` | 一意の識別子。<br>通常は関数名をそのまま使用 | 他の操作と重複しないように注意 |
| `tags` | 関数の分類。<br>Swagger UIでグループ化される | 読み取り関数は `Read Functions` タグを使用 |
| `parameters` | 関数の引数。<br>`in: query` でクエリパラメータとして表現 | 各引数に適切な型とバリデーションルールを設定 |
| `responses` | 戻り値とエラー。<br>成功時は `200`、エラー時は `500` | 読み取り関数は通常200レスポンスのみ |

- **型の表現方法**

| Solidity型 | OpenAPI表現 | 理由 |
|-----------|------------|------|
| `address` | `type: string`, <br>`pattern: "^0x[0-9a-fA-F]{40}$"` | 40桁の16進数文字列であることをバリデーション |
| `uint256` | `type: string` | JavaScriptの安全な整数範囲（2^53-1）を超える可能性があるため |
| `uint8` | `type: integer`, <br>`minimum: 0`, `maximum: 255` | 安全な範囲内なので数値型として表現可能 |
| `bool` | `type: boolean` | 真偽値はそのまま表現 |
| `string` | `type: string` | 文字列型はそのまま表現 |

### 書き込み関数

書き込み関数は、ブロックチェーンのステートを変更する関数です。
`nonpayable` 修飾子が付いた関数はEtherを受け取らず、`payable` 修飾子が付いた関数はEtherを受け取ることができます。

これらの関数は、HTTP `POST` メソッドで表現します。
POSTメソッドはサーバーのステートを変更する操作を表すため、書き込み関数の特性と一致します。

書き込み関数では、エラーハンドリングが重要になります。
500レスポンスで発生しうるエラーを明示的に記載します。

- **基本的な例**

```yaml
/transfer:
  post:
    summary: "transfer"
    description: |
      指定されたアドレスにトークンを転送します。

      ERC20標準関数です。この関数を呼び出すと、呼び出し元のアカウントから `to` で指定したアドレスに、
      `amount` で指定した量のトークンが転送されます。

      トランザクションが成功すると、`Transfer` イベントが発行されます。
      残高が不足している場合や、送金先が無効なアドレスの場合はエラーが発生します。
    operationId: transfer
    tags:
      - Write Functions
    parameters:
      - name: to
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "送金先のアドレスを指定します。ゼロアドレス（`0x0000000000000000000000000000000000000000`）は指定できません。"
        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:
                result:
                  type: boolean
                  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: {}
```

- **複雑な例（複数のエラーパターンを持つ関数）**

```yaml
/transferFrom:
  post:
    summary: "transferFrom"
    description: |
      第三者が所有者の代わりにトークンを転送します。

      ERC20標準関数です。この関数は、`approve` 関数で事前に許可された量の範囲内で、
      `from` アドレスから `to` アドレスにトークンを転送します。

      呼び出し元は、`from` アドレスから十分な許可量（allowance）を得ている必要があります。
      転送が成功すると、`Transfer` イベントが発行され、許可量が減少します。
    operationId: transferFrom
    tags:
      - Write Functions
    parameters:
      - name: from
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンの転送元アドレスを指定します。このアドレスから `amount` 分のトークンが引き出されます。"
        example: "0x1234567890123456789012345678901234567890"
      - name: to
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "トークンの転送先アドレスを指定します。ゼロアドレスは指定できません。"
        example: "0x0987654321098765432109876543210987654321"
      - name: amount
        in: query
        required: true
        schema:
          type: string
        description: "転送するトークン量を指定します（wei単位）。呼び出し元の許可量以下である必要があります。"
        example: "1000000000000000000"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: boolean
                  description: "転送が成功した場合 `true` を返します。"
                  example: true
      "500":
        description: "この関数で発生しうるエラーの一覧です。\n\n・InsufficientBalance\n\n・InsufficientAllowance\n\n・InvalidSender\n\n・InvalidReceiver"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ErrorResponse'
            examples:
              InsufficientBalance:
                summary: "残高不足"
                value:
                  message: "InsufficientBalance"
                  data:
                    account: "0x1234567890123456789012345678901234567890"
                    balance: "500000000000000000"
                    required: "1000000000000000000"
              InsufficientAllowance:
                summary: "許可量不足"
                value:
                  message: "InsufficientAllowance"
                  data:
                    spender: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
                    allowance: "500000000000000000"
                    required: "1000000000000000000"
              InvalidSender:
                summary: "無効な送信者アドレス"
                value:
                  message: "InvalidSender"
                  data:
                    sender: "0x0000000000000000000000000000000000000000"
              InvalidReceiver:
                summary: "無効な受信者アドレス"
                value:
                  message: "InvalidReceiver"
                  data:
                    receiver: "0x0000000000000000000000000000000000000000"
```

書き込み関数特有のフィールドは以下です。

| フィールド | 説明 | 注意点 |
|-----------|------|--------|
| `responses["500"]` | エラーレスポンス。<br>`description` にエラー一覧を箇条書きで記載 | 各エラーの間には `\n\n` で空行を入れる |
| `examples` | 各エラーの具体例。<br>エラー名をキーに `summary` と `value` を指定 | エラーに引数がある場合は `data` フィールドに含める |
| `$ref` | 共通スキーマの参照。<br>`#/components/schemas/ErrorResponse` で形式を統一 | すべてのエラーで同じスキーマを使用 |

エラーはすべてHTTP 500で表現し、200と500以外のステータスコードは使用しません。
これは、Solidityの `revert` やカスタムエラーを統一的に表現するための設計方針です。
HTTPの4xx系エラー（クライアントエラー）と5xx系エラー（サーバーエラー）の区別は、スマートコントラクトの文脈では意味を持たないため、500に統一しています。

## エラーレスポンスの詳細

エラーレスポンスは、書き込み関数において特に重要な要素です。
Solidityでは、`revert`、`require`、カスタムエラーなど複数のエラーハンドリング方法があり、これらを統一的に表現する必要があります。

### ErrorResponseスキーマ

すべてのエラーは共通の `ErrorResponse` スキーマを使用します。
このスキーマは、エラーの種類に関わらず一貫した形式でエラー情報を提供するために設計されています。

```yaml
components:
  schemas:
    ErrorResponse:
      type: object
      description: "コントラクト呼び出し時のエラー情報を表現する共通スキーマです。"
      properties:
        message:
          type: string
          description: "カスタムエラー名またはrevert/requireのメッセージ文字列。"
        data:
          type: object
          description: "追加情報（任意）。カスタムエラーが引数を持つ場合、その値をここに格納します。"
```

- **スキーマの構成要素**

| フィールド | 型 | 説明 | 例 |
|-----------|-----|------|-----|
| `message` | `string` | エラー名またはメッセージ | `"InsufficientBalance"`, <br>`"Amount must be greater than zero"` |
| `data` | `object` | エラーの詳細情報（オプション） | `{"balance": "100", "required": "1000"}` |

`message` フィールドには、Solidityのカスタムエラー名（例: `InsufficientBalance`）または、`revert()`/`require()` で指定した文字列メッセージが入ります。

`data` フィールドには、カスタムエラーが引数を持つ場合、その引数の値を格納します。
例えば、以下のようなSolidityエラーの場合には、

```solidity
error InsufficientBalance(address account, uint256 balance, uint256 required);
```

`data` フィールドは以下のようになります:

```json
{
  "account": "0x1234567890123456789012345678901234567890",
  "balance": "100",
  "required": "1000"
}
```

### 500レスポンスのdescription書き方

500レスポンスの `description` には、その関数で発生する可能性のあるすべてのエラーを箇条書きで記載します。
これにより、関数を呼び出す際に想定すべきエラーパターンを一目で把握できます。

- **基本的な書き方**

```yaml
"500":
  description: "この関数で発生しうるエラーの一覧です。\n\n・InsufficientBalance\n\n・InvalidAddress\n\n・AmountZero"
```

- **複数のエラーがある場合の例**

```yaml
"500":
  description: |
    この関数で発生しうるエラーの一覧です。

    ・InsufficientBalance
    ・InvalidAddress
    ・AmountZero
    ・Unauthorized
    ・ContractPaused
```

### examplesの書き方

各エラーの具体例は `examples` フィールドで定義します。
`examples` を使うことで、Swagger UIでエラーごとの詳細な情報を確認できるようになります。

- **基本的な書き方**

```yaml
examples:
  InsufficientBalance:
    summary: "残高不足"
    value:
      message: "InsufficientBalance"
      data: {}
  InvalidAddress:
    summary: "無効なアドレス"
    value:
      message: "InvalidAddress"
      data:
        address: "0x0000000000000000000000000000000000000000"
```

- **フィールドの説明**

| フィールド | 説明 | 注意点 |
|-----------|------|--------|
| キー（例: `InsufficientBalance`） | エラー名。<br>Swagger UIのドロップダウンに表示される | Solidityのエラー名と一致させる |
| `summary` | エラーの簡潔な日本語説明 | ユーザーフレンドリーな表現を使う |
| `value.message` | エラー名またはメッセージ文字列 | Solidityのエラー名をそのまま記載 |
| `value.data` | エラーの詳細情報 | カスタムエラーが引数を持つ場合のみ記載 |

- **引数を持たないエラーの例**

```yaml
examples:
  Unauthorized:
    summary: "権限がありません"
    value:
      message: "Unauthorized"
      data: {}
```

- **引数を持つエラーの例**

```yaml
examples:
  InsufficientBalance:
    summary: "残高不足"
    value:
      message: "InsufficientBalance"
      data:
        account: "0x1234567890123456789012345678901234567890"
        balance: "500000000000000000"
        required: "1000000000000000000"
  InsufficientAllowance:
    summary: "許可量不足"
    value:
      message: "InsufficientAllowance"
      data:
        spender: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
        allowance: "100000000000000000"
        required: "500000000000000000"
```

- **require文のエラーの例**

Solidityの `require()` や `revert()` を使ったエラーの場合、`message` にはエラーメッセージ文字列が入ります。

```yaml
examples:
  RequireError:
    summary: "require文によるエラー"
    value:
      message: "Amount must be greater than zero"
      data: {}
```

エラーに引数がある場合は、必ず `data` フィールドにその値を含めます。
これにより、エラーが発生した際の具体的な状況を把握しやすくなります。

## イベントの表現

イベント（Event）は、スマートコントラクトのステート変化を外部に通知するための重要な仕組みです。
トランザクションのログに記録され、フロントエンドアプリケーションやインデックスサービスがこれを監視してリアルタイムにステート変化を追跡できます。

イベントは `/events/{EventName}` パスで表現し、HTTP `GET` メソッドを使用します。
イベントは読み取り専用の情報であり、発行されたイベントを照会する操作として扱うため、GETメソッドが適切です。

### 基本的なイベントの例

```yaml
/events/Transfer:
  get:
    summary: "Transfer"
    description: |
      トークンが転送された時に発行されるイベントです。

      ERC20標準イベントです。このイベントは、`transfer` 関数や `transferFrom` 関数が実行されたときに発行されます。
      トークンの移動を追跡するために使用されます。

      **パラメータ:**
      - `from` (address) - indexed
      - `to` (address) - indexed
      - `value` (uint256)
    operationId: event_Transfer
    tags:
      - Events
    responses:
      "200":
        description: "トークン転送イベント"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferEvent'
```

### イベントスキーマの定義

イベントのスキーマは `components/schemas` に定義します。
各パラメータの型、説明、サンプル値を明確に記載します。

```yaml
components:
  schemas:
    TransferEvent:
      type: object
      description: "Transfer イベント。トークンが転送されたときに発行されます。"
      properties:
        from:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。送金元のアドレス。トピックとして検索可能です。"
          example: "0x1234567890123456789012345678901234567890"
        to:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。送金先のアドレス。トピックとして検索可能です。"
          example: "0x0987654321098765432109876543210987654321"
        value:
          type: string
          description: "indexed: false。転送されたトークン量（wei単位）。"
          example: "1000000000000000000"
```

### 複雑なイベントの例（複数のindexedパラメータ）

```yaml
/events/Approval:
  get:
    summary: "Approval"
    description: |
      トークンの使用許可が設定された時に発行されるイベントです。

      ERC20標準イベントです。このイベントは、`approve` 関数が実行されたときに発行されます。
      `owner` が `spender` に対してトークンの使用を許可した量を記録します。

      **パラメータ:**
      - `owner` (address) - indexed
      - `spender` (address) - indexed
      - `value` (uint256)
    operationId: event_Approval
    tags:
      - Events
    responses:
      "200":
        description: "承認イベント"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalEvent'
```

- **スキーマ定義**

```yaml
components:
  schemas:
    ApprovalEvent:
      type: object
      description: "Approval イベント。トークンの使用許可が設定されたときに発行されます。"
      properties:
        owner:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。トークンの所有者アドレス。トピックとして検索可能です。"
          example: "0x1234567890123456789012345678901234567890"
        spender:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。トークンの使用を許可されたアドレス。トピックとして検索可能です。"
          example: "0x0987654321098765432109876543210987654321"
        value:
          type: string
          description: "indexed: false。許可されたトークン量（wei単位）。"
          example: "1000000000000000000"
```

### カスタムイベントの例

```yaml
/events/RoleGranted:
  get:
    summary: "RoleGranted"
    description: |
      ロールが付与された時に発行されるイベントです。

      AccessControl で使用されるイベントです。管理者が特定のアカウントに新しいロールを付与したときに発行されます。
      このイベントを監視することで、権限の変更履歴を追跡できます。

      **パラメータ:**
      - `role` (bytes32) - indexed
      - `account` (address) - indexed
      - `sender` (address) - indexed
    operationId: event_RoleGranted
    tags:
      - Events
    responses:
      "200":
        description: "ロール付与イベント"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleGrantedEvent'
```

- **スキーマ定義**

```yaml
components:
  schemas:
    RoleGrantedEvent:
      type: object
      description: "RoleGranted イベント。ロールが付与されたときに発行されます。"
      properties:
        role:
          type: string
          pattern: "^0x[0-9a-fA-F]{64}$"
          description: "indexed: true。付与されたロールのID（bytes32形式）。"
          example: "0x0000000000000000000000000000000000000000000000000000000000000000"
        account:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。ロールを付与されたアカウントのアドレス。"
          example: "0x1234567890123456789012345678901234567890"
        sender:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "indexed: true。ロールを付与した管理者のアドレス。"
          example: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
```

イベントスキーマのフィールドは以下です。

| フィールド | 説明 | 注意点 |
|-----------|------|--------|
| `type` | スキーマの型。<br>イベントは `object` を使用 | 常に `object` |
| `description` | イベントの説明。<br>どのような状況で発行されるかを記述 | 発行条件、用途、関連する関数を明記 |
| `properties` | イベントの各パラメータ。<br>`indexed` は`description`に明記 | 各パラメータに `indexed: true/false` を明記 |

### indexedパラメータの重要性

Solidityの `indexed` パラメータは、イベントログのトピック（topics）として記録されます。
トピックとして記録されたパラメータは、効率的に検索・フィルタリングが可能です。

- **indexed パラメータ**
  `description`に「`indexed: true`」と明記し、「**トピックとして検索可能です**」と追記。
- **非indexed パラメータ**
  `description`に「`indexed: false`」と明記。

例えば、特定のアドレス（`from`）から送信されたすべての `Transfer` イベントを検索したい場合、`from` が `indexed` であれば効率的に検索できます。

```yaml
description: "indexed: true。送金元のアドレス。トピックとして検索可能です。"
```

一方、`indexed` でないパラメータは、イベントのデータ部分に記録され、検索には使用できませんが、より多くの情報を含めることができます。

```yaml
description: "indexed: false。転送されたトークン量（wei単位）。"
```

## エラーの表現

カスタムエラーは、Solidity 0.8.4以降で導入された効率的なエラーハンドリング機能です。
従来の `revert()` や `require()` よりもガス代が安く、型安全性が高いという特徴があります。

カスタムエラーは `/errors/{ErrorName}` パスで表現し、HTTP `GET` メソッドを使用します。
エラーの定義を参照する操作として扱うため、GETメソッドが適切です。

### 基本的なカスタムエラーの例（引数なし）

```yaml
/errors/Unauthorized:
  get:
    summary: "Unauthorized"
    description: |
      権限がないアカウントが操作を実行しようとした時に返されるエラーです。

      このエラーは、アクセス制御で保護された関数を、権限を持たないアカウントが呼び出した場合に発生します。
      適切な権限（ロール）を付与してから、再度実行してください。
    operationId: error_Unauthorized
    tags:
      - Errors
    responses:
      "200":
        description: "権限エラー"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "Unauthorized"
```

### 引数を持つカスタムエラーの例

カスタムエラーに引数がある場合、エラーが発生した際の具体的な状況を伝えることができます。

```yaml
/errors/InsufficientBalance:
  get:
    summary: "InsufficientBalance"
    description: |
      残高が不足している時に返されるエラーです。

      このエラーは、トークン転送や支払いの際に、実行者の残高が必要な量に満たない場合に発生します。
      `balance` フィールドで現在の残高を、`required` フィールドで必要な残高を確認できます。

      **エラー引数:**
      - `account` (address) - 残高が不足しているアカウントのアドレス
      - `balance` (uint256) - 現在の残高
      - `required` (uint256) - 必要な残高
    operationId: error_InsufficientBalance
    tags:
      - Errors
    responses:
      "200":
        description: "残高不足エラー"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InsufficientBalanceError'
```

- **エラースキーマの定義**

```yaml
components:
  schemas:
    InsufficientBalanceError:
      type: object
      description: "InsufficientBalance エラー。残高が不足している場合に返されます。"
      properties:
        account:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "残高が不足しているアカウントのアドレス。"
          example: "0x1234567890123456789012345678901234567890"
        balance:
          type: string
          description: "現在の残高（wei単位）。"
          example: "500000000000000000"
        required:
          type: string
          description: "必要な残高（wei単位）。"
          example: "1000000000000000000"
```

### 複雑なカスタムエラーの例

```yaml
/errors/InvalidAddress:
  get:
    summary: "InvalidAddress"
    description: |
      無効なアドレスが指定された時に返されるエラーです。

      このエラーは、ゼロアドレス（`0x0000000000000000000000000000000000000000`）が指定された場合や、
      コントラクトのロジック上許可されないアドレスが指定された場合に発生します。

      **エラー引数:**
      - `addr` (address) - 無効と判定されたアドレス
    operationId: error_InvalidAddress
    tags:
      - Errors
    responses:
      "200":
        description: "無効なアドレスエラー"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvalidAddressError'
```

- **エラースキーマの定義**

```yaml
components:
  schemas:
    InvalidAddressError:
      type: object
      description: "InvalidAddress エラー。無効なアドレスが指定された場合に返されます。"
      properties:
        addr:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "無効と判定されたアドレス。"
          example: "0x0000000000000000000000000000000000000000"
```

エラー定義のフィールドは以下です。

| フィールド | 説明 | 注意点 |
|-----------|------|--------|
| `summary` | エラー名をそのまま記載 | Solidityのエラー名と一致させる |
| `description` | エラーの詳細な説明。発生条件、対処方法を記述 | 引数がある場合は「**エラー引数:**」として列挙 |
| `operationId` | `error_{ErrorName}` の形式 | 他の操作と重複しないよう接頭辞を付ける |
| `tags` | `Errors` タグを使用 | エラーは単一のタグで分類 |
| `responses["200"]` | エラースキーマへの参照 | 引数がある場合は `$ref` でスキーマを参照 |

カスタムエラーを使うことで、従来の文字列ベースのエラーメッセージよりも構造化された詳細な情報を提供でき、デバッグやエラーハンドリングが容易になります。

## 変数・定数の表現

Solidityの `public` なステート変数や定数は、コンパイラによって自動的にgetter関数が生成されます。
これらの自動生成されたgetterをOpenAPI/Swagger形式で表現する際には、HTTP `GET` メソッドを使用します。

重要なポイントとして、`description` には必ず変数宣言そのものをコードブロックで含めます。
これにより、型、可視性、初期値などの重要な情報を一目で確認できます。

### 基本的な変数の例

```yaml
/totalSupply:
  get:
    summary: "totalSupply"
    description: |
      ```solidity
      uint256 public totalSupply;
      ```

      トークンの総供給量を取得します。

      ERC20標準のステート変数です。この値は、トークンの発行（mint）や焼却（burn）によって変動します。
      現在発行されているトークンの総量を確認できます。
    operationId: totalSupply
    tags:
      - Variables
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "トークンの総供給量（wei単位）を返します。"
                  example: "1000000000000000000000000"
```

### 定数（constant）の例

定数は、コンパイル時に値が確定し、デプロイ後は変更できません。
`constant` キーワードを使って定義します。

```yaml
/DECIMALS:
  get:
    summary: "DECIMALS"
    description: |
      ```solidity
      uint8 public constant DECIMALS = 18;
      ```

      トークンの小数点以下の桁数を表す定数です。

      この値は、トークンの表示や計算において重要な役割を果たします。
      例えば、`DECIMALS` が18の場合、1トークンは `1 * 10^18` wei として扱われます。
      ERC20標準では、通常18が使用されます（Etherと同じ）。

      この値はコンパイル時に確定し、変更できません。
    operationId: DECIMALS
    tags:
      - Constants
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: integer
                  minimum: 0
                  maximum: 255
                  description: "小数点以下の桁数を返します。"
                  example: 18
```

### イミュータブル変数（immutable）の例

イミュータブル変数は、コンストラクタ内で一度だけ値を設定でき、その後は変更できません。
デプロイ時に値が確定します。

```yaml
/owner:
  get:
    summary: "owner"
    description: |
      ```solidity
      address public immutable owner;
      ```

      コントラクトのオーナーアドレスを取得します。

      この値は、コントラクトのデプロイ時にコンストラクタで設定されます。
      デプロイ後は変更できません。
      オーナーは、コントラクトの管理機能（ミント、バーン、権限設定など）にアクセスできます。
    operationId: owner
    tags:
      - Variables
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  pattern: "^0x[0-9a-fA-F]{40}$"
                  description: "コントラクトのオーナーアドレスを返します。"
                  example: "0x1234567890123456789012345678901234567890"
```

### bytes32型の定数の例

ロールベースのアクセス制御でよく使われる `bytes32` 型の定数の例です。

```yaml
/ADMIN_ROLE:
  get:
    summary: "ADMIN_ROLE"
    description: |
      ```solidity
      bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
      ```

      管理者ロールを表す定数です。

      AccessControl で使用されるロール識別子です。
      このロールを持つアカウントは、コントラクトの管理機能にアクセスできます。
      `hasRole(ADMIN_ROLE, account)` で、特定のアカウントがこのロールを持っているかを確認できます。
    operationId: ADMIN_ROLE
    tags:
      - Constants
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  pattern: "^0x[0-9a-fA-F]{64}$"
                  description: "ロールIDを表すbytes32値を返します。"
                  example: "0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42"
```

変数・定数の表現において重要なポイントは以下です。

| ポイント | 説明 | 例 |
|---------|------|-----|
| **変数宣言の記載** | `description` の最初にSolidityの変数宣言をコードブロックで記載 | `solidity uint256 public totalSupply; ` |
| **型の明確化** | 変数宣言により、型、可視性、修飾子が一目で分かる | `uint256 public`, `uint8 public constant`, `address public immutable` |
| **変更可能性の説明** | `constant` や `immutable` の場合、変更できないことを明記 | 「**この値はコンパイル時に確定し、変更できません**」 |
| **使用目的の説明** | 変数が何のために使われるか、どのように使われるかを説明 | 「**トークンの発行や焼却によって変動します**」 |

変数宣言を含めることで、Swagger UIで仕様書を見る人が、実際のSolidityコードの構造を理解しやすくなります。

## Mappingの表現

Mappingは、Solidityでキーと値の対応関係を管理するデータ構造です。
`public` な Mapping は、コンパイラによって自動的にgetter関数が生成されます。

Mappingも変数と同様に、`description` には型定義をコードブロックで含めます。
特に、ネストしたMapping（二重Mapping）の場合は、型定義を明記することで構造が理解しやすくなります。

### 単一Mappingの例

```yaml
/balanceOf:
  get:
    summary: "balanceOf"
    description: |
      ```solidity
      mapping(address account => uint256) public balanceOf;
      ```

      各アカウントのトークン残高を管理するMappingです。

      ERC20標準のMappingです。アカウントアドレスをキーとして、そのアカウントが保有するトークン量を取得できます。
      トークンの転送や発行、焼却によって残高が変動します。
    operationId: balanceOf
    tags:
      - Mapping
    parameters:
      - name: account
        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: "指定されたアカウントのトークン残高（wei単位）を返します。"
                  example: "1000000000000000000"
```

### ネストしたMapping（二重Mapping）の例

ERC20の `allowance` のような二重Mappingの例です。

```yaml
/allowance:
  get:
    summary: "allowance"
    description: |
      ```solidity
      mapping(address owner => mapping(address spender => uint256)) public allowance;
      ```

      `spender` が `owner` から使用を許可されているトークン量を管理するMappingです。

      ERC20標準のMappingです。このMappingは、トークンの所有者（`owner`）が、
      別のアカウント（`spender`）に対して使用を許可したトークン量を記録します。

      `approve` 関数で許可量を設定し、`transferFrom` 関数で実際に使用されます。
      `transferFrom` が実行されると、この許可量が減少します。
    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: "0x0987654321098765432109876543210987654321"
    responses:
      "200":
        description: "正常終了時のレスポンスです。"
        content:
          application/json:
            schema:
              type: object
              properties:
                result:
                  type: string
                  description: "`spender` が使用できる残りのトークン量（wei単位）を返します。"
                  example: "500000000000000000"
```

### アクセス制御に使われるMappingの例

```yaml
/hasRole:
  get:
    summary: "hasRole"
    description: |
      ```solidity
      mapping(bytes32 role => mapping(address account => bool)) public hasRole;
      ```

      特定のアカウントが特定のロールを持っているかを確認するMappingです。

      AccessControl で使用されるMappingです。ロールID（`role`）とアカウントアドレス（`account`）の
      組み合わせに対して、そのアカウントがそのロールを持っているかどうかをブール値で管理します。

      `grantRole` 関数でロールが付与され、`revokeRole` 関数で剥奪されます。
    operationId: hasRole
    tags:
      - Mapping
    parameters:
      - name: role
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{64}$"
        description: "確認するロールのID（bytes32形式）を指定します。"
        example: "0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42"
      - name: account
        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: boolean
                  description: "アカウントがロールを持っている場合 `true`、持っていない場合 `false` を返します。"
                  example: true
```

Mappingの表現において重要なポイントは以下です。

| ポイント | 説明 | 例 |
|---------|------|-----|
| **型定義の記載** | Mappingの完全な型定義をコードブロックで記載 | `mapping(address => uint256) public balanceOf` |
| **ネスト構造の明示** | 二重Mappingの場合、キーの階層構造を明確に説明 | 「`owner` → `spender` → `amount`」 |
| **parametersの順序** | Mappingのキーの順序に従ってparametersを定義 | 外側のキー（owner）を先に、内側のキー（spender）を後に |
| **使用場面の説明** | どの関数でこのMappingが使われるかを明記 | 「`approve` で設定、`transferFrom` で使用」 |

## 構造体の表現

構造体（struct）は、複数のデータをまとめて管理するための複合型です。
関連するデータをグループ化することで、コードの可読性と保守性を向上させます。

構造体は `/structs/{StructName}` パスで表現し、HTTP `GET` メソッドを使用します。

### 基本的な構造体の例

```yaml
/structs/TokenInfo:
  get:
    summary: "TokenInfo"
    description: |
      トークンの基本情報を保持する構造体です。

      この構造体は、トークンのメタデータ（名前、シンボル、decimals）を一つのデータ構造にまとめています。
      コントラクトの初期化時や設定変更時に使用されます。

      **フィールド:**
      - `name` (string) - トークンの名前
      - `symbol` (string) - トークンのシンボル
      - `decimals` (uint8) - トークンの小数点以下の桁数
    operationId: struct_TokenInfo
    tags:
      - Structs
    responses:
      "200":
        description: "TokenInfo構造体"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenInfoStruct'
```

構造体のスキーマは `components/schemas` に定義します。

```yaml
components:
  schemas:
    TokenInfoStruct:
      type: object
      description: "TokenInfo 構造体。トークンの基本情報を保持します。"
      properties:
        name:
          type: string
          description: "トークンの名前。例: 'MyToken'。"
          example: "Example Token"
        symbol:
          type: string
          description: "トークンのシンボル。例: 'MTK'。通常は3-5文字の大文字。"
          example: "EXT"
        decimals:
          type: integer
          minimum: 0
          maximum: 255
          description: "トークンの小数点以下の桁数。通常は18。"
          example: 18
```

### 複雑な構造体の例

```yaml
/structs/VestingSchedule:
  get:
    summary: "VestingSchedule"
    description: |
      トークンのベスティング（段階的権利確定）スケジュールを管理する構造体です。

      この構造体は、トークンの配布スケジュールを定義します。
      ベスティング期間中、受益者は時間の経過とともに徐々にトークンを受け取る権利を獲得します。

      **フィールド:**
      - `beneficiary` (address) - 受益者のアドレス
      - `start` (uint256) - ベスティング開始時刻（UNIXタイムスタンプ）
      - `cliff` (uint256) - クリフ期間（秒）。この期間中はトークンを受け取れない
      - `duration` (uint256) - ベスティング期間（秒）
      - `totalAmount` (uint256) - ベスティング対象のトークン総量
      - `releasedAmount` (uint256) - すでに引き出されたトークン量
    operationId: struct_VestingSchedule
    tags:
      - Structs
    responses:
      "200":
        description: "VestingSchedule構造体"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VestingScheduleStruct'
```

- **スキーマ定義**

```yaml
components:
  schemas:
    VestingScheduleStruct:
      type: object
      description: "VestingSchedule 構造体。トークンのベスティングスケジュールを定義します。"
      properties:
        beneficiary:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
          description: "受益者のアドレス。このアドレスがベスティングされたトークンを受け取ります。"
          example: "0x1234567890123456789012345678901234567890"
        start:
          type: string
          description: "ベスティング開始時刻（UNIXタイムスタンプ、秒単位）。"
          example: "1704067200"
        cliff:
          type: string
          description: "クリフ期間（秒）。開始からこの期間中はトークンを引き出せません。"
          example: "2592000"
        duration:
          type: string
          description: "ベスティング期間（秒）。この期間をかけてトークンが徐々に権利確定します。"
          example: "31536000"
        totalAmount:
          type: string
          description: "ベスティング対象のトークン総量（wei単位）。"
          example: "1000000000000000000000"
        releasedAmount:
          type: string
          description: "すでに引き出されたトークン量（wei単位）。"
          example: "500000000000000000000"
```

### ネストした構造体の例

```yaml
/structs/Proposal:
  get:
    summary: "Proposal"
    description: |
      ガバナンス提案を管理する構造体です。

      この構造体は、DAOの投票提案に関する情報を保持します。
      提案の内容、投票状況、実行ステートなどを一元管理します。

      **フィールド:**
      - `id` (uint256) - 提案のID
      - `proposer` (address) - 提案者のアドレス
      - `targets` (address[]) - 実行対象のコントラクトアドレスのリスト
      - `values` (uint256[]) - 各呼び出しで送信するEther量のリスト
      - `signatures` (string[]) - 各呼び出しの関数シグネチャのリスト
      - `calldatas` (bytes[]) - 各呼び出しのコールデータのリスト
      - `startBlock` (uint256) - 投票開始ブロック番号
      - `endBlock` (uint256) - 投票終了ブロック番号
      - `forVotes` (uint256) - 賛成票の総数
      - `againstVotes` (uint256) - 反対票の総数
      - `abstainVotes` (uint256) - 棄権票の総数
      - `executed` (bool) - 提案が実行済みかどうか
      - `canceled` (bool) - 提案がキャンセル済みかどうか
    operationId: struct_Proposal
    tags:
      - Structs
    responses:
      "200":
        description: "Proposal構造体"
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposalStruct'
```

構造体スキーマのフィールドは以下です。

| フィールド | 説明 | 注意点 |
|-----------|------|--------|
| `type` | スキーマの型。構造体は `object` を使用 | 常に `object` |
| `description` | 構造体の用途や役割の説明 | どのような場面で使われるかを明記 |
| `properties` | 構造体の各フィールド。Solidityの変数名をキー名として使用 | フィールドごとに詳細な説明を記載 |

構造体を使うことで、関連するデータをまとめて扱えるため、関数の引数や戻り値をシンプルに保つことができます。

## Modifierの表現

Modifier（修飾子）は、関数の実行前後に特定の条件をチェックしたり、処理を追加したりする仕組みです。
アクセス制御、ステートチェック、再入攻撃対策などに広く使われます。

Modifierは `/modifiers/{ModifierName}` パスで定義し、HTTP `GET` メソッドを使用します。

### 基本的なModifierの例

```yaml
/modifiers/onlyOwner:
  get:
    summary: "onlyOwner"
    description: |
      ```solidity
      modifier onlyOwner() {
          require(msg.sender == owner, "Not owner");
          _;
      }
      ```

      このmodifierは、関数の実行者がコントラクトのオーナーであることを確認します。

      関数の実行前に `msg.sender` がオーナーアドレスと一致するかをチェックします。
      オーナー以外が実行した場合は、「Not owner」というメッセージとともにrevertします。

      **使用例:**
      - `mint` 関数: トークンの発行
      - `burn` 関数: トークンの焼却
      - `transferOwnership` 関数: オーナー権限の移譲
    operationId: modifier_onlyOwner
    tags:
      - Modifier
    responses:
      "200":
        description: "onlyOwner modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "onlyOwner"
```

### ロールベースのModifierの例

```yaml
/modifiers/onlyRole:
  get:
    summary: "onlyRole"
    description: |
      ```solidity
      modifier onlyRole(bytes32 role) {
          require(hasRole(role, msg.sender), "AccessControl: account is missing role");
          _;
      }
      ```

      このmodifierは、関数の実行者が指定されたロールを持っているかを確認します。

      AccessControl で使用されるmodifierです。関数の実行前に、呼び出し元のアカウントが
      指定されたロール（`role`）を持っているかをチェックします。
      ロールを持っていない場合は、エラーメッセージとともにrevertします。

      **引数:**
      - `role` (bytes32) - 必要なロールのID

      **使用例:**
      - `grantRole` 関数: ロールの付与（`ADMIN_ROLE` が必要）
      - `revokeRole` 関数: ロールの剥奪（`ADMIN_ROLE` が必要）
    operationId: modifier_onlyRole
    tags:
      - Modifier
    responses:
      "200":
        description: "onlyRole modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "onlyRole"
```

### ステートチェックのModifierの例

```yaml
/modifiers/whenNotPaused:
  get:
    summary: "whenNotPaused"
    description: |
      ```solidity
      modifier whenNotPaused() {
          require(!paused, "Pausable: paused");
          _;
      }
      ```

      このmodifierは、コントラクトが一時停止ステートでないことを確認します。

      Pausable パターンで使用されるmodifierです。関数の実行前に、コントラクトの `paused` フラグを
      チェックします。コントラクトが一時停止中（`paused == true`）の場合は、関数の実行を拒否します。

      **使用例:**
      - `transfer` 関数: 一時停止中は転送を禁止
      - `mint` 関数: 一時停止中は発行を禁止
      - `burn` 関数: 一時停止中は焼却を禁止

      **関連するModifier:**
      - `whenPaused`: 一時停止中のみ実行可能（一時停止解除のため）
    operationId: modifier_whenNotPaused
    tags:
      - Modifier
    responses:
      "200":
        description: "whenNotPaused modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "whenNotPaused"
```

### 再入攻撃対策のModifierの例

```yaml
/modifiers/nonReentrant:
  get:
    summary: "nonReentrant"
    description: |
      ```solidity
      modifier nonReentrant() {
          require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
          _status = _ENTERED;
          _;
          _status = _NOT_ENTERED;
      }
      ```

      このmodifierは、再入攻撃を防ぐためのガードです。

      ReentrancyGuard パターンで使用されるmodifierです。関数の実行中に、同じ関数が再度呼び出されることを防ぎます。
      外部コントラクトを呼び出す関数や、Etherを送信する関数に適用することで、再入攻撃のリスクを軽減できます。

      **仕組み:**
      1. 関数の実行前に、`_status` が `_NOT_ENTERED` であることを確認
      2. `_status` を `_ENTERED` に設定
      3. 関数本体を実行
      4. 関数の実行後、`_status` を `_NOT_ENTERED` に戻す

      **使用例:**
      - `withdraw` 関数: Etherの引き出し
      - `claim` 関数: 報酬の受け取り
    operationId: modifier_nonReentrant
    tags:
      - Modifier
    responses:
      "200":
        description: "nonReentrant modifier"
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: "nonReentrant"
```

### Modifierが付与された関数のdescription

関数にmodifierが付与されている場合、その効果を関数の `description` に追記します。
これにより、関数を呼び出す際に必要な権限や条件を明確にできます。

- **単一のModifierの場合**

```yaml
/mint:
  post:
    summary: "mint"
    description: |
      新しいトークンを発行します。

      指定されたアドレス（`to`）に、指定された量（`amount`）のトークンを新規に発行します。
      総供給量（`totalSupply`）が増加し、`Transfer` イベントが発行されます。

      :::message
      **Modifier**: この関数は `onlyOwner` modifierにより保護されています。
      コントラクトのオーナーのみが実行可能です。
      :::
    operationId: mint
    tags:
      - Write Functions
```

- **複数のModifierの場合**

```yaml
/withdraw:
  post:
    summary: "withdraw"
    description: |
      コントラクトからEtherを引き出します。

      呼び出し元のアカウントが預けているEtherの全額を引き出します。
      残高が不足している場合はエラーが発生します。

      :::message
      **Modifiers**:
      - `whenNotPaused`: コントラクトが一時停止中でないことを確認
      - `nonReentrant`: 再入攻撃を防止
      :::
    operationId: withdraw
    tags:
      - Write Functions
```

複数のmodifierが付与されている場合は、すべてのmodifierの効果を箇条書きで記載します。
これにより、関数を安全に呼び出すために必要な条件を包括的に理解できます。

## Solidityの型マッピング

Solidityの型をOpenAPI/Swagger形式に正確にマッピングすることは、仕様書の品質を左右する重要なポイントです。
以下では、よく使われるSolidity型とそのOpenAPI表現を詳しく説明します。

### 基本型のマッピング表

| Solidity型 | OpenAPI表現 | 理由・注意点 |
|------------|-------------|------------|
| `address` | `type: string`, `pattern: "^0x[0-9a-fA-F]{40}$"` | 40桁の16進数文字列。<br>パターンでバリデーション |
| `uint256` / `int256` | `type: string` | JavaScriptの安全な整数範囲（2^53-1）を超えるため文字列化 |
| `uint8` / `uint16` / `uint32` | `type: integer`, `minimum: 0`, `maximum: {max}` | 安全な範囲内なので数値型で表現可能 |
| `int8` / `int16` / `int32` | `type: integer`, `minimum: {min}`, `maximum: {max}` | 符号付き整数。<br>範囲を明示 |
| `bytes32` | `type: string`, `pattern: "^0x[0-9a-fA-F]{64}$"` | 64桁の16進数文字列（32バイト） |
| `bytes4` | `type: string`, `pattern: "^0x[0-9a-fA-F]{8}$"` | 8桁の16進数文字列（4バイト、関数セレクタなど） |
| `bytes` | `type: string` | 可変長バイト列。<br>16進数文字列として表現 |
| `bool` | `type: boolean` | 真偽値。<br>そのまま表現 |
| `string` | `type: string` | 文字列。<br>そのまま表現 |
| `address[]` | `type: array`, `items: {type: string, pattern: ...}` | アドレスの配列 |
| `uint256[]` | `type: array`, `items: {type: string}` | uint256の配列 |

### 各型の詳細な説明と例

#### address型

```yaml
schema:
  type: string
  pattern: "^0x[0-9a-fA-F]{40}$"
  description: "Ethereumアドレス（40桁の16進数文字列）"
  example: "0x1234567890123456789012345678901234567890"
```

- 常に `0x` プレフィックスを含む
- 40桁の16進数（20バイト）
- 大文字・小文字を区別しない（チェックサムアドレスには大文字小文字の区別あり）

#### uint256型

```yaml
schema:
  type: string
  description: "符号なし256ビット整数（wei単位）。JavaScriptの安全な整数範囲を超えるため文字列として扱います。"
  example: "1000000000000000000"
```

- JavaScriptの `Number.MAX_SAFE_INTEGER` は `2^53-1`（約9京）
- `uint256` は `2^256-1`（約10の77乗）まで扱える
- 文字列化しないと精度が失われる

#### uint8型（小さい整数）

```yaml
schema:
  type: integer
  minimum: 0
  maximum: 255
  description: "符号なし8ビット整数（0-255の範囲）"
  example: 18
```

- **使用例**

`decimals`（小数点以下の桁数）。

#### bytes32型

```yaml
schema:
  type: string
  pattern: "^0x[0-9a-fA-F]{64}$"
  description: "32バイトの固定長バイト列（64桁の16進数文字列）"
  example: "0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42"
```

- **使用例**

ロールID、ハッシュ値、一意識別子。

#### bool型

```yaml
schema:
  type: boolean
  description: "真偽値"
  example: true
```

ー **使用例**

フラグ、権限の有無。

#### string型

```yaml
schema:
  type: string
  description: "文字列"
  example: "Example Token"
```

- **使用例**

トークン名、シンボル、メッセージ。

#### 配列型（address[]）

```yaml
schema:
  type: array
  items:
    type: string
    pattern: "^0x[0-9a-fA-F]{40}$"
  description: "アドレスの配列"
  example:
    - "0x1234567890123456789012345678901234567890"
    - "0x0987654321098765432109876543210987654321"
```

#### 配列型（uint256[]）

```yaml
schema:
  type: array
  items:
    type: string
  description: "uint256の配列（各要素は文字列として表現）"
  example:
    - "1000000000000000000"
    - "2000000000000000000"
```

### 型マッピングの実例

- **複数の型を持つ関数の例**

```yaml
/complexFunction:
  post:
    summary: "complexFunction"
    description: "複数の型を扱う関数の例です。"
    parameters:
      - name: account
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        description: "アドレス型のパラメータ"
      - name: amount
        in: query
        required: true
        schema:
          type: string
        description: "uint256型のパラメータ（文字列として扱う）"
      - name: decimals
        in: query
        required: true
        schema:
          type: integer
          minimum: 0
          maximum: 255
        description: "uint8型のパラメータ（数値として扱える）"
      - name: isActive
        in: query
        required: true
        schema:
          type: boolean
        description: "bool型のパラメータ"
      - name: roleId
        in: query
        required: true
        schema:
          type: string
          pattern: "^0x[0-9a-fA-F]{64}$"
        description: "bytes32型のパラメータ"
```

このように、各型の特性を理解し、適切なOpenAPI表現を選択することで、正確で使いやすい仕様書を作成できます。

# サンプル仕様書（ERC20）

以下は、ERC20トークンコントラクトの完全なOpenAPI仕様書サンプルです。

https://github.com/cardene777/smartcontract-specification/blob/main/docs/contract/specs/ERC20/ERC20.openapi.yaml

# 最後に

今回は「**SolidityスマートコントラクトをOpenAPI/Swagger形式で仕様書化する方法**」についてまとめてきました。

https://www.amazon.co.jp/dp/B0DQDWM1YK

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://twitter.com/cardene777

https://qiita.com/cardene

https://cardene.substack.com/

https://mirror.xyz/0xACAc709e21e7a4A5a4458BDeeCCA0fc98A6a8A4D
