import json
from pathlib import Path
from typing import Annotated, assert_never
from eip712.convert.input_to_resolved import EIP712InputToResolvedConverter
from eip712.model.input.descriptor import InputEIP712DAppDescriptor
from pydantic_string_url import HttpUrl
from rich import print
from typer import Argument, Exit, Option, Typer
from erc7730.common.output import ConsoleOutputAdder
from erc7730.convert.convert import convert_to_file_and_print_errors
from erc7730.convert.ledger.eip712.convert_eip712_to_erc7730 import EIP712toERC7730Converter
from erc7730.convert.ledger.eip712.convert_erc7730_to_eip712 import ERC7730toEIP712Converter
from erc7730.convert.resolved.convert_erc7730_input_to_resolved import ERC7730InputToResolved
from erc7730.format.format import format_all_and_print_errors
from erc7730.generate.generate import generate_descriptor
from erc7730.lint.lint import lint_all_and_print_errors
from erc7730.list.list import list_all
from erc7730.model import ERC7730ModelType
from erc7730.model.base import Model
from erc7730.model.input.descriptor import InputERC7730Descriptor
from erc7730.model.resolved.descriptor import ResolvedERC7730Descriptor
from erc7730.model.types import Address
app = Typer(
name="erc7730",
no_args_is_help=True,
help="""
ERC-7730 tool.
""",
)
convert_app = Typer(
name="convert",
no_args_is_help=True,
short_help="Commands to convert descriptor files.",
help="""
Commands to convert descriptor files.
""",
)
app.add_typer(convert_app)
[docs]
@app.command(
name="schema",
short_help="Print ERC-7730 descriptor JSON schema.",
help="""
Print ERC-7730 descriptor JSON schema.
""",
)
def command_schema(
model_type: Annotated[ERC7730ModelType, Argument(help="The descriptor form ")] = ERC7730ModelType.INPUT,
) -> None:
descriptor_type: type[Model]
match model_type:
case ERC7730ModelType.INPUT:
descriptor_type = InputERC7730Descriptor
case ERC7730ModelType.RESOLVED:
descriptor_type = ResolvedERC7730Descriptor
case _:
assert_never(model_type)
print(json.dumps(descriptor_type.model_json_schema(by_alias=True), indent=4))
[docs]
@app.command(
name="lint",
short_help="Validate descriptor files.",
help="""
Validate descriptor files.
""",
)
def command_lint(
paths: Annotated[list[Path], Argument(help="The files or directory paths to lint")],
gha: Annotated[bool, Option(help="Enable Github annotations output")] = False,
) -> None:
if not lint_all_and_print_errors(paths, gha):
raise Exit(1)
[docs]
@app.command(
name="list",
short_help="List descriptor files.",
help="""
Recursively list all descriptor files, starting from current directory by default.
""",
)
def command_list(
paths: Annotated[list[Path] | None, Argument(help="The files or directory paths to search")] = None,
) -> None:
if not list_all(paths or [Path.cwd()]):
raise Exit(1)
[docs]
@app.command(
name="resolve",
short_help="Convert descriptor to resolved form.",
help="""
Convert descriptor to resolved form:
- URLs fetched
- Contract addresses normalized to lowercase
- References inlined
- Constants inlined
- Field definitions inlined
- Nested fields flattened where possible
- Selectors converted to 4 bytes form
See `erc7730 schema resolved` for the resolved descriptor schema.
""",
)
def command_resolve(
input_path: Annotated[Path, Argument(help="The input ERC-7730 file path")],
) -> None:
input_descriptor = InputERC7730Descriptor.load(input_path)
if (resolved_descriptor := ERC7730InputToResolved().convert(input_descriptor, ConsoleOutputAdder())) is None:
raise Exit(1)
print(resolved_descriptor.to_json_string())
[docs]
@app.command(
name="generate",
short_help="Bootstrap a descriptor for given ABI/schema.",
help="""
Fetches ABI or schema files and generates a minimal descriptor.
""",
)
def command_generate(
chain_id: Annotated[int, Option(help="The EIP-155 chain id")],
address: Annotated[Address, Option(help="The contract address")],
abi: Annotated[Path | None, Option(help="Path to a JSON ABI file (to generate a calldata descriptor)")] = None,
schema: Annotated[Path | None, Option(help="Path to an EIP-712 schema (to generate an EIP-712 descriptor)")] = None,
owner: Annotated[str | None, Option(help="The display name of the owner or target of the contract")] = None,
legal_name: Annotated[str | None, Option(help="The full legal name of the owner")] = None,
url: Annotated[str | None, Option(help="URL with more info on the entity interacted with")] = None,
) -> None:
descriptor = generate_descriptor(
chain_id=chain_id,
contract_address=address,
abi_file=abi,
eip712_schema_file=schema,
owner=owner,
legal_name=legal_name,
url=HttpUrl(url) if url is not None else None,
)
print(descriptor.to_json_string())
[docs]
@convert_app.command(
name="eip712-to-erc7730",
short_help="Convert a legacy EIP-712 descriptor file to an ERC-7730 file.",
help="""
Convert a legacy EIP-712 descriptor file to an ERC-7730 file.
""",
)
def command_convert_eip712_to_erc7730(
input_eip712_path: Annotated[Path, Argument(help="The input EIP-712 file path")],
output_erc7730_path: Annotated[Path, Argument(help="The output ERC-7730 file path")],
) -> None:
input_descriptor = InputEIP712DAppDescriptor.load(input_eip712_path)
resolved_descriptor = EIP712InputToResolvedConverter().convert(input_descriptor)
if not convert_to_file_and_print_errors(
input_descriptor=resolved_descriptor,
output_file=output_erc7730_path,
converter=EIP712toERC7730Converter(),
):
raise Exit(1)
[docs]
@convert_app.command(
name="erc7730-to-eip712",
short_help="Convert an ERC-7730 file to a legacy EIP-712 descriptor file.",
help="""
Convert an ERC-7730 file to a legacy EIP-712 descriptor file (if applicable).
""",
)
def command_convert_erc7730_to_eip712(
input_erc7730_path: Annotated[Path, Argument(help="The input ERC-7730 file path")],
output_eip712_path: Annotated[Path, Argument(help="The output EIP-712 file path")],
) -> None:
input_descriptor = InputERC7730Descriptor.load(input_erc7730_path)
resolved_descriptor = ERC7730InputToResolved().convert(input_descriptor, ConsoleOutputAdder())
if resolved_descriptor is None or not convert_to_file_and_print_errors(
input_descriptor=resolved_descriptor,
output_file=output_eip712_path,
converter=ERC7730toEIP712Converter(),
):
raise Exit(1)
if __name__ == "__main__":
app()