Skip to content

feat: v2 implementation (EXPERIMENTAL)#255

Open
fsamier wants to merge 27 commits intomainfrom
feat/model-v2
Open

feat: v2 implementation (EXPERIMENTAL)#255
fsamier wants to merge 27 commits intomainfrom
feat/model-v2

Conversation

@fsamier
Copy link
Contributor

@fsamier fsamier commented Feb 12, 2026

Context: https://github.com/ethereum/ERCs/blob/master/assets/erc-7730/erc7730-v2.schema.json

NOTE: v2 support is experimental and not fully tested. The only guarantee is v1 full backward-compatibility

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an ERC-7730 v2 pipeline to the codebase: new v2 input/resolved Pydantic models, an input→resolved converter, v2-specific linters, and golden-file tests validating conversion output.

Changes:

  • Introduce v2 input + resolved descriptor models (context/metadata/display) and discriminated unions.
  • Implement v2 input→resolved conversion (selectors, references, params, constants/maps/enums).
  • Add v2 linting entrypoints + linters and golden reference tests with sample v2 descriptors.

Reviewed changes

Copilot reviewed 66 out of 69 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/v2/convert/resolved/test_convert_input_to_resolved.py Golden-file conversion tests for v2 input→resolved.
tests/v2/convert/resolved/data/calldata_with_extended_params_input.json Input fixture for calldata params conversion.
tests/v2/convert/resolved/data/calldata_with_extended_params_resolved.json Expected resolved output for calldata params conversion.
tests/v2/convert/resolved/data/deprecated_abi_ignored_input.json Input fixture asserting deprecated ABI is ignored.
tests/v2/convert/resolved/data/deprecated_abi_ignored_resolved.json Expected resolved output with deprecated ABI removed/ignored.
tests/v2/convert/resolved/data/deprecated_schemas_ignored_input.json Input fixture asserting deprecated EIP-712 schemas are ignored.
tests/v2/convert/resolved/data/deprecated_schemas_ignored_resolved.json Expected resolved output with deprecated schemas removed/ignored.
tests/v2/convert/resolved/data/field_group_basic_input.json Input fixture for field-group scoping.
tests/v2/convert/resolved/data/field_group_basic_resolved.json Expected resolved output for field-group scoping.
tests/v2/convert/resolved/data/field_group_with_iteration_input.json Input fixture for field-group iteration.
tests/v2/convert/resolved/data/field_group_with_iteration_resolved.json Expected resolved output for field-group iteration.
tests/v2/convert/resolved/data/field_group_with_label_input.json Input fixture for field-group labels.
tests/v2/convert/resolved/data/field_group_with_label_resolved.json Expected resolved output for field-group labels.
tests/v2/convert/resolved/data/field_with_encryption_input.json Input fixture for encryption metadata on fields.
tests/v2/convert/resolved/data/field_with_encryption_resolved.json Expected resolved output for encryption metadata.
tests/v2/convert/resolved/data/field_with_separator_input.json Input fixture for array separator handling.
tests/v2/convert/resolved/data/field_with_separator_resolved.json Expected resolved output for array separator handling.
tests/v2/convert/resolved/data/field_with_visibility_conditions_input.json Input fixture for conditional visibility rules.
tests/v2/convert/resolved/data/field_with_visibility_conditions_resolved.json Expected resolved output for conditional visibility rules.
tests/v2/convert/resolved/data/field_with_visibility_simple_input.json Input fixture for simple visibility rules.
tests/v2/convert/resolved/data/field_with_visibility_simple_resolved.json Expected resolved output for simple visibility rules.
tests/v2/convert/resolved/data/format_chain_id_input.json Input fixture for chainId field format.
tests/v2/convert/resolved/data/format_chain_id_resolved.json Expected resolved output for chainId field format.
tests/v2/convert/resolved/data/format_interoperable_address_name_input.json Input fixture for interoperable address name format.
tests/v2/convert/resolved/data/format_interoperable_address_name_resolved.json Expected resolved output for interoperable address name format.
tests/v2/convert/resolved/data/format_token_ticker_input.json Input fixture for token ticker format.
tests/v2/convert/resolved/data/format_token_ticker_resolved.json Expected resolved output for token ticker format.
tests/v2/convert/resolved/data/format_with_interpolated_intent_input.json Input fixture for interpolated intent strings.
tests/v2/convert/resolved/data/format_with_interpolated_intent_resolved.json Expected resolved output for interpolated intent strings.
tests/v2/convert/resolved/data/metadata_with_contract_name_input.json Input fixture for metadata.contractName.
tests/v2/convert/resolved/data/metadata_with_contract_name_resolved.json Expected resolved output for metadata.contractName.
tests/v2/convert/resolved/data/metadata_with_maps_input.json Input fixture for metadata.maps.
tests/v2/convert/resolved/data/metadata_with_maps_resolved.json Expected resolved output for metadata.maps.
tests/v2/convert/resolved/data/minimal_contract_v2_input.json Minimal contract-context v2 input fixture.
tests/v2/convert/resolved/data/minimal_contract_v2_resolved.json Expected resolved output for minimal contract-context fixture.
tests/v2/convert/resolved/data/minimal_eip712_v2_input.json Minimal EIP-712-context v2 input fixture.
tests/v2/convert/resolved/data/minimal_eip712_v2_resolved.json Expected resolved output for minimal EIP-712-context fixture.
tests/v2/convert/resolved/data/token_amount_with_chainid_input.json Input fixture for tokenAmount + chainIdPath parameters.
tests/v2/convert/resolved/data/token_amount_with_chainid_resolved.json Expected resolved output for tokenAmount + chainIdPath parameters.
tests/v2/convert/resolved/init.py Test package init for v2 resolved conversion tests.
tests/v2/convert/init.py Test package init for v2 conversion tests.
tests/v2/init.py Test package init for v2 tests.
src/erc7730/model/input/v2/init.py v2 input model package init.
src/erc7730/model/input/v2/context.py v2 input context models (contract/EIP-712).
src/erc7730/model/input/v2/descriptor.py v2 input descriptor root model.
src/erc7730/model/input/v2/display.py v2 input display/field/params models (incl. groups, visibility, maps).
src/erc7730/model/input/v2/format.py v2 field format enums.
src/erc7730/model/input/v2/metadata.py v2 input metadata models (incl. maps/enums/constants).
src/erc7730/model/input/v2/unions.py v2 discriminators for fields/params/visibility unions.
src/erc7730/model/resolved/v2/init.py v2 resolved model package init.
src/erc7730/model/resolved/v2/context.py v2 resolved context models with normalized addresses and deprecated fields dropped.
src/erc7730/model/resolved/v2/descriptor.py v2 resolved descriptor root model.
src/erc7730/model/resolved/v2/display.py v2 resolved display/field/params models.
src/erc7730/model/resolved/v2/metadata.py v2 resolved metadata models (resolved enums/maps).
src/erc7730/convert/resolved/v2/init.py v2 conversion helpers package init.
src/erc7730/convert/resolved/v2/constants.py v2 constant provider + path resolution helpers (incl. map reference scaffolding).
src/erc7730/convert/resolved/v2/convert_erc7730_input_to_resolved.py Main v2 input→resolved converter implementation.
src/erc7730/convert/resolved/v2/enums.py Enum reference parsing/validation helpers for v2 conversion.
src/erc7730/convert/resolved/v2/parameters.py v2 parameter resolution for field formats (token amount/ticker, calldata, etc.).
src/erc7730/convert/resolved/v2/references.py v2 display definition reference inlining.
src/erc7730/convert/resolved/v2/values.py v2 field value/path resolution + constant encoding.
src/erc7730/lint/v2/init.py v2 linter base + MultiLinter runner.
src/erc7730/lint/v2/lint.py v2 lint CLI/entrypoint functions (load→convert→lint).
src/erc7730/lint/v2/lint_transaction_type_classifier.py v2 transaction classification linter (permit heuristics + ABI classifier hook).
src/erc7730/lint/v2/lint_validate_display_fields.py v2 ABI-backed display path validation (via explorer ABI fetch).
src/erc7730/lint/v2/lint_validate_max_length.py v2 max-length checks for Ledger device constraints.
src/erc7730/lint/v2/path_schemas.py v2 schema-path extraction for linting.
specs/erc7730-v1.schema.json Adds/records the v1 JSON schema file under specs/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

