Source code for erc7730.convert.convert_eip712_to_erc7730

from typing import assert_never, final, override

from eip712 import (
    EIP712DAppDescriptor as LegacyEIP712DAppDescriptor,
)
from eip712 import (
    EIP712Field as LegacyEIP712Field,
)
from eip712 import (
    EIP712Format as LegacyEIP712Format,
)
from pydantic import AnyUrl, RootModel

from erc7730.common.output import OutputAdder
from erc7730.convert import ERC7730Converter
from erc7730.model.context import Deployment, Domain, EIP712Field, EIP712JsonSchema, EIP712Type
from erc7730.model.display import (
    DateEncoding,
    DateParameters,
    FieldFormat,
    TokenAmountParameters,
)
from erc7730.model.input.context import InputEIP712, InputEIP712Context
from erc7730.model.input.descriptor import InputERC7730Descriptor
from erc7730.model.input.display import (
    InputDisplay,
    InputFieldDescription,
    InputFormat,
    InputNestedFields,
    InputReference,
)
from erc7730.model.metadata import Metadata


[docs] @final class EIP712toERC7730Converter(ERC7730Converter[LegacyEIP712DAppDescriptor, InputERC7730Descriptor]): """ Converts Ledger legacy EIP-712 descriptor to ERC-7730 descriptor. Generates 1 output ERC-7730 descriptor per contract, as ERC-7730 descriptors only represent 1 contract. """
[docs] @override def convert( self, descriptor: LegacyEIP712DAppDescriptor, out: OutputAdder ) -> dict[str, InputERC7730Descriptor] | None: descriptors: dict[str, InputERC7730Descriptor] = {} for contract in descriptor.contracts: formats: dict[str, InputFormat] = {} schemas: list[EIP712JsonSchema | AnyUrl] = [] for message in contract.messages: # TODO Improve typing on EIP-712 library to use dict[EIP712Type, list[EIP712Field]] # we serialize/deserialize here to be sure to have the proper types, as in some context we have dicts # instead of classes. schema_dict = RootModel(message.schema_).model_dump() schema = RootModel[dict[EIP712Type, list[EIP712Field]]].model_validate(schema_dict).root if (primary_type := self._get_primary_type(schema, out)) is None: return None schemas.append(EIP712JsonSchema(primaryType=primary_type, types=schema)) formats[primary_type] = InputFormat( intent=message.mapper.label, fields=[self._convert_field(field) for field in message.mapper.fields], required=None, screens=None, ) descriptors[contract.address] = InputERC7730Descriptor( context=InputEIP712Context( eip712=InputEIP712( domain=Domain( name=descriptor.name, version=None, chainId=descriptor.chain_id, verifyingContract=contract.address, ), schemas=schemas, deployments=[Deployment(chainId=descriptor.chain_id, address=contract.address)], ) ), metadata=Metadata( owner=contract.name, info=None, token=None, constants=None, enums=None, ), display=InputDisplay( definitions=None, formats=formats, ), ) return descriptors
@classmethod def _convert_field(cls, field: LegacyEIP712Field) -> InputFieldDescription | InputReference | InputNestedFields: # FIXME must generate nested fields for arrays match field.format: case LegacyEIP712Format.RAW | None: return InputFieldDescription(path=field.path, label=field.label, format=FieldFormat.RAW, params=None) case LegacyEIP712Format.AMOUNT if field.assetPath is not None: return InputFieldDescription( path=field.path, label=field.label, format=FieldFormat.TOKEN_AMOUNT, params=TokenAmountParameters(tokenPath=field.assetPath), ) case LegacyEIP712Format.AMOUNT: return InputFieldDescription(path=field.path, label=field.label, format=FieldFormat.AMOUNT, params=None) case LegacyEIP712Format.DATETIME: return InputFieldDescription( path=field.path, label=field.label, format=FieldFormat.DATE, params=DateParameters(encoding=DateEncoding.TIMESTAMP), ) case _: assert_never(field.format) @classmethod def _get_primary_type(cls, schema: dict[EIP712Type, list[EIP712Field]], out: OutputAdder) -> EIP712Type | None: # TODO _schema_top_level_type() is wrong on EIP-712 library (fails with "SomeType[]" syntax) referenced_types = { field.type.rstrip("[]") for type_name, type_fields in schema.items() for field in type_fields } match len(roots := set(schema.keys()) - referenced_types - {"EIP712Domain"}): case 0: return out.error( title="Invalid EIP-712 schema", message="Primary type could not be determined on EIP-712 schema, as all types are referenced by" "other types. Please make sure your schema has a root type.", ) case 1: return next(iter(roots)) case _: return out.error( title="Invalid EIP-712 schema", message="Primary type could not be determined on EIP-712 schema, as several types are not" "referenced by any other type. Please make sure your schema has a single root type.", )