# OpenAPI+Swagger形式のSolidity仕様書を自動生成

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

- URL: https://cardene.dev/articles/5fee6d0417692955fe93
- 公開日: 2025-11-28
- 更新日: 2025-11-28
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, openapi

## はじめに

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）およびSwagger 2.0（JSON）形式で仕様書化する方法を提案しています。
さらに、この仕様書生成を自動化するClaude Code Subagentの使い方も紹介します。

最終的には以下のようなサイト作成まで行ってくれます。

https://smartcontract-specification.vercel.app/

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

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

- **標準化されたフォーマット**
  APIドキュメントの業界標準であるOpenAPI/Swagger形式を使用することで、多くの開発者にとって馴染みのある形式で仕様を共有できる
- **インタラクティブなドキュメント**
  Swagger UIやRedocusaurusを使って、視覚的で操作可能なドキュメントサイトを構築可能
- **型情報の明確化**
  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-` で始まるフィールドは使用しない |

### 識別子の表記ルール

description内で以下の要素に言及する時は、必ずバッククォート（`` ` ``）で囲みます。

| 対象 | 例 |
|------|-----|
| 関数名 | `transfer`、`balanceOf` |
| 変数名・引数名 | `_to`、`amount`、`owner` |
| エラー名 | `InsufficientBalance`、`InvalidAddress` |
| イベント名 | `Transfer`、`Approval` |
| 型名 | `address`、`uint256` |
| modifier名 | `onlyOwner`、`whenNotPaused` |
| 定数名 | `DECIMALS`、`MAX_SUPPLY` |

```yaml
# 正しい例
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」や「を取得」といった冗長な表現は避けます。

```yaml
# 正しい例
summary: "balanceOf"
summary: "transfer"
summary: "totalSupply"

# 間違った例（これらは使用しない）
summary: "balanceOf getter"
summary: "残高を取得する"
summary: "Get balance"
```

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

パラメータは常に `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形式でのコントラクト仕様書の構造

スマートコントラクトを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` が設定され、その他のタグは名前のみです。
:::

## 各要素の表現方法

### 関数の表現

#### 読み取り関数（view/pure）

読み取り関数は`GET`メソッドで表現し、引数は`parameters`（query/path）で定義します。

```yaml
/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`メソッドで表現します。

```yaml
/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` スキーマを使用します。

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

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

500レスポンスの `description` には、発生しうるエラーを `・` で始まる箇条書きで記載します。

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

箇条書きの各項目の間には空行（`\n\n`）を入れます。
これにより、Swagger UIで見やすく表示されます。

#### examplesの書き方

各エラーの具体例は `examples` フィールドで定義します。
キーにはエラー名を使用し、`summary` には日本語の簡潔な説明を記載します。

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

エラーに引数がある場合は、`data` にその値を含めます。

### イベントの表現

イベントは`/events/{EventName}`パスで表現します。

```yaml
/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` に定義します。

```yaml
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}`パスで表現します。

```yaml
/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には必ず変数宣言そのものをコードブロックで含め、その後に説明を記述します。

```yaml
/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`）の場合も同様に宣言を含めます。

````yaml
description: |
  ```solidity
  uint8 public constant DECIMALS = 18;
  ```

  トークンの小数点以下の桁数です。
````

## Mappingの表現

Mappingも変数と同様に、descriptionには型定義をコードブロックで含めます。

```yaml
/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"
```

## 構造体の表現

```yaml
/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` に定義します。

```yaml
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}` パスで定義します。

```yaml
/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` に追記します。

```yaml
/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仕様書サンプルです。

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

<details><summary>ERC20.openapi.yaml</summary>

```yaml
# 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)。"
```

</details>

# 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` |

:::message
ABIファイルはビルドツールによって出力先が異なります。
事前にコントラクトをビルドしてABIを生成しておいてください。
:::

## 自動実行される処理の詳細

### Step 1: コントラクトのビルドとABI生成（事前準備）

Subagentを呼び出す前に、コントラクトをビルドしてABIを生成しておく必要があります。
ABIにはコントラクトの関数シグネチャ、イベント、エラー、構造体の情報が含まれており、これが仕様書生成の基礎データとなります。

使用するビルドツールに応じて、以下のコマンドでABIを生成します。

**Foundry（forge）の場合**

```bash
cd contract
forge build
```

生成されたABIは `out/{ContractName}.sol/{ContractName}.json` に配置されます。

**Hardhatの場合**

```bash
cd contract
npx hardhat compile
```

生成されたABIは `artifacts/contracts/{ContractName}.sol/{ContractName}.json` に配置されます。

### Step 2: スケルトン生成スクリプトの実行

`generate-openapi-specs.js` スクリプトがABIとソースコードを解析し、OpenAPI/Swagger仕様書の骨格を一括生成します。

```bash
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）」と答えると、以下の処理が自動実行されます。

1. 仕様書ファイルを `docs-site/specs/` にコピー
2. `docusaurus.config.js` のパスを更新
3. `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/` に生成され、概要・継承関係・主要機能・関数一覧が記載されます。

# サイトの構成

生成されたドキュメントサイトは以下のようになります。

https://smartcontract-specification.vercel.app/

ドキュメントページには、コントラクトごとに主要関数の説明などが記載されています。

https://smartcontract-specification.vercel.app/docs/intro

そして、各コントラクトごとに仕様書ページがあります。

https://smartcontract-specification.vercel.app/api/ERC20

### ページ構成

| ページ | パス | 内容 |
|--------|------|------|
| トップページ | `/` | カテゴリ別コントラクトカード一覧 |
| 概要ページ | `/overview` | システム全体の説明、コントラクト一覧 |
| ドキュメント | `/docs/contracts/*` | 各コントラクトの詳細ドキュメント（Markdown） |
| 仕様書 | `/api/*` | OpenAPI/Swagger（Redoc表示） |

### ヘッダー構成

| 位置 | 要素 | リンク先 |
|------|------|----------|
| 左端 | Logo + プロジェクト名 | `/` |
| 2番目 | **Docs** | `/overview`（システム概要） |
| 3番目以降 | カテゴリドロップダウン | `/api/{ContractName}`（ドキュメント） |
| 右端 | GitHub | リポジトリURL |

### トップページ

カテゴリ別にコントラクトカードが表示され、「**仕様書を見る**」ボタンから各コントラクトの詳細仕様にアクセスできます。

ヒーロー部分にはタイトルとサブタイトルのみを表示し、ボタンは配置しません。
カテゴリ名は中央寄せで表示されます。

## API仕様ページ

Redocusaurusによるインタラクティブな仕様書表示。
サイドバーに関数、イベント、エラー、構造体が分類表示されます。

## ドキュメントページ

Markdown形式のドキュメント。
概要、継承関係、主要機能、関数一覧などが記載されます。

各ドキュメントページの先頭には、対応するAPI仕様書へのリンクが配置されます。

```markdown
> **[📋 API仕様書を見る](/api/{ContractName})**
```

## 概要ページ

システム全体の説明と、すべてのコントラクトの一覧を表示します。
各コントラクトについて、ドキュメントと仕様書の両方へのリンクが提供されます。

# 最後に

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

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

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

https://twitter.com/cardene777

https://qiita.com/cardene

https://cardene.substack.com/

https://mirror.xyz/0xACAc709e21e7a4A5a4458BDeeCCA0fc98A6a8A4D