fsamier and others added 12 commits February 12, 2026 18:22
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The lint CLI command was hardcoded to use the v1 model, causing false
errors when linting v2 descriptors. This adds auto-detection based on
the $schema field and routes to the v2 linter accordingly. A --v2 flag
is also available for explicit override.
Pure/view functions cannot produce transactions and don't need clear
signing descriptors. Skip them by default in get_functions() to reduce
noise in lint output. The generator still includes all functions via
include_read_only=True.
rich.print wraps long strings at terminal width, inserting literal
newlines into JSON string values (e.g. hex descriptor fields). This
corrupts the output when piped or captured. Use builtins.print for
schema, resolve, and calldata commands that output raw JSON.
The calldata command now auto-detects v2 descriptors (via $schema field)
and builds ABI Function objects from human-readable format keys in
display.formats, instead of requiring an embedded ABI in the contract
context. Fields with visible="never" are skipped to match v1 behavior.
- `erc7730 resolve` now auto-detects v2 descriptors and uses the v2
  resolver (or accepts --v2 flag).
- `erc7730 convert erc7730-to-eip712` now detects v2 descriptors early
  and exits with a clear error: v2 drops embedded EIP-712 schemas, so
  conversion to legacy format is not supported.
Reconstruct EIP-712 schemas from v2 descriptors where the embedded
schemas have been removed. The display.formats keys (encodeType strings)
are parsed back into full EIP-712 type definitions and the EIP712Domain
is rebuilt from the domain/deployment info.

