Source code for erc7730.convert.ledger.eip712.convert_eip712_to_erc7730

from typing import assert_never, final, override

from eip712.model.resolved.descriptor import ResolvedEIP712DAppDescriptor
from eip712.model.resolved.message import ResolvedEIP712MapperField
from eip712.model.schema import EIP712SchemaField, EIP712Type
from eip712.model.types import EIP712Format, EIP712NameSource, EIP712NameType
from eip712.utils import MissingRootTypeError, MultipleRootTypesError, get_primary_type
from pydantic_string_url import HttpUrl

from erc7730.common.output import ExceptionsToOutput, OutputAdder
from erc7730.convert import ERC7730Converter
from erc7730.model.context import EIP712Schema
from erc7730.model.display import (
    AddressNameType,
    DateEncoding,
    FieldFormat,
)
from erc7730.model.input.context import InputDeployment, InputDomain, InputEIP712, InputEIP712Context
from erc7730.model.input.descriptor import InputERC7730Descriptor
from erc7730.model.input.display import (
    InputAddressNameParameters,
    InputDateParameters,
    InputDisplay,
    InputFieldDescription,
    InputFormat,
    InputNestedFields,
    InputReference,
    InputTokenAmountParameters,
)
from erc7730.model.input.metadata import InputMetadata
from erc7730.model.paths import ContainerField, ContainerPath


[docs] @final class EIP712toERC7730Converter(ERC7730Converter[ResolvedEIP712DAppDescriptor, 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: ResolvedEIP712DAppDescriptor, out: OutputAdder ) -> dict[str, InputERC7730Descriptor] | None: with ExceptionsToOutput(out): descriptors: dict[str, InputERC7730Descriptor] = {} for contract in descriptor.contracts: formats: dict[str, InputFormat] = {} schemas: list[EIP712Schema | HttpUrl] = [] for message in contract.messages: if (primary_type := self._get_primary_type(message.schema_, out)) is None: return None schemas.append(EIP712Schema(primaryType=primary_type, types=message.schema_)) formats[primary_type] = InputFormat( intent=message.mapper.label, fields=[self._convert_field(field, out) for field in message.mapper.fields], required=None, screens=None, ) descriptors[contract.address] = InputERC7730Descriptor( context=InputEIP712Context( eip712=InputEIP712( domain=InputDomain( name=descriptor.name, version=None, chainId=descriptor.chainId, verifyingContract=contract.address, ), schemas=schemas, deployments=[InputDeployment(chainId=descriptor.chainId, address=contract.address)], ) ), metadata=InputMetadata( owner=contract.contractName, info=None, token=None, constants=None, enums=None, ), display=InputDisplay( definitions=None, formats=formats, ), ) return descriptors
@classmethod def _convert_field( cls, field: ResolvedEIP712MapperField, out: OutputAdder ) -> InputFieldDescription | InputReference | InputNestedFields: # FIXME must generate nested fields for arrays match field.format: case EIP712Format.RAW | None: return InputFieldDescription(path=field.path, label=field.label, format=FieldFormat.RAW, params=None) case EIP712Format.AMOUNT if field.assetPath is not None: return InputFieldDescription( path=field.path, label=field.label, format=FieldFormat.TOKEN_AMOUNT, params=InputTokenAmountParameters(tokenPath=field.assetPath), ) case EIP712Format.AMOUNT: return InputFieldDescription( path=field.path, label=field.label, format=FieldFormat.TOKEN_AMOUNT, params=InputTokenAmountParameters(tokenPath=ContainerPath(field=ContainerField.TO)), ) case EIP712Format.DATETIME: return InputFieldDescription( path=field.path, label=field.label, format=FieldFormat.DATE, params=InputDateParameters(encoding=DateEncoding.TIMESTAMP), ) case EIP712Format.TRUSTED_NAME: field_format = ( FieldFormat.NFT_NAME if EIP712NameType.COLLECTION in field.nameTypes else FieldFormat.ADDRESS_NAME ) return InputFieldDescription( path=field.path, label=field.label, format=field_format, params=InputAddressNameParameters( types=cls.convert_trusted_names_types(field.nameTypes, out), sources=cls.convert_trusted_names_sources(field.nameSources), ), ) case _: assert_never(field.format) @classmethod def _get_primary_type( cls, schema: dict[EIP712Type, list[EIP712SchemaField]], out: OutputAdder ) -> EIP712Type | None: try: return get_primary_type(schema) except MissingRootTypeError: 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.", ) except MultipleRootTypesError: 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.", )
[docs] @classmethod def convert_trusted_names_types( cls, types: list[EIP712NameType] | None, out: OutputAdder ) -> list[AddressNameType] | None: if types is None: return None name_types: list[AddressNameType] = [] for name_type in types: match name_type: case EIP712NameType.WALLET: name_types.append(AddressNameType.WALLET) case EIP712NameType.EOA: name_types.append(AddressNameType.EOA) case EIP712NameType.SMART_CONTRACT: name_types.append(AddressNameType.CONTRACT) case EIP712NameType.TOKEN: name_types.append(AddressNameType.TOKEN) case EIP712NameType.COLLECTION: name_types.append(AddressNameType.COLLECTION) case EIP712NameType.CONTEXT_ADDRESS: return out.error("EIP712 context_address trusted name type is not supported in ERC-7730") case _: assert_never(name_type) return name_types
[docs] @classmethod def convert_trusted_names_sources(cls, sources: list[EIP712NameSource] | None) -> list[str] | None: if sources is None: return None name_sources: list[str] = [] for name_source in sources: if name_source == EIP712NameSource.LOCAL_ADDRESS_BOOK: # ERC-7730 specs defines "local" as an example name_sources.append("local") else: name_sources.append(name_source.value) return name_sources