From c083f299959307ba660567990f3fb8822a5cd9df Mon Sep 17 00:00:00 2001 From: Laurent Castillo Date: Fri, 27 Mar 2026 11:04:43 +0100 Subject: [PATCH 1/2] feat: widen v2 schema fields to accept constants and map references Update the ERC-7730 v2 JSON schema and spec to allow constant references ($.metadata.constants.*) and map references alongside literal values in many fields. Schema changes: - Add reusable definitions: stringValueOrMap, integerValueOrMap, stringOrArrayValueOrMap, scalarValueOrMap - Update parameter fields (token, callee, selector, amount, spender, chainId, collection, nativeCurrencyAddress, threshold, message, senderAddress) to use shared definitions - Update metadata fields (owner, contractName) and context fields (domain.name, domain.version) to accept map references - Update display fields (label, value, interpolatedIntent, intent) to accept map references - Tighten mapReference definition: require map + keyPath, disallow additional properties Spec changes: - Update eip712.domain description to note constant/map reference support - Update intent description to document three forms (string, object, map ref) - Update field format specification for value and label to note they accept constant/map references Made-with: Cursor --- specs/erc-7730.md | 1280 +++++++++++++++++++++++----------- specs/erc7730-v2.schema.json | 174 ++--- 2 files changed, 959 insertions(+), 495 deletions(-) diff --git a/specs/erc-7730.md b/specs/erc-7730.md index 6317950a1..f7af5d93a 100644 --- a/specs/erc-7730.md +++ b/specs/erc-7730.md @@ -1,44 +1,39 @@ --- eip: 7730 title: Structured Data Clear Signing Format -description: Draft specification of a json format providing additional description of how to clear-sign smart contract calls and typed messages -author: Laurent Castillo (@lcastillo-ledger) +description: JSON format describing how to clear-sign smart contract calls and typed messages. +author: Laurent Castillo (@lcastillo-ledger), Derek Rein (@arein), Pierre Aoun (@paoun-ledger), Arik Galansky (@arikg), Bartosz Rozwarski (@llbartekll), Kaan Uzdogan (@kuzdogan), Fredrik (@Fredrik0x) discussions-to: https://ethereum-magicians.org/t/eip-7730-proposal-for-a-clear-signing-standard-format-for-wallets/20403 status: Draft type: Standards Track category: ERC created: 2024-02-07 -requires: 712 +requires: 155, 712 --- ## Abstract -This specification defines a JSON format carrying additional information required to correctly display structured data to a human for review on a wallet screen, before signature by the wallet. +This specification defines a JSON format carrying additional information required to correctly display structured data for human verification on wallet screens or for machine consumption e.g. by transaction simulation engines. -The [ERC-7730](./eip-7730.md) specification enriches type data contained in the ABIs and schemas of structured messages (structures like the calldata of an EVM transaction or an [EIP-712](./eip-712.md) message) with additional formatting information, so that wallets can construct a better UI when displaying the data before signature. For instance, a solidity field containing an amount, encoded as an uint256, can be converted to the right magnitude and appended with the correct ticker. +The [ERC-7730](./eip-7730.md) specification enriches type data contained in the ABIs and schemas of structured messages (structures like the calldata of an EVM transaction, an [EIP-712](./eip-712.md) message or an [EIP-4337](./eip-4337.md) User Operation) with additional formatting information and dynamic value interpolation, enabling both human-readable display with contextual intent descriptions and machine-interpretable data processing. For instance, a solidity field containing an amount, encoded as an uint256, can be converted to the right magnitude and appended with the correct ticker for display, or parsed programmatically for transaction simulation. Fields containing encrypted values (such as FHE-encrypted amounts in confidential token standards like [ERC-7984](./eip-7984.md)) can also be annotated with decryption context, enabling wallets to either decrypt and display the plaintext value or present a meaningful fallback. -Wallets will use (wallet-curated) ERC-7730 files alongside the raw data to sign in order to construct a display adapted to be reviewed by humans. +Wallets and automated systems will use curated ERC-7730 files alongside the raw data to sign in order to construct appropriate interfaces for their respective use cases. -In the current scope, the only structured data supported are smart contract calls (aka calldata) in EVM transactions and EIP-712 structured messages. +This enables significantly improved signing user experiences and lower end-user risk from frontend and phishing attacks. ## Motivation -Properly validating a transaction on a Hardware Wallets screen (also known as Clear Signing) is a key element of good security practices for end users when interacting with any Blockchain. Unfortunately, most data to sign, even enriched with the data structure description (like ABIs or EIP-712 types) are not self-sufficient in terms of correctly displaying them to the user for review. Among other things: +Properly validating a transaction on a hardware wallet's screen (also known as Clear Signing) is a key element of good security practices for end users when interacting with any Blockchain. Unfortunately, most data to sign, even enriched with the data structure description (like ABIs or EIP-712 types) are not self-sufficient in terms of correctly displaying them to users for review. Among other things: -* Function name or main message type is often a developer oriented name and does not translate to a clear intent for the user -* Fields in the data to sign are tied to primitive types only, but those can be displayed in many different ways. For instance, integers can be displayed as percentages, dates, etc... -* Some fields require additional metadata to be displayed correctly, for instance token amounts require knowledge of the decimals and the ticker, as well as where to find the token address itself to be correctly formatted. +- Function name or main message type is often a developer oriented name and does not translate to a clear intent for the user +- Fields in the data to sign are tied to primitive types only, but those can be displayed in many different ways. For instance, integers can be displayed as percentages, dates, etc... +- Some fields require additional metadata to be displayed correctly, for instance token amounts require knowledge of the decimals and the ticker, as well as where to find the token address itself to be correctly formatted. +- Some values can be encrypted, such as encrypted amounts in confidential tokens following [ERC-7984](./eip-7984.md). These values cannot be directly interpreted, so additional context must be provided to enable wallets to decrypt and display them for users. This specification intends to provide a simple, open standard format to provide wallets with the additional information required to properly format a structured data to sign for review by users. -Providing this additional formatting information requires deep knowledge of the way a smart contract or message is going to be used. It is expected that DApp developers will be the best placed to write such a file. The intent of an open standard is to only write this file once and have it work with most wallets supporting this standard. +Providing this additional formatting information requires deep knowledge of the way a smart contract or message is going to be used. It is expected that app developers will be the best placed to write such a file. The intent of an open standard is to only write this file once and have it work with most wallets supporting this standard. -> *Deployment model* -> -> Making the ERC-7730 available for wallets is a key factor of adoption. We have a few options: -> - In each dApps GitHub repository or website: good for autonomy but can be problematic for discoverability by wallets of new ERC-7730 files -> - Foundation operated repository, like ethereum chainID list: good alternative between decentralization and discoverability. -> - Ledger repository: as a short term solution, Ledger is providing a central repository (See Ledger GitHub repository) ## Specification @@ -46,16 +41,15 @@ The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SH ### Simple example -The following is a simple example of how to clear sign a `transfer` function call on an [ERC-20](./eip-20.md) contract. +The following is an example of how to clear sign a `transfer` function call on an [ERC-20](./eip-20.md) contract. ```json { - "$schema": "https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/specs/erc7730-v1.schema.json", + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-v2.schema.json", "context": { - "$id": "Tether USD", + "$id": "Example ERC-20", "contract" : { - "abi": "https://api.etherscan.io/api?module=contract&action=getabi&address=0xdac17f958d2ee523a2206206994597c13d831ec7", "deployments": [ { "chainId": 1, @@ -74,63 +68,67 @@ The following is a simple example of how to clear sign a `transfer` function cal }, "metadata": { - "owner": "Tether", + "owner": "Example", + "contractName": "Example Token", "info": { - "legalName": "Tether Limited", - "url": "https://tether.to/", + "url": "https://example.io/", "deploymentDate": "2017-11-28T12:41:21Z" } }, "display": { "formats": { - "transfer(address _to,uint256 _value)": { + "transfer(address to,uint256 value)": { "intent": "Send", + "interpolatedIntent": "Send {value} to {to}", "fields": [ { - "path": "_to", - "label": "To", - "format": "addressOrName" - }, - { - "path": "_value", + "path": "value", "label": "Amount", "format": "tokenAmount", "params": { "tokenPath": "@.to" } + }, + { + "path": "to", + "label": "To", + "format": "addressName" } - ], - "required": ["_to", "_value"], - "excluded": [] + ] } } } } ``` -The `$schema` key refers to the latest version of this specification json schema (currently version 1). +The `$schema` key refers to the latest version of this specification json schema (version 1 at the time of writing). The `context` key is used to provide binding context information for this file. It can be seen as a set of *constraints* on the structured data being reviewed, indicating whether the ERC-7730 file is valid for this data. A wallet MUST ensure these constraints are met before ERC-7730 formatting information is applied to the data being signed. -In this example, the context section indicates that the ERC-7730 file should only be applied to the USDT smart contract whose deployment addresses are provided and only if they match the reference ABI. Note that the ABI is used to define unique *path* references to fields being formatted in the `display.formats` section, so the ABI is mandatory for all contracts clear-signed. +In this example, the context section indicates that the ERC-7730 file should only be applied to the Example smart contract whose deployment addresses are provided. Once the contract is matched, wallets use the `display.formats` entry itself to derive the function selector, parameter layout, and user-facing labels. The `metadata` section contains constants that can be trusted when the ERC-7730 file is applicable for the given context. This section is typically used to: -* Provide displayable information about the recipient of the contract call / message -* Provide displayable values of enumeration or encoded id parameters, typically smart contract / message specific -* Provide common constants used by the various formats defined in the file +- Provide displayable information about the recipient of the contract call / message +- Provide displayable values of enumeration or encoded id parameters, typically smart contract / message specific +- Provide common constants used by the various formats defined in the file -In this example, the metadata section contains only the recipient information, in the form of a displayable name (`owner` key) and additional information (`info` key) that MAY be used by wallets to provide details about the recipient. +In this example, the metadata section contains only the recipient information, in the form of a displayable name (`owner` key), contract displayable name (`contractName` key) and additional information (`info` key) that MAY be used by wallets to provide details about the recipient. Finally, the `display` section contains the definitions of how to format each field of targeted contract calls under the `formats` key. -In this example, the function being described is identified by its solidity signature `transfer(address _to,uint256 _value)`. This is the signature used to compute the function selector `0xa9059cbb` (using the solidity signature guarantees unicity in the context of the contract). -* The `intent` key contains a human-readable string that wallets SHOULD display to explain to the user the intent of the function call. -* The `fields` key contains all the parameters that CAN be displayed, and the way to format them -* The `required` key indicates which parameters wallets SHOULD display. -* The `excluded` key indicates which parameters are intentionally left out (none in this example). +In this example, the function being described is identified by its human-readable ABI fragment `transfer(address to,uint256 value)`. Wallets strip the parameter names to compute the type-only signature `transfer(address,uint256)`, hash it, and match the resulting selector `0xa9059cbb` against the calldata. +- The `intent` key contains a human-readable string that wallets SHOULD display to explain to the user the intent of the function call. +- The `fields` key contains all the parameters formatting information +- The `interpolatedIntent` key is a suggested short string representation of intent and fields that wallet CAN use instead of having a dedicated layout. -In this example, the `_to` parameter and the `_value` SHOULD both be displayed, one as an address replaceable by a trusted name (ENS or others), the other as an amount formatted using metadata of the target ERC-20 contract (USDT). +In this example, the `to` parameter and the `value` parameter SHOULD both be displayed, one as an address replaceable by a trusted name (ENS or others), the other as an amount formatted using metadata of the target ERC-20 contract. + +### Versioning + +The version of the specification used by the 7730 file is specified by linking to the reference schema file under the `$schema` key. + +The current version of the specification is `2` at the time of writing. The reference schema for version `2` is [here](../assets/eip-7730/erc7730-v2.schema.json). ### Common concepts @@ -138,51 +136,32 @@ In this example, the `_to` parameter and the `_value` SHOULD both be displayed, In all the specification, key names starting with `$` are *internal* and have no value beyond readability of the specification file itself. They should not be used in any function to build the UI to review structured data. -#### Structured Data +#### Structured data This specification intends to be extensible to describe the display formatting of any kind of *structured data*. By *Structured data*, we target any data format that has: -* A well-defined *type* system; the data being described itself being of a specific type -* A description of the type system, the *schema*, that should allow splitting the data into *fields*, each field clearly identified with a *path* that can be described as a string. +- A well-defined *type* system; the data being described itself being of a specific top-level type +- A description of the type system, the *schema*, that should allow splitting the data into *fields*, each field clearly identified with a *path* that can be described as a string. Displaying structured data is often done by wallets to review its content before authorizing an action in the form of a *signature* over some serialization of the structured data. As such, the structured data is contained in a *container structure*: -* Container structure has a well-defined *signature* scheme (a serialization scheme, a hashing scheme, and signature algorithm). -* The container structure does not necessarily follow the same type system as the structured data. -* Wallets receive the full container structure and uses the signature scheme to generate a signature on the overall structure. - -```mermaid -block-beta - columns 1 - block:container - columns 3 - title["Container Structure"] space:2 - a["Data"] - struct["Structured Data"] - c["Data"] - end - blockArrowId4<["Wallet"]>(down) - block:signature - columns 3 - space sig["Signature"] space - end - style title fill:transparent,stroke:transparent - style struct fill:#555 -``` +- Container structure has a well-defined *signature* scheme (a serialization scheme, a hashing scheme, and signature algorithm). +- The container structure does not necessarily follow the same type system as the structured data. +- Wallets receive the full container structure and uses the signature scheme to generate a signature on the overall structure. + +![Structured Data and Container](../assets/eip-7730/structured-data.svg) Current specification covers EVM smart contract calldata: -* Defined in Solidity -* The schema is the ABI (expected in json format when linked to) -* The container structure is an EVM Transaction serialized in RLP encoding +- Defined in Solidity +- The schema is the function ABI (derived from the matched `display.formats` function fragment) +- The container structure is an EVM Transaction serialized in RLP encoding It also supports EIP-712 messages -* Defined in EIP-712 -* The schema is extracted from the message itself, from the `types` definitions and the `primaryType` key. -* An EIP-712 message is self-contained, the signature is applied to the hashed message itself. - -In the future, it could be extended to structured data like Meta Transaction in [EIP-2771](./eip-2771.md) or User Operations in [EIP-4337](./eip-4337.md). +- Defined in EIP-712 +- The schema is extracted from the type string representation carried in the `display.formats` message fragments. +- An EIP-712 message is self-contained, the signature is applied to the hashed message itself following EIP-712 specification. -The *schema* is referenced and bound to this file under the `context` key. In turn, it enables using *path* strings to target specific fields in the `display` section to describe what formatting should be applied to these fields before display. +The *schema* is defined by the binding context together with the selected display entry. For contracts, the matched `display.formats` key provides the full function signature (types and parameter names); for EIP-712 messages, the matched `display.formats` provides the type encoding of the message. Both form of type definition allows defining unique *paths* pointing to specific fields in the data. Formats are dependent on and defined for the underlying *types* on the structured data. The [Reference](#reference) section covers formats and types supported by this current version of the specification. @@ -193,38 +172,153 @@ It is sometime necessary for formatting of fields of the structured data to refe This specification uses a limited [json path](https://www.rfc-editor.org/rfc/rfc9535) notation to reference values that can be found in multiple json documents. Limitation to the json path specification are the following: -* Paths MUST use the dot notation, including for slice and array selectors (i.e. an element of an array should be selected through `array_name.[index]`) -* Only name, index and slices selectors are supported. +- Paths MUST use the dot notation, including for slice and array selectors (i.e. an element of an array should be selected through `array_name.[index]`) +- Only name, index and slices selectors are supported. * Slices selectors MUST NOT contain the optional step. Start index is inclusive, end index is exclusive. Start or end index can be omitted to indicate the beginning or end of the array. In addition, additional *roots* are introduced to support description of paths over multiple files in a single common notation. The root node identifier indicates which document or data location this path references, according to the following table: -| Root node identifier | Refers to | Value location | -|----------------------|---------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| -| # | Path applies to the structured data *schema* (ABI path for contracts, path in the message types itself for EIP-712) | Values can be found in the serialized representation of the structured data | -| $ | Path applies to the current file describing the structured data formatting, after merging with includes | Values can be found in the merged file itself | -| @ | Path applies to the container of the structured data to be signed | Values can be found in the serialized container to sign, and are uniquely defined per container in the [Reference](#reference) section | +| Root | Short summary | +|------|---------------| +| `#` | Structured data schema — decoded function parameters or EIP‑712 message fields | +| `$` | ERC‑7730 specification file (after merging includes) | +| `@` | Container-level values (transaction or message metadata) | + +Notes: +- Omitting the root makes the path relative to the structure being described. If there is no enclosing container, a relative path is equivalent to starting with `#.`. + +##### `#` — structured data (decoded) + +Paths with the `#` root refer to fields in the structured data being signed — e.g. decoded function arguments for contract calldata or fields of an EIP‑712 message. Names of the path selectors match the schema of the transaction or message, found in the keys under `display.formats`. Values come from the serialized structured data once decoded by the wallet. + +*Examples* +- `#.params.amountIn` — the `amountIn` field inside top-level `params`. +- `params.amountIn` (relative) — equivalent when the path is resolved relative to the structured data. +- `#.details.[]` refers to the array with the Permit Details of a PermitBatch message + +##### `$` — merged ERC‑7730 file + +Paths with the `$` root point to values in the ERC‑7730 file itself after any `includes` have been merged by the consumer. Use `$` to reference metadata, definitions, enums, maps and any constants authored in the specification. + +*Examples* +- `$.metadata.enums.interestRateMode` — enumeration values defined in the spec. +- `$.display.definitions.minReceiveAmount` — a shared field definition. -Root nodes and following separator can be omitted, in which case the path is *relative* to the structure being described. In case there is no encapsulating structure, relative paths refer to the top-level structure described in the current file and are equivalent to `#.` root node. +##### `@` — container (transaction / message) -For paths referring to structured data fields, if a field has a variable length primitive type (like `bytes` or `string` in solidity), a slice selector can be appended to the path, to refer to the specific set of bytes indicated by the slice. +The `@` root names values coming from the container that wraps the structured data (for example, an EVM transaction or an EIP‑712 message). These values are container-specific; see the [Reference](#reference) section for the canonical list of container fields (e.g. `@.from`, `@.to`, `@.value`, `@.chainId`). + +*Examples* +- `@.value` — native currency value of an EVM transaction. +- `@.to` — transaction destination (contract address). + +##### Path slices + +For paths referring to structured data fields, if a field has a variable length primitive type (like `bytes` or `string` in solidity), a slice selector can be appended to the path, to refer to the specific set of bytes indicated by the slice. A slice selector can also be appended to arrays to select only the specified part of the array. *Examples* -References to values in the serialized structured data -* `#.params.amountIn` or `params.amountIn` refers to parameter `amountIn` in top-level structure `params` as defined in the ABI * `#.data.path.[0].path.[-1].to` or `data.path.[0].path.[-1].to` refers to the field `to` taken from last member of `path` array, itself taken from first member of enclosing `path` array, itself part of top level `data` structure. * `#.params.path.[:20]` or `#.params.path.[0:20]` refers to the first 20 bytes of the `path` byte array * `#.params.path.[-20:]` refers to the last 20 bytes of the `path` byte array -* `#.details.[]` refers to the array with the Permit Details of a PermitBatch message -References to values in the format specification file -* `$.metadata.enums.interestRateMode` refers to the values of an enum defined in the specification file (typically to convert an integer into a displayed string) -* `$.display.definitions.minReceiveAmount` refers to a common definition reused across fields formatting definition +#### Value interpolation + +The `interpolatedIntent` field supports embedding formatted field values directly within intent strings using interpolation syntax. This allows constructing dynamic, context-aware descriptions of transactions and messages as an alternative to using individual `intent` and `fields`. + +The `interpolatedIntent` makes transaction intents significantly shorter and easier to read by presenting all relevant information in a single, natural language sentence rather than as separate labeled fields. This is particularly valuable for: +- Reducing the cognitive load on users reviewing transactions +- Displaying concise summaries on resource-constrained devices +- Enabling clear descriptions of batch transactions (e.g., [EIP-5792](./eip-5792.md)), where multiple operations can be concatenated into a single readable sentence + +**Interpolation syntax** + +Values are interpolated using curly braces containing a path reference: `{path}`. The path MUST follow the [path reference rules](#path-references) and can reference: +- Structured data fields (e.g., `{to}`, `{params.amountIn}`) +- Container values (e.g., `{@.value}`, `{@.from}`) +- Metadata constants (e.g., `{$.metadata.constants.nativeAssetAddress}`) + +Interpolated intents MUST only refer to paths that are noted as always visible (i.e. `visible` key in the field formatter is `always` or not present). + +**Formatting behavior** + +When a wallet processes an `interpolatedIntent`: +1. The wallet MUST identify all interpolation expressions `{path}` in the string +2. For each expression, the wallet MUST resolve the path and locate the corresponding field format specification in the `fields` array +3. The wallet MUST apply the field's `format` and `params` to format the value +4. The wallet MUST replace the interpolation expression with the formatted value +5. If any interpolation fails (path not found, formatting error, etc.), the wallet MUST fall back to displaying the regular `intent` field -References to values in the container (here an EVM Tx container) -* `@.value` refers to the enclosing transaction native currency amount -* `@.to` refers to the enclosing transaction destination address (usually, a smart contract bound to this ERC-7730 through the `context` section) +**Escaping** + +To include literal curly braces in the intent text, escape them by doubling: `{{` for `{` and `}}` for `}`. + +**Examples** + +*Simple token transfer:* +```json +{ + "intent": "Send", + "interpolatedIntent": "Send {value} to {to}", + "fields": [ + {"path": "to", "format": "addressName"}, + {"path": "value", "format": "tokenAmount", "params": {"tokenPath": "@.to"}} + ] +} +``` +Displays as: **"Send 100 USDT to cyberdrk.eth"** + +*Simple encrypted token transfer:* +```json +{ + "intent": "Send", + "interpolatedIntent": "Send {value} to {to}", + "fields": [ + {"path": "to", "format": "addressName"}, + {"path": "value", "format": "tokenAmount", "params": {"tokenPath": "@.to"}, "encryption": { + "scheme": "fhevm", + "plaintextType": "uint64", + "fallbackLabel": "[Encrypted Amount]" + }} + ] +} +``` +Displays as: +- When decryption is available: **"Send 100 USDT to cyberdrk.eth"** +- When decryption is not available: **"Send [Encrypted Amount] to cyberdrk.eth"** + +*Swap with native currency:* +```json +{ + "intent": "Swap", + "interpolatedIntent": "Swap {amountIn} for at least {amountOutMinimum}", + "fields": [ + {"path": "amountIn", "format": "tokenAmount", "params": {"tokenPath": "tokenIn"}}, + {"path": "amountOutMinimum", "format": "tokenAmount", "params": {"tokenPath": "tokenOut"}} + ] +} +``` +Displays as: **"Swap 1000 USDC for at least 0.25 WETH"** + +*Using container values:* +```json +{ + "intent": "Wrap ETH", + "interpolatedIntent": "Wrap {@.value} ETH for WETH", + "fields": [ + {"path": "@.value", "format": "amount"} + ] +} +``` +Displays as: **"Wrap 0.5 ETH for WETH"** + +*Escaping literal braces:* +```json +{ + "interpolatedIntent": "Execute {{function}} with {amount} tokens" +} +``` +Displays as: **"Execute {function} with 100 tokens"** #### Organizing files @@ -245,38 +339,18 @@ Special care must be taken when merging [field format specifications](#field-for This file defines a generic ERC-20 interface for a single `approve` function: ```json { - "context": { - "contract": { - "abi": [ - { - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "type": "function" - } - ] - } - }, "display": { "formats": { - "approve(address _spender,uint256 _value)": { + "approve(address spender,uint256 value)": { "intent": "Approve", "fields": [ { - "path": "_spender", + "path": "spender", "label": "Spender", - "format": "addressOrName" + "format": "addressName" }, { - "path": "_value", + "path": "value", "label": "Amount", "format": "tokenAmount", "params": { @@ -285,8 +359,7 @@ This file defines a generic ERC-20 interface for a single `approve` function: "thresholdLabel": "Unlimited" } } - ], - "required": ["_spender", "_value"] + ] } } } @@ -298,7 +371,7 @@ The following file would include this generic interface and bind it to the speci ```json { "context": { - "$id": "Tether USD", + "$id": "Example Contract", "contract" : { "deployments": [ { @@ -309,28 +382,28 @@ The following file would include this generic interface and bind it to the speci } }, - "includes": "../ercs/calldata-ERC-20-tokens.json", + "includes": "./example-erc20.json", "metadata": { - "owner": "Tether", + "owner": "Example", + "contractName": "Example Token", "info": { - "legalName": "Tether Limited", - "url": "https://tether.to/", + "url": "https://example.io/", "deploymentDate": "2017-11-28T12:41:21Z" }, "token": { - "ticker": "USDT", - "name": "Tether USD", + "ticker": "STABLE", + "name": "Example Stablecoin", "decimals": 6 } }, "display": { "formats": { - "approve(address _spender,uint256 _value)": { + "approve(address spender,uint256 value)": { "fields": [ { - "path": "_value", + "path": "value", "params" : { "threshold": "0xFFFFFFFFFFFFFFFFFF" } @@ -341,7 +414,7 @@ The following file would include this generic interface and bind it to the speci } } ``` -Note that the keys under `context.contract` would be merged together to construct a full contract binding object. The field formatter `_value` parameter `threshold` is overridden with value `0xFFFFFFFFFFFFFFFFFF`. +Note that the keys under `context.contract` would be merged together to construct a full contract binding object. The field formatter `value` parameter `threshold` is overridden with value `0xFFFFFFFFFFFFFFFFFF`. ### `Context` section @@ -355,32 +428,19 @@ All context support an `$id` sub-key as an internal identifier (only relevant to The `contract` sub-key is used to introduce an EVM smart contract binding context, with the following constraints expressed as sub-keys of `contract`. -**`contract.abi`** - -Either an URL of the reference ABI (served in json representation), or the ABI itself as a json array. - -A wallet MUST verify that the ABI is applicable to the contract being called. - -All paths described in the ERC-7730 starting with the `#.` root node (typically used to describe a single parameter of the contract call) are using selectors names coming from the parameter names of the ABI referenced. - -The `contract.abi` key is mandatory for a smart contract ERC-7730 file. - **`contract.deployments`** -An array of deployments options. Wallets MUST verify that the target chain and contract address of the containing transaction MUST match one of these deployment options. +An array of deployments options. Wallets MUST verify that the target chain and contract address of the containing transaction either: + * Match one of these deployment options + * Is a *proxy* pointing to one of the deployment option (see [Proxy support](#proxy-support)) A deployment option is an object with: * `chainId`: an [EIP-155 identifier](./eip-155.md) of the chain the described contract is deployed on. * `address`: the address of the deployed contract on specified `chainId` chain. ---- -*The following constraints for contracts are considered draft* - -**`contract.addressMatcher`** - -An URL that can be used to check whether the current transaction `(chainId, contract address)` can be formatted using the ERC-7730 file. +**`contract.abi`** *(deprecated)* -A wallet MAY use this URL to check unknown addresses, default is to fail constraint on unknown addresses. +Legacy ABI attachment kept for backward compatibility. Authors SHOULD prefer `display.formats` and avoid relying on this key, as it will be removed in a future revision. **`contract.factory`** @@ -393,70 +453,25 @@ A factory is a json object with: To verify a factory constraints a wallet MUST check that: * The current transaction destination address is included in an event of signature `factory.deployEvent` * The emitter of the event is a factory contract deployed at an address matching one of the deployment option in `factory.deployments` ---- - -*Examples* - -[TBD] #### EIP-712 messages binding context (denoted 'messages') * The `eip712` sub-key is used to introduce an EIP-712 message type to bind to: -**`eip712.schemas`** - -The `schemas` key is either an URL pointing to the EIP-712 schemas (in json representation) or an array of either individual *EIP-712 json schema*, or URLs pointing to a single message schema. - -An *EIP-712 schema* consists in the subset of the EIP-712 message containing only the `types` and `primaryType` elements of the message, represented in json notation for this specification. +**`eip712.schemas`** *(deprecated)* -A wallet MUST verify that the message being signed matches the `types` and `primaryType` of the schema. +Legacy Schema attachment kept for backward compatibility. Authors SHOULD prefer `display.formats` and avoid relying on this key, as it will be removed in a future revision. -All paths in the ERC-7730 file relative to the message (root node `#.`) are using selectors taken from the types *names* in the schema. As such, the `schema` is a mandatory key for `eip712` contexts. - -*Example* - -In the sample EIP-712 message included in the specification [here](../assets/eip-712/Example.js), the schema for the message would be the following json. Note the inclusion in an array, since a single ERC-7730 can describe multiple messages. - -```json -{ - "context": { - "eip712": { - "schemas": [ - { - "types": { - "EIP712Domain": [ - { "name": "name", "type": "string" }, - { "name": "version", "type": "string" }, - { "name": "chainId", "type": "uint256" }, - { "name": "verifyingContract", "type": "address" } - ], - "Person": [ - { "name": "name", "type": "string" }, - { "name": "wallet", "type": "address" } - ], - "Mail": [ - { "name": "from", "type": "Person" }, - { "name": "to", "type": "Person" }, - { "name": "contents", "type": "string" } - ] - }, - "primaryType": "Mail" - } - ] - } - } -} -``` **`eip712.domain`** -The `domain` constraint is a json object with simple key-value pairs, describing a set of values that the *EIP-712 Domain* of the message MUST match. +The `domain` constraint is a json object describing a set of values that the *EIP-712 Domain* of the message MUST match. Values are typically simple key-value pairs, but MAY also be constant references or map references (see [`metadata.constants`](#metadata-section) and [`metadata.maps`](#metadata-section)). A wallet MUST verify that each key-value pair in this `domain` binding matches the values of the `domain` key-value pairs of the message. Note that the message can have more keys in its `domain` than those listed in the ERC-7730 file. An EIP-712 domain is a free-form list of keys, but those are very common to include: * `name`: the name of the message verifier * `version`: the version of the message -* `chainID`: an [EIP-155](./eip-155.md) identifier of the chain the message is bound to +* `chainId`: an [EIP-155](./eip-155.md) identifier of the chain the message is bound to * `verifyingContract`: the address the message is bound to Note that `chainId` and `verifyingContract` can also be bound to their values thanks to the `eip712.deployments` constraint, in a more flexible way (supporting multiple deployment values). @@ -467,7 +482,9 @@ An array of deployments options. When an `eip712.deployments` constraint is set, the wallet MUST verify that: * The message being displayed has both `domain.chainId` and `domain.verifyingContract` keys -* The `chainId` and `verifyingContract` values in the domain match ONE of the deployment option specified in `eip712.deployments` +* The `chainId` and `verifyingContract` values in the domain either + * Match ONE of the deployment option specified in `eip712.deployments` + * Is a *proxy* pointing to one of the deployment options (see [Proxy support](#proxy-support)) A deployment option is an object with: * `chainId`: an EIP-155 identifier of the chain the described contract is deployed on. @@ -487,16 +504,6 @@ When the exact construction of the EIP-712 domain is not known (for instance, wh { "context" : { "eip712": { - "schemas": [ - { - "types": { - "EIP712Domain": [], - "PermitDetails": [], - "PermitSingle": [] - }, - "primaryType": "PermitSingle" - } - ], "domain": { "name": "Permit2" }, @@ -532,10 +539,15 @@ A key containing a displayable name of the *owner* of the contract or of the ver Wallet MAY use this value to display the target of the interaction being reviewed. +**`metadata.contractName`** + +A key containing a displayable name of the contract or of the verifying contract for a message. + +Wallet MAY use this value to display the target of the interaction being reviewed. + **`metadata.info`** A key containing additional structured info about the *owner* of the contract: - * `legalName` is the legal owner entity name * `deploymentDate` is the date of deployment of the contract (or verifying contract) * `url` is the official URL of the owner @@ -547,7 +559,7 @@ The `token` key is only relevant for `contract` ERC-7730 files and only for cont It contains the ERC-20 metadata when the contract itself does not support the optional calls to retrieve it. It SHOULD NOT be present if the contract does support the `name()`, `symbol()` and `decimals()` smart contract calls. -The ERC-20 token metadata for the contract described is in the sub-keys `name`, `ticker` and `decimals` +The ERC-20 token metadata for the contract described is in the sub-keys `name`, `ticker` and `decimals` and contains the values that the correponding function calls would have returned. **`metadata.constants`** @@ -568,6 +580,62 @@ It is a list of key / value pairs, the key being used to reference this constant ``` This snippet introduces a constant `nativeAssetAddress` (an address typically used to represent the native network underlying currency, which is smart contract specific). This constant can be referenced using the path `$.metadata.constants.nativeAssetAddress` +**`metadata.maps`** + +Sometimes constants are dependant on some part of the context of the transaction, for instance a token address used by a contract might depend on the deployment chain. `maps` key allow an efficient representation of those types of context-dependent constants. + +Each key under the `maps` object is a *map* name. + +A map is a json object with two keys: +* The `keyType` key contains an non normative indication of the expected type of the map key passed when referencing the map. +* The `values` key contains a list of key / value pairs, each key being used to match the data pointed to in the reference `keyPath` key. The corresponding value is used as the context-dependent constant when referenced in the `display` section. + +Maps references can be used anywhere a parameter with constant value would be used. A map reference is a json object with two keys: +* The `map` key refers to the map to use to resolve the constant value, using a path starting with root node `$.` (i.e. `$.metadata.maps.MAP_NAME`). +* The `keyPath` key refers to the path to use to retrieve the map key to use for the resolution. + +A wallet MUST replace a path to a map using the value matching the `keyPath` parameter. In case no values matches the wallet MUST consider the 7730 file invalid for the transaction. + +An example can be found in [example-maps.json](../assets/eip-7730/example-maps.json) and [example-maps-pools.json](../assets/eip-7730/example-maps-pools.json). + +*Examples* + +```json +{ + "metadata": { + "maps": { + "underlyingToken": { + "keyPath": "@.chainId", + "values" : { + "1": "0xaabbccddeeff...", + "17000": "0x112233445566..." + } + } + } + }, + "display": { + "formats": { + "deposit(amount)" : { + "intent": "Deposit", + "fields": [ + { + "path": "#.amount", + "label": "Deposit Amount", + "format": "tokenAmount", + "params": { + "token": "$.metadata.maps.underlyingToken" + } + } + ] + } + } + } +} +``` + +This example shows a deposit function that takes as only input the amount of a token hardcoded in the contract. The token address deposited changes based on the chain the contract is deployed to. +The `underlyingToken` map allows defining the hardcoded values of the underlying token based on the chainId of the transaction context (as specified in `"keyPath": "@.chainId"`). + **`metadata.enums`** The `enums` key contains displayable values for parameters that are enumerations (in a loose sense including parameters taking fixed number of well known values). These `enums` are used to replace specific parameter values with a human-readable one. @@ -576,13 +644,6 @@ Each key of the `enums` object is an *enumeration* name. Enumeration names can b An enum is a json object with a flat list of key / value pairs, each key being the enumeration *value* to replace, and the value being the display string to use instead of the enumeration value. ---- -*The following is considered a draft* - -Alternatively, rather than a json object with displayable values, an enum can be a simple string containing an URL. That URL MUST return the json object with the enumeration key-values. - -A wallet MAY use this URL to resolve more dynamic enumeration values under the control of the owner of the URL. - *Examples* ```json @@ -592,23 +653,21 @@ A wallet MAY use this URL to resolve more dynamic enumeration values under the c "interestRateMode": { "1": "stable", "2": "variable" - }, - "vaultIDs": "https://example.com/vaultIDs" + } } } } ``` This snippet introduces an enumeration describing the displayable values of an integer parameter used to represent multiple modes of interest rates. It can be referenced using the path `$.metadata.enums.interestRateMode`. -It also shows how to describe a more dynamic enumeration, like a list of possible vaultIDs as integer values, retrievable through an URL. This dynamic enumeration can be referenced using `$.metadata.enums.vaultIDs`. - ```json { "display": { "formats": { - "repay(address asset, uint256 amount, uint256 interestRateMode)": { + "repay(address asset,uint256 amount,uint256 interestRateMode)": { "$id": "repay", "intent": "Repay loan", + "interpolatedIntent": "Repay {amount} of {interestRateMode} rate loan", "fields": [ { "path": "amount", @@ -622,8 +681,7 @@ It also shows how to describe a more dynamic enumeration, like a list of possibl "label": "Interest rate mode", "params": { "$ref": "$.metadata.enums.interestRateMode" } } - ], - "required": ["amount", "interestRateMode"] + ] } } } @@ -662,20 +720,104 @@ Definitions don't usually include the `path` or `value` key of a [*field format ``` This snippet defines a common formatter for an amount to send that can be used by a simple reference to the path `$.display.definitions.sendAmount`. -**`metadata.formats`** +**`display.formats`** + +The `formats` key is an object containing the actual information used to format the structured data. It is a json object in which each sub-key is a specific function call (for contracts) or a specific message type (for EIP-712) being described. The values are each a [*structured data format specification*](#structured-data-format-specification). + + +**Contract keys** + +For contract calldata, this specification only covers functions. Each key MUST be a human-readable ABI function fragment that includes parameter names, for example `transfer(address to,uint256 value)` or `submitOrder((address token,uint256 amount) order,bytes32 salt)`. + +- Keys MUST use canonical Solidity type names: `uint256`, `bytes32`, `address`, `bool`, tuple syntax `(…)`, dynamic arrays `type[]`, and fixed arrays `type[N]`. Aliases (e.g., `uint`) are NOT permitted, commas MUST NOT be followed by spaces, and there MUST be exactly one space between each type and its parameter name. +- Parameter names in the fragment MUST match the names used throughout the formatting specification; wallets derive all display paths from these names. +- Overloaded functions are distinguished solely by their type signatures. Parameter names do not affect selector matching. + +**Selector matching (contracts)** + +Wallets MUST match calldata to a `display.formats` entry using the following procedure: +1. Parse the key and drop parameter names to obtain the canonical type-only signature (e.g., `transfer(address,uint256)`). +2. Compute `keccak256()[:4]`. +3. Compare the resulting selector to the transaction calldata selector; a match selects the corresponding format specification. + +If multiple keys share the same type-only signature, wallets MUST treat this as an invalid descriptor. + +**Decoding and parameter names** + +Once a format entry is selected, wallets MUST decode calldata arguments using the canonical type vector derived from the key. Parameter names, `fields[].path` entries, and `{placeholders}` in `interpolatedIntent` MUST all use the names from the key (e.g., `value`, `order.amount`, `recipients[0]`). + +Paths are relative to the matched function parameters unless prefixed by another root node; for example, `to`, `order.price`, and `recipients[0]` point to decoded arguments, while `@.value` references the transaction container. + +Placeholders in strings MUST use `{}` with the same path semantics. + +**Unknown selectors** + +If no key matches the calldata selector, wallets SHOULD display a safe fallback (for example, "Unknown function" alongside raw arguments) and MUST NOT attempt to apply any unrelated format specifications. + +*Minimal contract examples* + +```json +{ + "display": { + "formats": { + "transfer(address to,uint256 value)": { + "intent": "Send", + "interpolatedIntent": "Send {value} to {to}", + "fields": [ + { "path": "value", "label": "Amount", "format": "tokenAmount" }, + { "path": "to", "label": "To", "format": "addressName" } + ] + } + } + } +} +``` + +```json +{ + "display": { + "formats": { + "submitOrder((address token,uint256 amount,uint256 price) order,bytes32 salt)": { + "intent": "Place order", + "interpolatedIntent": "Buy {order.amount} @ {order.price}", + "fields": [ + { "path": "order.token", "label": "Token", "format": "addressName" }, + { "path": "order.amount", "label": "Amount", "format": "number" }, + { "path": "order.price", "label": "Price", "format": "number" }, + { "path": "salt", "label": "Salt", "format": "bytes32" } + ] + } + } + } +} +``` + +```json +{ + "display": { + "formats": { + "airdrop(address[] recipients,uint256[3] values)": { + "intent": "Airdrop", + "interpolatedIntent": "Airdrop to {recipients[0]} (+{recipients.length} total)", + "fields": [ + { "path": "recipients[0]", "label": "First recipient", "format": "addressName" }, + { "path": "values[0]", "label": "Tier 1", "format": "tokenAmount" } + ] + } + } + } +} +``` -The `formats` key is an object containing the actual information used to format the structured data. It is a json object in which each sub-key is a specific function call (for contracts) or a specific message type (for EIP-712) being described. The values are each a *structured data format specification*. +**EIP-712 keys** -For contracts, the key names MUST be one of those three forms: -* A 4-bytes selector of the function being described, in hex string notation: the corresponding function MUST be in the abi specified in `context.abi`. -* The function signature used to compute the selector, i.e. without parameter names and without spaces (refer to Solidity documentation): the corresponding function MUST be in the abi specified in `context.abi`. -* A full solidity signature, including parameter names: the corresponding function MUST be in the abi specified in `context.abi` and the parameters names MUST match those in the ABI. +For EIP-712, the key names MUST be the string returned by the `encodeType` function defined in EIP-712 specification applied to the primary type of the message. -> Why is the ABI still necessary? -> -> The full signature does not contain information about the types of complex parameters that might be used in the function call, only their names, while the ABI does carry this information. This is why the ABI is the reference and the function signature MUST match it. The full form is allowed in order to simplify writing and reading the ERC 7730 file. +During the EIP-712 signature process, wallets will compute the type hash from the message. This type hash MUST match the hash computed from the `display.formats` key in use: `keccak256(encodeType(typeOf(s))) == keccak256(TYPE_KEY)` -For EIP-712, the key names MUST be one of the primary type names included in `context.eip712.schemas`. +*Example* + +In the sample EIP-712 message included in the specification [here](../assets/eip-712/Example.js), the key used to describe the message would be the string `Mail(Person from,Person to,string contents)Person(string name,address wallet)`. #### Structured data format specification @@ -689,9 +831,10 @@ This key is purely internal and used to specify a human-readable identifier for Use to specify the *intent* of the function call or message signature in a user-friendly way. -An intent can take two forms: -* A simple string with human-readable content -* A json object with a flat list of string key-value pairs, representing more complex intents. Both keys and values should be human-readable and user-friendly. +An intent can take three forms: +* A simple string with human-readable content, or a constant reference / map reference resolving to a string +* A json object with a flat list of key-value pairs, representing more complex intents. Both keys and values should be human-readable and user-friendly. Values MAY be constant references or map references. +* A map reference resolving to a string Wallets SHOULD use this `intent` value to display a clear intent when reviewing the structured data before signature. When displaying a complex json intent, it is expected that keys represent labels, and values should be displayed after their label. @@ -711,19 +854,43 @@ Wallets SHOULD use this `intent` value to display a clear intent when reviewing ``` This snippet defines an intent for a withdrawal function on a contract, with an expectation that the intent would be displayed in a structured way on the wallet screen. +**`interpolatedIntent`** + +A string containing an intent description with embedded field values using [interpolation syntax](#value-interpolation). + +Wallets MUST display either `interpolatedIntent` strings or screens generated from `intent` and individual `fields`. + +Wallets MAY display both fields, with `interpolatedIntent` as the primary description. + +Interpolated paths MUST reference fields that have corresponding format specifications in the `fields` array. The formatting applied during interpolation MUST match the formatting that would be applied if the field were displayed separately. + +*Example with complex intent:* +```json +{ + "intent": { + "Action": "Approve", + "Type": "Batch" + }, + "interpolatedIntent": "Approve {spender} to spend up to {amount} on your behalf until {deadline}", + "fields": [ + {"path": "spender", "label": "Spender", "format": "addressName"}, + {"path": "amount", "label": "Amount", "format": "tokenAmount"}, + {"path": "deadline", "label": "Deadline", "format": "date"} + ] +} +``` + **`fields`** The `fields` key defines formatting information for individual fields of the structured data (function or message). -each key name is a [path](#path-references) in the structured data, + `fields` is an array of elements, each element being either: * A single [*field format specification*](#field-format-specification) * A reference to a format specification in the `definitions` section: by declaring an object with two keys, a `path` key with the [path](#path-references) to the field being formatted, and a `$ref` key with a path to the internal definition. * A reference object can override a field format specification `params` by including its own `params` sub-key, whose values will take precedence over the common definition - * A recursive [*structured data format specification*](#structured-data-format-specification), by declaring an object with two keys, a `path` key with the [path](#path-references) to the field(s) being formatted, and a `fields` with the recursive list of sub-fields. + * A group of fields, defined in [*Group format specification*](#group-format-specification) -Recursive references work by concatenating the paths of the parents *structured data format specification*, all the way to the leaf, to build a full reference path to the field being described. The leaf should be either a *field format specification*, or a reference to a *field format specification* in the `definitions` section. - -This recursivity allows structuring the ERC-7730 file itself, but is NOT RECOMMENDED. It is expected that resource limited wallets will only support very limited recursivity in the ERC-7730 file itself, so the initial intent of the spec is to "flatten" the list of fields to display using the *path* mechanics. +*Grouping* fields allows for both control of the way wallets should order the display of fields and *recursivity* of fields definitions, which can be a more readable form for the 7730 file itself. *Examples* @@ -740,7 +907,7 @@ contract MyContract { } // Function declaration - function myFunction(address _address, uint256 _amount, MyParamType memory _param) public { + function myFunction(address account, uint256 amount, MyParamType memory param) public { // Function logic here } } @@ -751,20 +918,20 @@ The following ERC-7730 shows examples for the three kinds of `fields` options { "display": { "formats": { - "myFunction(address _address,uint256 _amount,MyParamType _param)" : { + "myFunction(address account,uint256 amount,(string name,uint256 value) param)" : { "fields": [ { - "path": "_address", + "path": "account", "$ref": "$.display.definitions.sendTo", "params": { "type": "eoa" } }, { - "path": "_amount", + "path": "amount", "label": "Number of items", "format": "raw" }, { - "path": "_param", + "path": "param", "fields": [ { "path": "name", "$ref": "$.display.definitions.itemName" }, { "path": "value", "$ref": "$.display.definitions.itemReference" } @@ -776,19 +943,19 @@ The following ERC-7730 shows examples for the three kinds of `fields` options } } ``` -* The `_address` field is an example of an internal reference (reference not included in the example), overriding the reference `type` parameter with another value. -* The `_amount` field shows an example of a simple formatter, displaying an int in its natural representation with a label. -* The `_param` field shows an example of defining formatters with a recursive structure, ending up defining two embedded formatters for paths `#._param.name` and `#._param.value` +* The `account` field is an example of an internal reference (reference not included in the example), overriding the reference `type` parameter with another value. +* The `amount` field shows an example of a simple formatter, displaying an int in its natural representation with a label. +* The `param` field shows an example of defining formatters with a recursive structure, ending up defining two embedded formatters for paths `#.param.name` and `#.param.value` Note that the recursive definition is equivalent to the following definition, which is the preferred form: ```json { "display": { "formats": { - "myFunction(address _address,uint256 _amount,MyParamType _param)" : { + "myFunction(address account,uint256 amount,MyParamType param)" : { "fields": [ - { "path":"_param.name", "$ref": "$.display.definitions.itemName" }, - { "path":"_param.value", "$ref": "$.display.definitions.itemReference" } + { "path":"param.name", "$ref": "$.display.definitions.itemName" }, + { "path":"param.value", "$ref": "$.display.definitions.itemReference" } ] } } @@ -796,37 +963,113 @@ Note that the recursive definition is equivalent to the following definition, wh } ``` -**`required`** -Required fields, as an array of *paths* referring to specific fields. +#### Field format specification + +A *field format specification* is a json object defining how to format a single field of the structured data. -Wallets SHOULD display at least all the fields referred by the `required` key. +**`path` / `value`** -**`excluded`** +The path is the absolute or relative location of the field in the structured data, following the rules in the [path section](#path-references). Instead of `path`, a literal `value` may be provided to display a constant, a constant reference, or a map reference without looking up a field. -Intentionally excluded fields, as an array of *paths* referring to specific fields. +**`label`** -A field that has no formatter and is not declared in this list MAY be considered as an error by the wallet when interpreting the descriptor. +A displayable string shown before the formatted field value. May also be a constant reference or a map reference resolving to a string. -**`screens`** +**`format`** -Wallet specific grouping information. The structure of the `screens` sub-keys is wallet maker specific and referenced in the [Wallets](#wallets) section. +Indicates how the field value must be transformed for display. Supported format identifiers are listed in the [Reference](#field-formats) section. -The current ERC-7730 specification does not contain layout or ordering information, since capabilities for controlling those are highly wallet dependent. +**`params`** -It is expected that wallets supporting advanced re-ordering and layout features would enable those in the `screens` object. +Optional object containing formatter-specific parameters. Available parameters are described alongside each format in the [Reference](#field-formats) section. -Wallets SHOULD NOT override formatting information in the `fields` key in the wallet specific `screens` key. +*Array references in parameters* -#### Field format specification +When a formatter applies to an array, the parameters CAN also reference a path to an array. In that case, wallets should format each element of the array using the parameter value at the same index as the current element being formatted. Parameter arrays should be the same length as the formatted array else the wallet SHOULD raise an error. -A *field format specification* is a json object defining how to format a single field of the structured data. +See [example-array-iteration.json](../assets/eip-7730/example-array-iteration.json) for examples on array iteration. + +**`$id`** + +Optional internal identifier to help authors distinguish or reference the field definition; not intended for end-user display. + +**`visible`** + +Optional rule to apply before displaying a field. Rules allow defining under which conditions a field should be displayed. If not specified, the field is assumed to be always visible (ie, default value is `"visible"="always"`). Current supported rules are: + +* A simple string with values among `[never,always,optional]`, with the following meaning + * `never"`: Never display this field to users (this field should be *excluded* from the UI). + * `always`: Always display this field to users (this field is *required*). + * `optional`: Wallets MAY display this field if possible or sensible. +* An object with keys representing complex rules, with the following complex rules supported: + * `ifNotIn` key with an array of values to make the field visible only when the field value does NOT match any of the specified values + * `mustMatch` key with an array of values to make the field always hidden AND check its value against the list. Wallet should raise an error if the value does not match the list. + +**`separator`** + +Optional separator string to display before each element of an array. Can only be used be used for formatters that are applied to a path pointing to an array. Each element of the array is formatted using the provided formatter, and the `separator` value is displayed before each element. `separator` is a string using the interpolated syntax with one specific replacement `{index}` that gets replaced with the index of current element being displayed. -* The `path` is the absolute or relative path to the field location in the structured data, as described in the [path section](#path-references). A literal `value` can be used instead to display a constant rather than looking up a field in the structured data. -* The `label` is a displayable string that should be shown before displaying this field -* The `format` key indicates how the value of the field should be formatted before being displayed. The list of supported formats are in the [Reference](#field-formats) section -* Each field format might have parameters in a `params` sub-key. Available parameters are described in the [Reference](#field-formats) section -* Each field definition can have an optional internal `$id` used to identify easily which field is described +**`encryption`** + +Optional object indicating the field value is encrypted. When present, it provides all the relevant information on how to handle the decryption process, in particular the encryption scheme used to produce the encrypted value. + +An `encryption` key is a json object with: +* A `scheme` key, providing the wallet an hint on encryption scheme used. Refer to [encryption schemes](#encryption-schemes) for reference values. +* A `plaintextType` key providing the solidity type of the decrypted field. The wallet SHOULD verify that the formatter is applicable to the `plaintextType`. +* An optional `fallbackLabel` to display when the wallet cannot decrypt the field. + +Fields MAY declare an `encryption` object to indicate that the on-chain value is an encrypted amount (or a pointer to an encrypted amount), typically a `bytes32`. When decryption is available, wallets SHOULD decrypt first and then apply the field `format` to the plaintext. When decryption is not available, wallets SHOULD display a clear encrypted placeholder (optionally using `fallbackLabel`) and are RECOMMENDED to also show the raw encrypted value (or its pointer), either fully or partially. + +Because encrypted values do not reveal their underlying type on-chain, authors SHOULD provide `plaintextType` so wallets can correctly format decrypted values. + +*Example* + +```json +{ + "path": "encryptedAmount", + "label": "Amount", + "format": "tokenAmount", + "params": { "tokenPath": "@.to" }, + "encryption": { + "scheme": "fhevm", + "plaintextType": "uint64", + "fallbackLabel": "[Encrypted Amount]" + } +} +``` + +#### Group format specification + +A *Group format specification* is a json object defining how to format a group of fields of the structured data. + +**`path`** + +The absolute or relative path to the field location in the structured data, as described in the [path section](#path-references). + +`path` is optional, in which case the group refer to the path of the embedding `fields` definition. + +**`label`** + +Optional displayable string that should be shown before displaying the fields described in this group. + +**`iteration`** + +Key controlling how arrays in the group should be iterated over when formatting the group. It can only be applied to groups containing arrays. + +It supports two modes `sequential` and `bundled`: + +* In `sequential` mode, arrays are displayed fully one after the other - array_0[0] array_0[1] .. array_0[N] array_1[0] array_1[1] .. array_1[M] +* In `bundled` mode, elements of the arrays gets bundled together - array_0[0] array_1[0] array_0[1] array_1[1] .. array_0[N] array_1[N] + * In this mode, the arrays of the group MUST be the same size else the wallet SHOULD return an error + +Defining groups of fields allows for controlling the *order* in which wallets should display fields. Wallets SHOULD display fields in the order in which they appear in the group defintion. For this grouping purpose, the top level [Structured data format specification](#structured-data-format-specification) is also considered a group. + +Recursive path references work by concatenating the paths of the parents *structured data format specification*, all the way to the leaf, to build a full reference path to the field being described. The leaf should be either a *field format specification*, or a reference to a *field format specification* in the `definitions` section. + +This recursivity allows structuring the ERC-7730 file itself, but is NOT RECOMMENDED. It is expected that resource limited wallets will only support very limited recursivity in the ERC-7730 file itself, so the initial intent of the spec is to "flatten" the list of fields to display using the *path* mechanics. + +See [example-array-iteration.json](../assets/eip-7730/example-array-iteration.json) for examples on grouping and group iteration control. #### Slices in paths @@ -842,7 +1085,7 @@ A slice on an array type means that the associated [field format specification]( { "display": { "formats": { - "0x09b81346": { + "exactOutput((bytes path, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum) params)": { "fields": [ { "path": "params.amountInMaximum", @@ -864,7 +1107,7 @@ The `tokenPath` parameter uses a slice on a `bytes` value, indicating only the f { "display": { "formats": { - "0xb2f1e6db": { + "buyOnMySwap(address router,uint256 amountIn,uint256 amountOutMin,address tokenPathStart,uint256[] pools)": { "fields": [ { "path": "pools.[-1]", @@ -883,7 +1126,9 @@ This examples uses an array slice to indicate that only the last element of the { "display": { "formats": { - "PermitBatch": { + "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline) PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)": { + "intent": "Approve token spending", + "interpolatedIntent": "Approve {spender} to spend multiple tokens until {sigDeadline}", "fields": [ { "path": "details.[]", @@ -905,6 +1150,19 @@ This examples uses an array slice to indicate that only the last element of the } } ] + }, + { + "path": "spender", + "label": "Spender", + "format": "addressName" + }, + { + "path": "sigDeadline", + "label": "Signature Deadline", + "format": "date", + "params": { + "encoding": "timestamp" + } } ] } @@ -916,17 +1174,16 @@ This example uses a full array selector `details.[]` to apply the list of the un #### Embedded Calldata -Embedded calldata are used when a parameter of a smart contract contains the calldata of another function call to a smart contract. -This is common in contract interactions where one function call triggers another function call as part of its execution. -For example, the `permitAndCall` function that would verifie a permit and then call another function within the same contract using the provided calldata. - -Here is an example of how to format embedded calldata in the descriptor JSON: +Embedded calldata is used when a parameter of a smart contract function contains the calldata for another function call to a smart contract. +This pattern is common in contract interactions where one function call triggers another function call as part of its execution. +For example, the `permitAndCall` function verifies a permit and then executes another function within the same contract using the provided embedded calldata. +Here is an example of how to format embedded calldata:" ```json { "display": { "formats": { - "permitAndCall(bytes permit, bytes action)": { + "permitAndCall(bytes permit,bytes action)": { "intent": "Execute with permit", "fields": [ { @@ -946,21 +1203,19 @@ Here is an example of how to format embedded calldata in the descriptor JSON: } ``` -In this example, the `permitAndCall` function takes two parameters: `permit` and `action`. -The `action` parameter contains the calldata for another function call. -The format field is set to calldata, which tells the wallet to interpret the `action` parameter as embedded calldata. -The `calleePath` parameter specifies the path to the address of the contract being called, and the optional selector parameter can be used to specify the function selector if it is not the first 4 bytes of the calldata. -When displaying this transaction, the wallet will attempt to find an ERC-7730 descriptor for the embedded calldata using the callee address and the selector. - -Sometimes, embedded calldata may require a transaction value and/or spender to be correctly clear signed, as the underlying function might spend a value passed from its parent transaction. -This can occur when the embedded calldata's associated function performs operations that involve the native currency value from the parent transaction, such as swaps or staking. -In such cases, `amountPath` and/or `spenderPath` parameters can be used to specify those values. +In this example, the `permitAndCall` function accepts two parameters: `permit` and `action`. +The `action` parameter contains the calldata for a subsequent function call. +The `format` field is set to `calldata`, instructing the wallet to interpret the action parameter as embedded calldata. +The `calleePath` parameter defines the path to the address of the target contract, while the optional `selector` parameter specifies the function selector if it is not the first 4 bytes of the calldata. +When displaying the transaction, the wallet will attempt to resolve an ERC-7730 descriptor for the embedded calldata using the callee address and the selector. +In some cases, embedded calldata may require a transaction value and/or a spender to be properly clear-signed, as the underlying function might rely on a value passed from the parent transaction (e.g., for swaps or staking). +To handle this, the `amountPath` and/or `spenderPath` parameters can be used to specify these values explicitly. ```json { "display": { "formats": { - "permitAndCall(bytes permit, address target, bytes action)": { + "permitAndCall(bytes permit,address target,bytes action)": { "intent": "Execute with permit", "fields": [ { @@ -990,19 +1245,21 @@ This section describes all container structure supported by this specification a #### EVM Transaction container -| Value reference | Value Description | Examples | -|-----------------|---------------------------------------------------------------------------------------------|----------| -| @.from | The address of the sender of the transaction | | -| @.value | The native currency value of the transaction | | -| @.to | The destination address of the containing transaction, ie the target smart contract address | | +| Value reference | Value Description | +|-----------------|-------------------| +| @.from | The address of the sender of the transaction | +| @.value | The native currency value of the transaction | +| @.to | The destination address of the containing transaction, ie the target smart contract address | +| @.chainId | The chainId of the transaction | #### EIP-712 container -| Value reference | Value Description | Examples | -|-----------------|-----------------------------------------------------------------------------------------------------------------------------------|----------| -| @.from | The address of the signer of the message | | -| @.value | EIP-712 have no underlying currency value transferred, so a wallet MAY interpret it as 0 | | -| @.to | The verifying contract address, when known. If not known a wallet SHOULD reject using the ERC-7730 file to clear sign the message | | +| Value reference | Value Description | +|-----------------|-------------------| +| @.from | The address of the signer of the message | +| @.value | EIP-712 have no underlying currency value transferred, so a wallet MAY interpret it as 0 | +| @.to | The verifying contract address, when known. If not known a wallet SHOULD reject using the ERC-7730 file to clear sign the message | +| @.chainId | The chainId of the verifying contract, when known. If not known a wallet SHOULD reject using the ERC-7730 file to clear sign the message. | ### Field formats @@ -1011,38 +1268,35 @@ In the following references, the format title is the value to use under the `for #### Integer formats -Formats usable for uint/int solidity types. +Formats applicable to `uint`/`int` solidity types. ---- | **`raw`** | | |---------------|-----------------------------------------------------------------------| | *Description* | Display the integer as a raw int in natural, localized representation | | *Parameters* | None | | *Examples* | Value 1000 displayed as `1000` | ---- | **`amount`** | | |---------------|------------------------------------------------------------------------------| | *Description* | Display as an amount in native currency, using best ticker / magnitude match | | *Parameters* | None | | *Examples* | Value 0x2c1c986f1c48000 is displayed as `0.19866144 ETH` | ---- -| **`tokenAmount`** | | -|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| *Description* | Convert value using token decimals, and append token ticker name. If value is above optional `threshold`, display instead `message` with ticker. | -| *Parameters* | --- | -| `tokenPath` or `token` | path reference, or constant value for the address of the token contract. Used to associate correct ticker. If ticker is not found or `tokenPath`/`token` is not set, the wallet SHOULD display the raw value instead with an "Unknown token" warning | -| `nativeCurrencyAddress` | Either a string or an array of strings. If the address pointed to by `tokenPath` is equal to one of the addresses in `nativeCurrencyAddress`, the tokenAmount is interpreted as expressed in native currency | -| `threshold` | integer value, above which value is displayed as a special message. Optional | -| `message` | message to display above threshold. Optional, defaults to "Unlimited" | -| *Examples* | --- | -| `1 DAI` | Field value = 1000000
`tokenPath` =0x6B17...1d0F (DAI, 6 decimals) | -| `Unlimited DAI` | Field value = 0xFFFFFFFF
`token` =0x6B17...1d0F (DAI, 6 decimals)
`threshold` "0xFFFFFFFF" | -| `Max DAI` | Field value = 0xFFFFFFFF
`tokenPath` =0x6B17...1d0F (DAI, 6 decimals)
`threshold` "0xFFFFFFFF"
`message` = "Max" | -| `0.002 ETH` | Field value = 2000000000000000
`tokenPath` = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
`nativeCurrencyAddress` = ["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"] | +| **`tokenAmount`** | | +|----------------------------|--------------| +| *Description* | Convert value using token decimals, and append token ticker name. If value is above optional `threshold`, display instead `message` with ticker. | +| *Parameters* | --- | +| `tokenPath` or `token` | Path reference, or constant value for the address of the token contract. Used to associate correct ticker. If ticker is not found or `tokenPath`/`token` is not set, the wallet SHOULD display the raw value instead with an "Unknown token" warning | +| `nativeCurrencyAddress` | Either a string or an array of strings. If the address pointed to by `tokenPath` is equal to one of the addresses in `nativeCurrencyAddress`, the tokenAmount is interpreted as expressed in native currency | +| `threshold` | integer value, above which value is displayed as a special message. Optional | +| `message` | message to display above threshold. Optional, defaults to "Unlimited" | +| `chainIdPath` or `chainId` | Optional. The chain on which the token is deployed (constant or path). When present, the wallet SHOULD resolve token metadata (ticker, decimals) for this chain. Useful for cross-chain swap clear signing where the same token address may refer to different chains. At most one of `chainId` and `chainIdPath` may be set. | +| *Examples* | --- | +| `1 DAI` | Field value = 1000000
`tokenPath` =0x6B17...1d0F (DAI, 6 decimals) | +| `Unlimited DAI` | Field value = 0xFFFFFFFF
`token` =0x6B17...1d0F (DAI, 6 decimals)
`threshold` "0xFFFFFFFF" | +| `Max DAI` | Field value = 0xFFFFFFFF
`tokenPath` =0x6B17...1d0F (DAI, 6 decimals)
`threshold` "0xFFFFFFFF"
`message` = "Max" | +| `0.002 ETH` | Field value = 2000000000000000
`tokenPath` = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
`nativeCurrencyAddress` = ["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"] | ---- | **`nftName`** | | |------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| | *Description* | Display value as a specific NFT in a collection, if found by wallet, or fallback to a raw int token ID if not | @@ -1051,7 +1305,6 @@ Formats usable for uint/int solidity types. | *Examples* | --- | | `Collection Name: BoredApeYachtClub`
`Token ID: 1036` | Field Value = 1036
`collectionPath` = ""0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D | ---- | **`date`** | | |-----------------------------|-------------------------------------------------------------------------------------------| | *Description* | Display int as a date, using specified encoding. Date display RECOMMENDED use of RFC 3339 | @@ -1062,7 +1315,6 @@ Formats usable for uint/int solidity types. | `2024-02-29T08:27:12` | Field Value = 1709191632
`encoding` = "timestamp" | | `2024-02-29T09:00:35` | Field Value = 19332140
`encoding` = "blockheight" | ---- | **`duration`** | | |----------------|-----------------------------------------------------------------------------------------| | *Description* | Display int as a duration interpreted in seconds and represented as a duration HH:MM:ss | @@ -1070,20 +1322,18 @@ Formats usable for uint/int solidity types. | *Examples* | --- | | `02:17:30` | Field Value = 8250 | ---- -| **`unit`** | | -|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **`unit`** | | +|---------------|-------------------| | *Description* | Value is converted to a float using `decimals` (`value / 10^decimals`) and displayed appending the corresponding unit. If `prefix` is true, the value is further converted to scientific representation, minimizing the significand and converting the exponent to an SI prefix added in front of the unit symbol | -| *Parameters* | --- | -| `base` | The symbol of the base unit, an SI unit or other acceptable symbols like "%", "bps" | -| `decimals` | Number of decimals in integer representation, defaults to 0 | -| `prefix` | A boolean indicating whether an SI prefix should be appended, defaults to `False` | -| *Examples* | --- | -| `10h` | Field Value = 10
`base` = "h" | -| `1.5d` | Field Value = 15
`base` = "d"
`decimals` = 1 | -| `36ks` | Field Value = 36000
`base` = "s"
`prefix` = True | +| *Parameters* | --- | +| `base` | The symbol of the base unit, an SI unit or other acceptable symbols like "%", "bps" | +| `decimals` | Number of decimals in integer representation, defaults to 0 | +| `prefix` | A boolean indicating whether an SI prefix should be appended, defaults to `False` | +| *Examples* | --- | +| `10h` | Field Value = 10
`base` = "h" | +| `1.5d` | Field Value = 15
`base` = "d"
`decimals` = 1 | +| `36ks` | Field Value = 36000
`base` = "s"
`prefix` = True | ---- | **`enum`** | | |---------------|--------------------------------------------------------------------------------------------| | *Description* | Value is converted using referenced constant enumeration values | @@ -1091,11 +1341,18 @@ Formats usable for uint/int solidity types. | `$ref` | An internal path (starting with root node `$.`) to an enumerations in `metadata.constants` | | *Examples* | | +| **`chainId`** | | +|--------------------|--------------------------------------------------------------------------------------------| +| *Description* | Value is converted to a Blockchain name using EIP-155 reference values | +| *Parameters* | None | +| *Examples* | | +| `Ethereum Mainnet` | Field Value = 1 | + + #### String formats -Formats usable for strings. +Formats applicable to for `string` solidity type. ---- | **`raw`** | | |---------------|-----------------------------------------------| | *Description* | Display as an UTF-8 encoded string | @@ -1105,9 +1362,8 @@ Formats usable for strings. #### Bytes formats -Formats usable for bytes +Formats applicable to `bytes` solidity type. ---- | **`raw`** | | |---------------|------------------------------------------------| | *Description* | Display byte array as an hex-encoded string | @@ -1115,23 +1371,21 @@ Formats usable for bytes | *Examples* | --- | | `123456789A` | Field Value = Value ['12','34','56','78','9a'] | ---- -| **`calldata`** | | -|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| *Description* | Data contains a call to another smart contract, or to another function of the same smart contract. To look for relevant ERC-7730 files matching this embedded calldata, use `callee` parameter and `selector`. If an ERC-7730 is not found or if embedded calldata are not supported by the wallet, it MAY display a hash of the embedded calldata instead, with target `calleePath` resolved to a trusted name if possible. | -| *Parameters* | --- | -| `calleePath` or `callee` | A path reference or constant value for the contract being called. | -| `selectorPath` or `selector` | Optional selector, if not present the first 4 bytes of the calldata are interpreted as the selector. | -| `chainIdPath` or `chainId` | Optional chain ID if different from current contract. | -| `amountPath` or `amount` | Optional associated amount in native currency, if the calldata can be associated with a container value. If set, the relevant ERC-7730 file matching this embedded calldata can refers this value using `@.value` container path.| -| `spenderPath` or `spender` | Optional associated spender, if the calldata can be associated with a container value. If set, the relevant ERC-7730 file matching this embedded calldata can refers this value using `@.from` container path.| -| *Examples* | | +| **`calldata`** | | +|-----------------------------|------------------------| +| *Description* | Contains a call to another smart contract or to another function within the same contract. To resolve an ERC-7730 descriptor for this embedded calldata, use the `callee` and `selector` parameters. If no matching ERC-7730 descriptor is found or if the wallet does not support embedded calldata, it MAY display a hash of the embedded calldata instead, with target `calleePath` resolved to a trusted name if possible. | +| *Parameters* | --- | +| `calleePath` or `callee` | A path reference or a constant value specifying the address of the contract being called. | +| `selectorPath` or `selector`| Optional. If omitted, the first 4 bytes of the calldata are used as the selector. | +| `chainIdPath` or `chainId` | Optional. Specifies the chain ID if it differs from the current contract’s chain. | +| `amountPath` or `amount` | Optional. Specifies the native currency amount associated with the calldata, if applicable. If provided, the ERC-7730 descriptor for this embedded calldata MAY reference this value using the `@.value` container path. | +| `spenderPath` or `spender` | Optional. Specifies the spender address associated with the calldata, if applicable. If provided, the ERC-7730 descriptor for this embedded calldata MAY reference this value using the `@.from` container path. | +| *Examples* | --- | #### Address -Formats usable for address +Formats applicable to `address` solidity type. ---- | **`raw`** | | |-----------------|----------------------------------------------------------------------------------------------| | *Description* | Display address as an [EIP-55](./eip-55.md) formatted string. Truncation is device dependent | @@ -1139,18 +1393,41 @@ Formats usable for address | *Examples* | --- | | `0x5aAe...eAed` | Field Value 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed | +| **`addressName`** | | +|-------------------------|--------------------| +| *Description* | Display address as a trusted name if a trusted source exists, an EIP-55 formatted address otherwise. See [next section](#address-types-and-sources) for a reference of trusted sources | +| *Parameters* | --- | +| `types` | An array of expected types of the address (see [next section](#address-types-and-sources)). If set, the wallet SHOULD check that the address matches one of the types provided | +| `sources` | An array of acceptable sources for names (see [next section](#address-types-and-sources)). If set, the wallet SHOULD restrict name lookup to relevant sources | +| `senderAddress` | Either a string or an array of strings. If the address pointed to by `addressName` is equal to one of the addresses in `senderAddress`, the addressName is interpreted as the sender referenced by `@.from` | +| *Examples* | --- | +| `vitalik.eth` | Field Value 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
`types` = ["eoa"] (Externally Owned Account)
`sources` = ["ens"] (Ethereum Name Service) | +| `Uniswap V3: WBTC-USDC` | Field Value 0x99ac8cA7087fA4A2A1FB6357269965A2014ABc35
`types` = ["contract"] | +| `Sender` | Field Value 0x0000000000000000000000000000000000000000
`senderAddress` = ["0x0000000000000000000000000000000000000000"]
`types` = ["eoa"] | + --- -| **`addressName`** | | -|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| *Description* | Display address as a trusted name if a trusted source exists, an EIP-55 formatted address otherwise. See [next section](#address-types-and-sources) for a reference of trusted sources | -| *Parameters* | --- | -| `types` | An array of expected types of the address (see [next section](#address-types-and-sources)). If set, the wallet SHOULD check that the address matches one of the types provided | -| `sources` | An array of acceptable sources for names (see [next section](#address-types-and-sources)). If set, the wallet SHOULD restrict name lookup to relevant sources | +| **`tokenTicker`** | | +|----------------------------|----------------------------------------------------------------------------------------------| +| *Description* | Display address as an [ERC-20](./eip-20.md) token ticker | +| *Parameters* | --- | +| `chainIdPath` or `chainId` | Optional. The chain on which the token is deployed (constant or path). When present, the wallet SHOULD resolve the token ticker for this chain. Useful for cross-chain swap clear signing. At most one of `chainId` and `chainIdPath` may be set. | +| *Examples* | --- | +| `MYTOKEN` | Field Value 0xaabbccddeeff.... | + + +#### Interoperable addresses + +Interoperable address format applicable to solidity type `bytes`. + +| **`interoperableAddressName`** | | +|-------------------------|--------------------| +| *Description* | Display bytes as an [ERC-7930](./eip-7930.md) Interoperable Name. The wallet SHOULD parse the binary format, extract the target address and chain, and display it in the standard `
@ # ` format. | +| *Parameters* | --- | +| `types` | An array of expected types of the address (see [next section](#address-types-and-sources)). If set, the wallet SHOULD check that the address matches one of the types provided | +| `sources` | An array of acceptable sources for names (see [next section](#address-types-and-sources)). If set, the wallet SHOULD restrict name lookup to relevant sources | | `senderAddress` | Either a string or an array of strings. If the address pointed to by `addressName` is equal to one of the addresses in `senderAddress`, the addressName is interpreted as the sender referenced by `@.from` | -| *Examples* | --- | -| `vitalik.eth` | Field Value 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
`types` = ["eoa"] (Externally Owned Account)
`sources` = ["ens"] (Ethereum Name Service) | -| `Uniswap V3: WBTC-USDC` | Field Value 0x99ac8cA7087fA4A2A1FB6357269965A2014ABc35
`types` = ["contract"] | -| `Sender` | Field Value 0x0000000000000000000000000000000000000000
`senderAddress` = ["0x0000000000000000000000000000000000000000"]
`types` = ["eoa"] | +| *Examples* | --- | +| `0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045@eip155:1#4CA88C9C` | Field Value = `0x00010000010114D8DA6BF26964AF9D7EED9E03E53415D37AA96045` | #### Address types and sources @@ -1177,12 +1454,14 @@ Sources values are wallet manufacturer specific. Some example values could be: | local | Address MAY be replaced with a local name trusted by user. Wallets MAY consider that `local` setting for `sources` is always valid | | ens | Address MAY be replaced with an associated ENS domain | -### Wallets -#### Ledger +### Encryption schemes -[TBD] Link to ledger specification +List of currently supported encryption schemes hints for fields +| Scheme | Description | +| ------------- | --------------------------------- | +| fhevm | FHEVM full homomorphic encryption | ## Rationale @@ -1191,7 +1470,7 @@ Sources values are wallet manufacturer specific. Some example values could be: It is expected that the main limitation to adoption of ERC-7730 will be the complexity of writing this interface description file compared to interest of writing it. This drove a few choices when introducing this ERC specification: -* The form of an ERC itself will allow usage of these file by any wallets (Hardware or Software, without restrictions), and in turn drive up the benefits for dApps developers to provide their own ERC-7730 description +* The form of an ERC itself will allow usage of these file by any wallets (Hardware or Software, without restrictions), and in turn drive up the benefits for app developers to provide their own ERC-7730 description * The specification is intended to be directly readable by developers, in order to facilitate direct edition by developers. * In addition, a set of edition tools will be created and open sourced to ease visualization of the results for end users @@ -1204,55 +1483,260 @@ Hardware wallets tend to have more limited capabilities that will impact what th This consideration is driving a few choices done for ERC-7730: * Complex UI constructs like layouts, grouping and re-ordering of fields have been left to a wallet specific section, yet unspecified. After a time, we may see patterns emerge between wallets in terms of minimal features. * Representation of fields has been created to allow "flattening" the list of fields, when handling complex message structures. This flattened representation is expected to work better with Hardware wallets in particular, and is recommended at first. -* Some formatters that require recursive constructs, like `calldata` are expected to work with restrictions at first, especially on Hardware wallets. +* Some formatters that require recursive constructs, like `calldata` are expected to work with restrictions at first, especially on Hardware wallets. + +### Internationalization + +This specification intentionally does not address internationalization or localization of displayable strings. + +All display strings in ERC-7730 files—including intents, labels, field names, and enumeration values—are expected to be authored in English. + +Future versions of this specification may consider standardized internationalization support once the base standard achieves wider adoption and the ecosystem can better assess the need for multilingual support. + +### User Operations (EIP-4337) + +Clear signing [EIP-4337](./eip-4337.md) User Operations is supported using the `PackedUserOperation` EIP-712 signature format. See [example-userops-eip-712.json](../assets/eip-7730/example-userops-eip712.json) for a reference implementation. + +The inner calldata typically contains an `execute` call to interact with other contracts. This can be clear signed using a separate ERC-7730 file - see [example-account-execute.json](../assets/eip-7730/example-account-execute.json). Since execute functions are implementation-specific, each smart wallet will need its own ERC-7730 file. + +Smart wallets commonly use a proxy pattern. Refer to the [Proxy support](#proxy-support) section for guidance. + +### Batch transactions (EIP-5792) + +When displaying batch transactions as defined in [EIP-5792](./eip-5792.md), wallets SHOULD either: +* Concatenate the `interpolatedIntent` strings of individual operations using " and " as a separator to create a single, human-readable description of the entire batch. +* Concatenate screens generated using `intent` and required `fields`, clearly separating individual transactions + +The `interpolatedIntent` approach provides users with a clear, natural language summary of complex multi-step operations without requiring them to mentally piece together separate transaction descriptions. The `intent` approach provide a more controllable wallet UI for space limited wallets. + +*Permit + Swap example:* +```json +[ + { + "function": "permit", + "interpolatedIntent": "Approve {spender} to spend {value} USDC" + }, + { + "function": "swapExactTokensForTokens", + "interpolatedIntent": "Swap {amountIn} for at least {amountOutMin}" + } +] +``` +Combined display: **"Approve Uniswap Router to spend 1000 USDC and Swap 1000 USDC for at least 0.25 WETH"** + +*Approve + Swap example:* +```json +[ + { + "function": "approve", + "interpolatedIntent": "Approve {_spender} to spend {_value}" + }, + { + "function": "exactInputSingle", + "interpolatedIntent": "Swap {amountIn} for at least {amountOutMinimum}" + } +] +``` +Combined display: **"Approve Uniswap V3 Router to spend 5000 DAI and Swap 5000 DAI for at least 1.2 ETH"** + +*Approve + Swap + Mint NFT example:* +```json +[ + { + "function": "approve", + "interpolatedIntent": "Approve {_spender} to spend {_value}" + }, + { + "function": "swapExactTokensForETH", + "interpolatedIntent": "Swap {amountIn} for at least {amountOutMin} ETH" + }, + { + "function": "mint", + "interpolatedIntent": "Mint {quantity} NFT(s) from {collection}" + } +] +``` +Combined display: **"Approve DEX Router to spend 2000 USDC and Swap 2000 USDC for at least 0.5 ETH and Mint 2 NFT(s) from NftProject"** + +**Implementation guidance for batch transactions using interpolatedIntents** + +Wallets implementing batch transaction display SHOULD: +1. Process each transaction in the batch to generate its `interpolatedIntent` +2. Join the resulting intent strings with " and " (note the spaces) +3. Display the combined string as a single transaction summary +4. Provide a way for users to view individual transaction details if needed +5. Fall back to displaying individual `intent` fields if any `interpolatedIntent` processing fails + +Wallets MAY: +- Apply additional formatting (e.g., capitalizing the first letter, adding a period at the end) +- Truncate very long combined intents and provide expansion UI +- Group related operations visually while still showing the combined intent + +### Extensibility to other structured data formats + +In the future, this specification could be extended to structured data like Meta Transaction in [EIP-2771](./eip-2771.md), User Operations in [EIP-4337](./eip-4337.md), and batch transaction payloads in [EIP-5792](./eip-5792.md). + +The `interpolatedIntent` field is particularly well-suited for [EIP-5792](./eip-5792.md) batch transactions, as it enables wallets to concatenate multiple operation descriptions into a single, natural language sentence. By joining individual `interpolatedIntent` strings with " and ", wallets can provide users with clear, readable summaries of complex multi-step operations like "Approve Uniswap Router to spend 1000 USDC and Swap 1000 USDC for at least 0.25 WETH". This significantly improves the user experience when reviewing batch transactions compared to displaying separate, disconnected operation descriptions. See the [Value Interpolation](#value-interpolation) section for detailed implementation guidance and examples. + +### Common keywords +The DeFi ecosystem has matured and has some repeating usecases with well defined terms for different actions. While this ERC does not define specific standards for DEX or Lending protocol intents, for the sake of easier user readibility, teams with similar products should align on similar terminology to describe actions and actors. We encourage the creation of another layer of standardization on top of ERC-7730 to be recommeneded and even enforced in clear signing registries for improved user security. ## Test Cases -More examples can be found on the Ledger ERC-7730 public repository +More examples can be found in the asset folder. -## Security Considerations +| File name | Description | +| --- | ---| +| [example-main.json](../assets/eip-7730/example-main.json) | Simple example of a basic ERC-7730 file | +| [example-erc-20.json](../assets/eip-7730/example-erc20.json) | Simple example of defining an interface for ERC-20 contracts | +| [example-include.json](../assets/eip-7730/example-include.json) | Example of including an interface into another ERC-7730 file | +| [example-eip-712.json](../assets/eip-7730/example-eip712.json) | Clear signing EIP-712 example | +| [example-array-iteration.json](../assets/eip-7730/example-array-iteration.json) | Examples of various array iteration modes and field grouping features | +| [example-maps.json](../assets/eip-7730/example-maps.json) | Example of maps of constants | +| [example-maps-pools.json](../assets/eip-7730/example-maps-pools.json) | Example of maps of constants | +| [example-visibility-rules.json](../assets/eip-7730/example-visibility-rules.json) | Example of controlling the visibility of fields | +| [example-account-execute.json](../assets/eip-7730/example-account-execute.json) | Example of EIP-4337 smart wallet execute function clear signing | +| [example-userops-eip-712.json](../assets/eip-7730/example-userops-eip712.json) | Example of EIP-4337 User Operation clear signing | -The main security concern introduced by ERC-7730 is to avoid attacks that would use the ERC-7730 formatting mechanism to trick users into signing something wrong. -Two main attack vectors that needs to be mitigated: -* Avoid using a well-formed, trusted ERC-7730 file on data that should not be formatted using it -* Avoid badly formed, incorrect ERC-7730 files that would hide critical parameters or create misinterpretations through bed formatting +## Security Considerations + +ERC-7730 creates a risk surface where malicious actors could abuse formatting to make unsafe transactions appear benign. In addition to misapplied or malformed files, implementers should anticipate phishing techniques such as (a) parameter injection using legitimate, high-profile contract formats, (b) registry poisoning with well-formed but misleading entries, and (c) front-end/CDN compromise that prompts signatures for unexpected contracts or parameters. ### Binding context -The binding `context` is the way we mitigate the first attack, by specifying in the ERC-7730 file itself, **what kind** of structured data should be formatted by said file. +The binding `context` mitigates misuse by specifying exactly which structured data a given ERC-7730 file may format. app developers MUST set restrictive constraints; wallets MUST verify all constraints and ensure formatting is cryptographically bound to the reviewed data and not tamperable in transit (including on resource-constrained hardware wallets). -dApps developers MUST make sure that constraints in the binding context are restrictive enough to apply only to relevant data. +### Registry poisoning -Wallets MUST ensure that all constraints are met before formatting any data for review by the end user. +Curation and registry of ERC-7730 files are out of scope for this specification, but any external registry or wallet ecosystem MUST assume it will be targeted. A secure registry should (a) require cryptographically verifiable provenance and attestations for each ERC-7730 file and its maintainer, (b) keep a public, tamper-evident history of submissions, approvals, and revocations, (c) implement a mechanism to credibly link contract ownership or authority to the submitted file, and (d) adopt a clear governance model with multi-party sign-off and automated monitoring to detect anomalies or mass poisoning attempts. These measures mitigate attacks on ERC-7730 files in transit and make registry compromise significantly harder without constraining this ERC itself. -It is also expected that the way the formatting is applied to the data is not vulnerable to MITM or tampering attack from an attacker in control of communication means. Constraints have been design simple, so that a strong cryptographic binding to the data can be created even on resource constrained Hardware Wallets. +### External data resolution -### Curation model +ERC-7730 formatting relies on resolving various external data sources to provide human-readable information. This resolution introduces trust assumptions that wallets must understand when showing ERC-7730 information. -The second attack is not directly mitigated by the specification (beyond providing recommendations). It is rather expected that wallets will use a two-step curation process, and NOT trust ERC-7730 files coming directly from the public repository. +**Trust assumptions** -Rather wallets SHOULD do some additional verifications on the ERC-7730 file itself and corresponding dApps before trusting the file to build their user's UI. +The transition from transaction data to human readable data requires trust in external data sources to resolve some parts of the presentation layer. Without the resolution, it's not readable and missed the whole point of the ERC, with the resolution it adds a risk factor: +- **Token metadata** (tickers, decimals) A token address translated to a token ticker and proper decimals applied to the `tokenAmount`. +- **NFT collection names** and token metadata for the `nftName` format +- **Address resolution** when formatting `addressName` using ENS or similar tools +Malicious or compromised data sources could: +- Display incorrect token names, leading users to believe they are approving transactions for legitimate tokens when they are not +- Display malicious addresses as if it is a trusted address name +- Present fake NFT collection names to obscure the true nature of transactions +- Provide misleading enumeration values that misrepresent transaction parameters +- Supply modified ABIs that change how transaction data is interpreted -```mermaid -flowchart LR - dev([dApp Developer]) - pub_repo[ERC-7730 Public Repo] - c_cur([Community Curators]) - wal_repo[Wallet specific repo] - w_cur([Wallet Curators]) - wallet[Wallet] +**Example attack scenarios** - c_cur -- approve --> pub_repo - dev -- publish --> pub_repo - pub_repo -- fetch --> wal_repo - w_cur -- check & approve --> wal_repo - wal_repo -- use --> wallet +1. **Token name spoofing**: A malicious ERC-7730 file references a fake token contract at address `0x1234...` that returns "USDT" as its ticker and 6 decimals, making a transaction appear to transfer legitimate USDT when it actually transfers a worthless token. - classDef human fill:#555 - class dev,c_cur,w_cur human -``` +2. **Address name substitution**: A compromised ENS resolver returns a malicious address naming resolution causing a send to an address not anticipated + +**Mitigations** + +Wallets SHOULD: +- Maintain curated lists of well-known addresses (tokens, contracts, collections) and warn users when encountering unknown entities +- Display full addresses alongside resolved names in some form +- Clearly indicate the source and trust level of resolved names and metadata + +Wallets MUST: +- Display clear warnings when external data cannot be resolved or verified (e.g., "Unknown token") +- Never fallback to presenting untrusted information as if it was trusted and verified + +### Proxy support + +The proxy pattern is widely use in Ethereum in multiple scenarios to provide flexibility to essentially immutable contracts: +* Making smart contracts *upgradable* +* Making smart contracts *composable* +* Creating multiple *instances* of a reference implementation + +These patterns are often used in conjunction. Supporting those patterns require some considerations from both smart contract developers and wallets. + +**Upgradeable proxy contracts** + +Upgradeable proxy contracts (such as Transparent Proxies or UUPS proxies as defined in [EIP-1822](./eip-1822.md)) use a fixed proxy address that delegates calls to a replaceable implementation contract. For these contracts, the ERC-7730 file SHOULD be bound to the *proxy* address, since this is the stable address users interact with. + +When an upgrade introduces breaking ABI changes (new functions, modified signatures, or removed functions), developers SHOULD update the ERC-7730 file in the registry accordingly. Since the old implementation is no longer callable after the upgrade, the previous ERC-7730 definitions can be safely replaced. + +Wallets need to ensure they use the latest version of the registry and match the transaction target against the *proxy* address. + +**Composable contracts** + +Composable contracts, like Diamond proxies, aggregate through a single *proxy* address, multiple implementation facets. Each facet has its own ABI. + +A good pattern to support diamond proxies is to introduce an ERC-7730 file for each facet, without any context info, and use the composability of ERC-7730 files to create an overarching ERC-7730 file that includes all implemented facets of a proxy and bound to the final *proxy* address. + +Similarly wallets just need to ensure they have the latest version of the registry and that the target address is the *proxy* address. + +**Multi-instanciation** + +This pattern is typically used by smart wallet factories to efficiently deploy many smart wallets relying on a single, secure implementation. + +It is not efficient nor advisable to deploy an ERC-7730 for *each* smart wallet address. In this case, it is recommended to bind the ERC-7730 file to the *implementation* address of the smart wallet and rely on the user's wallet to detect that the target address is actually a proxy to a well known implementation address. + +### Interpolated intent security + +The `interpolatedIntent` feature introduces additional attack surfaces that wallets MUST mitigate: + +**Injection attacks** + +Malicious contracts could craft parameter values that, when interpolated, create misleading intent descriptions. For example: +- A token name containing "to vitalik.eth" could make "Send {amount} to {recipient}" appear as "Send 100 USDT to vitalik.eth to 0x123..." +- Addresses with misleading ENS names could obscure the true destination +- Very long formatted values could push critical information off-screen + +*Mitigations:* +- Wallets MUST apply the same formatting and validation to interpolated values as they would for separately displayed fields +- Wallets MUST validate that ENS names and other address labels are verified before interpolation +- Wallets SHOULD display field values separately in addition to the interpolated intent, or provide easy access to detailed field view + +**Format consistency** + +Wallets MUST ensure that the formatting applied during interpolation exactly matches the formatting that would be applied if the field were displayed separately. This prevents attackers from exploiting differences in formatting to hide malicious values. + +*Implementation requirement:* +- The interpolation engine MUST use the same formatter functions and parameters specified in the corresponding field format specification +- If a path in `interpolatedIntent` does not have a corresponding field format specification, interpolation MUST fail and fall back to `intent` + +**Path validation** + +Wallets MUST validate that all paths in interpolation expressions: +1. Follow the [path reference rules](#path-references) +2. Reference fields that exist in the structured data being signed +3. Have corresponding field format specifications in the `fields` array +4. Do not create circular references or unbounded recursion + +**Fallback behavior** + +If interpolation fails for any reason (invalid path, formatting error, missing field specification, validation failure), wallets MUST: +1. Fall back to displaying the static `intent` field +2. Display all fields separately according to their field format specifications +3. NOT attempt to partially process the interpolated intent +4. Optionally warn the user that the dynamic description could not be generated + +**Resource constraints** + +Hardware wallets and other resource-constrained devices MAY choose not to implement `interpolatedIntent` support. The `intent` field MUST always be present as a fallback for such devices. + +**Testing and validation** + +ERC-7730 file authors SHOULD test `interpolatedIntent` strings with: +- Minimum and maximum expected values for each interpolated field +- Edge cases like zero amounts, maximum allowances, expired dates +- Long addresses, token names, and other string fields to ensure proper truncation + +### Encryption support + +**Display pointers to encrypted values** + +Pointers to encrypted values MAY NOT be fully opaque handles, e.g. they MAY use meaningful prefixes or suffixes containing metadata about the encrypted value. When only partially displaying pointers, wallets SHOULD NOT display these structured parts so that users can discriminate different encrypted values. + +### Future work + +Future improvements could bind frontends to verified contract manifests so that a compromised UI cannot silently substitute a different contract. Clear, auditable registry governance (multi-party signoff, monitoring, and revocation) can further raise the cost of phishing, parameter injection, and front-end compromise attacks. ## Copyright diff --git a/specs/erc7730-v2.schema.json b/specs/erc7730-v2.schema.json index 8f3db9787..7eb520a07 100644 --- a/specs/erc7730-v2.schema.json +++ b/specs/erc7730-v2.schema.json @@ -127,10 +127,12 @@ "properties": { "name": { - "type": "string" + "$ref": "#/$definitions/stringValueOrMap", + "description": "The expected domain name. Can be a literal, constant reference, or map reference." }, "version": { - "type": "string" + "$ref": "#/$definitions/stringValueOrMap", + "description": "The expected domain version. Can be a literal, constant reference, or map reference." }, "chainId": { "type": "integer", @@ -189,13 +191,13 @@ "owner": { "title": "Owner display name", - "type": "string", + "$ref": "#/$definitions/stringValueOrMap", "description": "The display name of the owner or target of the contract / message to be clear signed." }, "contractName": { "title": "Contract Name", - "type": "string", + "$ref": "#/$definitions/stringValueOrMap", "description": "The name of the contract targeted by the transaction or message." }, @@ -399,16 +401,20 @@ "description": "A description of the intent of the structured data signing, that will be displayed to the user.", "type": "object", "additionalProperties": { - "type": "string" + "$ref": "#/$definitions/stringValueOrMap" } + }, + { + "title": "Map-resolved intent", + "description": "A map reference resolving to the intent based on context.", + "$ref": "#/$format/mapReference" } - ] }, "interpolatedIntent": { "title": "Interpolated intent message", - "description": "An optional intent string with embedded field values using {path} interpolation syntax. This provides a dynamic, contextual description by embedding actual transaction/message values directly in the intent string. Wallets should prefer displaying interpolatedIntent when available and fall back to intent if interpolation fails. See the specification for detailed formatting behavior and security considerations.", - "type": "string" + "$ref": "#/$definitions/stringValueOrMap", + "description": "An optional intent string with embedded field values using {path} interpolation syntax. This provides a dynamic, contextual description by embedding actual transaction/message values directly in the intent string. Wallets should prefer displaying interpolatedIntent when available and fall back to intent if interpolation fails. See the specification for detailed formatting behavior and security considerations." }, "fields": { "title": "Field Formats set", @@ -516,8 +522,8 @@ }, "value": { "title": "Value", - "type": ["string", "integer", "number", "boolean"], - "description": "A literal value on which the format should be applied instead of looking up a field in the structured data." + "$ref": "#/$definitions/scalarValueOrMap", + "description": "A literal value, constant reference, or map reference on which the format should be applied instead of looking up a field in the structured data." }, "field": { @@ -540,8 +546,8 @@ }, "label": { "title": "Field Label", - "description": "The label of the field, that will be displayed to the user in front of the formatted field value.", - "type": "string" + "$ref": "#/$definitions/stringValueOrMap", + "description": "The label of the field, that will be displayed to the user in front of the formatted field value." }, "format": { "title": "Field Format", @@ -691,7 +697,9 @@ "type": "string", "description": "The path to the key used to resolve a value using the referenced map." } - } + }, + "required": ["map", "keyPath"], + "additionalProperties": false }, "names": { "anyOf": [ @@ -786,19 +794,8 @@ }, "senderAddress": { "title": "Sender Address", - "oneOf": [ - { - "type": "string", - "description": "An address equal to this value is interpreted as the sender referenced by `@.from`." - }, - { - "type": "array", - "description": "An array of addresses, any of which are interpreted as the sender referenced by `@.from`.", - "items": { - "type": "string" - } - } - ] + "$ref": "#/$definitions/stringOrArrayValueOrMap", + "description": "An address (or array of addresses, constant reference, or map reference) interpreted as the sender referenced by `@.from`." } }, "additionalProperties": false @@ -809,11 +806,7 @@ "properties": { "callee": { "title": "Callee Address", - "type": ["string", "object"], - "anyOf": [ - { "type": "string" }, - { "$ref": "#/$format/mapReference" } - ], + "$ref": "#/$definitions/stringValueOrMap", "description": "The address of the contract being called by this embedded calldata." }, "calleePath": { @@ -823,11 +816,7 @@ }, "selector": { "title": "Called Selector (Optional)", - "type": ["string", "object"], - "anyOf": [ - { "type": "string" }, - { "$ref": "#/$format/mapReference" } - ], + "$ref": "#/$definitions/stringValueOrMap", "description": "The selector being called, if not contained in the calldata. Hex string representation." }, "selectorPath": { @@ -837,11 +826,7 @@ }, "amount": { "title": "Amount (Optional)", - "type": ["integer", "object"], - "anyOf": [ - { "type": "integer" }, - { "$ref": "#/$format/mapReference" } - ], + "$ref": "#/$definitions/integerValueOrMap", "description": "The associated amount in native currency, if the calldata can be associated with a container value." }, "amountPath": { @@ -850,12 +835,8 @@ "description": "The path to the associated amount in native currency, if the calldata can be associated with a container value." }, "spender": { - "title": "Spender Path (Optional)", - "type": ["string", "object"], - "anyOf": [ - { "type": "string" }, - { "$ref": "#/$format/mapReference" } - ], + "title": "Spender (Optional)", + "$ref": "#/$definitions/stringValueOrMap", "description": "The associated spender, if the calldata can be associated with a container value." }, "spenderPath": { @@ -882,12 +863,8 @@ "properties": { "token": { "title": "Token", - "type": ["string", "object"], - "anyOf": [ - { "type": "string" }, - { "$ref": "#/$format/mapReference" } - ], - "description": "The token address, or a path to a constant in the ERC 7730 file." + "$ref": "#/$definitions/stringValueOrMap", + "description": "The token address, a constant reference, or a map reference." }, "tokenPath": { "title": "Token Path", @@ -896,37 +873,22 @@ }, "nativeCurrencyAddress": { "title": "Native Currency Address", - "oneOf": [ - { - "type": "string", - "description": "An address equal to this value is interpreted as an amount in native currency rather than a token." - }, - { - "type": "array", - "description": "An array of addresses, any of which are interpreted as an amount in native currency rather than a token.", - "items": { - "type": "string" - } - } - ] + "$ref": "#/$definitions/stringOrArrayValueOrMap", + "description": "An address (or array of addresses, constant reference, or map reference) equal to which the amount is interpreted as native currency rather than a token." }, "threshold": { "title": "Unlimited Threshold", - "type": "string", + "$ref": "#/$definitions/stringValueOrMap", "description": "The threshold above which the amount should be displayed using the message parameter rather than the real amount." }, "message": { "title": "Unlimited Message", - "type": "string", + "$ref": "#/$definitions/stringValueOrMap", "description": "The message to display when the amount is above the threshold." }, "chainId": { "title": "Chain ID", - "type": ["integer", "object"], - "anyOf": [ - { "type": "integer" }, - { "$ref": "#/$format/mapReference" } - ], + "$ref": "#/$definitions/integerValueOrMap", "description": "Optional. The chain on which the token is deployed (constant, or a map reference). When present, the wallet SHOULD resolve token metadata (ticker, decimals) for this chain. Useful for cross-chain swap clear signing where the same token address may refer to different chains." }, "chainIdPath": { @@ -947,11 +909,7 @@ "properties": { "chainId": { "title": "Chain ID", - "type": ["integer", "object"], - "anyOf": [ - { "type": "integer" }, - { "$ref": "#/$format/mapReference" } - ], + "$ref": "#/$definitions/integerValueOrMap", "description": "Optional. The chain on which the token is deployed (constant, or a map reference). When present, the wallet SHOULD resolve the token ticker for this chain. Useful for cross-chain swap clear signing." }, "chainIdPath": { @@ -971,12 +929,8 @@ "properties": { "collection": { "title": "Collection Address", - "type": ["string", "object"], - "anyOf": [ - { "type": "string" }, - { "$ref": "#/$format/mapReference" } - ], - "description": "The collection address, or a path to a constant in the ERC 7730 file." + "$ref": "#/$definitions/stringValueOrMap", + "description": "The collection address, a constant reference, or a map reference." }, "collectionPath": { "title": "Collection Path", @@ -1077,19 +1031,8 @@ }, "senderAddress": { "title": "Sender Address", - "oneOf": [ - { - "type": "string", - "description": "An address equal to this value is interpreted as the sender referenced by `@.from`." - }, - { - "type": "array", - "description": "An array of addresses, any of which are interpreted as the sender referenced by `@.from`.", - "items": { - "type": "string" - } - } - ] + "$ref": "#/$definitions/stringOrArrayValueOrMap", + "description": "An address (or array of addresses, constant reference, or map reference) interpreted as the sender referenced by `@.from`." } }, "additionalProperties": false @@ -1122,6 +1065,43 @@ "title": "ID", "type": "string", "description": "An internal identifier that can be used either for clarity specifying what the element is or as a reference in device specific sections." + }, + + "stringValueOrMap": { + "title": "String Value or Map Reference", + "description": "A string literal, a constant reference ($.metadata.constants.*), or a map reference resolving to a string.", + "anyOf": [ + { "type": "string" }, + { "$ref": "#/$format/mapReference" } + ] + }, + + "integerValueOrMap": { + "title": "Integer Value or Map Reference", + "description": "An integer literal, or a map reference resolving to an integer.", + "anyOf": [ + { "type": "integer" }, + { "$ref": "#/$format/mapReference" } + ] + }, + + "stringOrArrayValueOrMap": { + "title": "String, String Array, or Map Reference", + "description": "A string literal, an array of strings, a constant reference, or a map reference.", + "anyOf": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } }, + { "$ref": "#/$format/mapReference" } + ] + }, + + "scalarValueOrMap": { + "title": "Scalar Value or Map Reference", + "description": "A scalar literal (string, integer, number, boolean), or a map reference.", + "anyOf": [ + { "type": ["string", "integer", "number", "boolean"] }, + { "$ref": "#/$format/mapReference" } + ] } } } \ No newline at end of file From 63adf4ede72d25a386ae99dfa5939471c37c0c14 Mon Sep 17 00:00:00 2001 From: Maroutis <118286466+Maroutis@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:47:33 +0100 Subject: [PATCH 2/2] fix: allow $id on display references Reference display nodes can override shared definitions with path, params, visibility, and labels, but they currently reject optional $id metadata. Allow $id in the v2 schema and document it so referenced field overrides can be identified without failing validation. Made-with: Cursor --- specs/erc-7730.md | 1 + specs/erc7730-v2.schema.json | 3 +++ 2 files changed, 4 insertions(+) diff --git a/specs/erc-7730.md b/specs/erc-7730.md index 6317950a1..5305b370e 100644 --- a/specs/erc-7730.md +++ b/specs/erc-7730.md @@ -718,6 +718,7 @@ each key name is a [path](#path-references) in the structured data, `fields` is an array of elements, each element being either: * A single [*field format specification*](#field-format-specification) * A reference to a format specification in the `definitions` section: by declaring an object with two keys, a `path` key with the [path](#path-references) to the field being formatted, and a `$ref` key with a path to the internal definition. + * A reference object can also carry an optional internal `$id` used to identify the field override * A reference object can override a field format specification `params` by including its own `params` sub-key, whose values will take precedence over the common definition * A recursive [*structured data format specification*](#structured-data-format-specification), by declaring an object with two keys, a `path` key with the [path](#path-references) to the field(s) being formatted, and a `fields` with the recursive list of sub-fields. diff --git a/specs/erc7730-v2.schema.json b/specs/erc7730-v2.schema.json index 8f3db9787..394d0e915 100644 --- a/specs/erc7730-v2.schema.json +++ b/specs/erc7730-v2.schema.json @@ -466,6 +466,9 @@ "title": "Reference", "description": "A reference to a shared definition that should be used as the field formatting definition. The value is the key in the display definitions section, as a path expression $.display.definitions.DEFINITION_NAME. It is used to share definitions between multiple messages / functions.", "properties": { + "$id": { + "$ref": "#/$definitions/id" + }, "path": { "$ref": "#/$format/path" },