- Add `parse_encode_type()` utility to reverse encodeType strings
- New `ERC7730V2toEIP712Converter` for v2 -> legacy EIP-712 conversion
- Update `command_convert_erc7730_to_eip712` to dispatch to v2 converter
- Add `validate_eip712_domain_fields()` to warn on non-standard or
  mis-ordered EIP712Domain fields in v1 descriptors
- V2 domain reconstruction emits fields in canonical EIP-712 order:
  name, version, chainId, verifyingContract, salt
- Emit name, version, salt only when present in domain (instead of always)
- Emit chainId + verifyingContract when deployments exist (unchanged)
- Add salt field to v2 InputDomain and ResolvedDomain models
- Propagate salt through the resolve step
- Add ValidateEIP712DomainLinter that checks EIP712Domain fields are in
  canonical EIP-712 order and warns about non-standard field names
- Incorrect ordering is reported as a critical error
- Move validate_eip712_domain_fields from converter to linter module
- Reuse the validation function from the v1 converter
- Remove unused imports flagged by pycln (function_to_selector,
  ResolvedValueConstant)
- Break long line in main.py to satisfy ruff E501 (120 char limit)
- Add nosec B112 to intentional try/except/continue in _any_v2_descriptor
- Pin sphinx<9 to work around sphinx-github-style IndexError with Sphinx 9
The lockfile hash was out of sync after pinning sphinx<9,
causing CI to fail on `pdm install --frozen-lockfile`.
- parameters.py: use data_or_container_path_concat instead of unsupported
  + operator on DataPath
- convert_erc7730_v2_to_eip712.py: convert path objects to str for
  _resolve_calldata_param_path, remove non-existent chainId access on v2
  ResolvedCallDataParameters
- convert_erc7730_v2_input_to_calldata.py: narrow value type to ScalarType,
  convert field.path to str before passing to _convert_v2_value
- main.py: use distinct variable name to avoid type conflict across
  if/else branches with walrus operator
Sphinx 8.2.x introduced a dependency on roman-numerals-py which causes
"No module named 'roman_numerals'" when loading sphinx.builders.latex.
Pinning to 8.1.x avoids this dependency entirely.
The GitHubLexer.get_tokens_unprocessed method accesses tokens[idx+1]
without bounds checking, causing IndexError when a builtin class or
Name token is the last in the list. This is a bug in sphinx-github-style
1.2.2 (latest). Added a monkey-patch in conf.py to add the missing
bounds check.
@github-actions github-actions bot added the documentation Improvements or additions to documentation label Feb 15, 2026
lcastillo-ledger and others added 3 commits February 16, 2026 22:17
The Address type normalizes addresses to lowercase via a BeforeValidator,
which caused v2 calldata output to differ from v1 for constant token
addresses. Use MixedCaseAddress instead to preserve the original casing
from the descriptor file, aligning v2 behavior with v1.
Align v2 resolved/calldata conversion with current schema semantics by handling optional encryption on shared field definitions and normalizing path/value parameter resolution. Add regression fixtures for encryption-by-definition and updated resolved outputs to keep v2 conversion tests consistent.
@fsamier fsamier changed the title feat: v2 implementation feat: v2 implementation (EXPERIMENTAL) Mar 4, 2026
@fsamier fsamier marked this pull request as ready for review March 4, 2026 09:47
@fsamier fsamier requested a review from a team as a code owner March 4, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies documentation Improvements or additions to documentation sources

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants