---
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)
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
---

## 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. 

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.

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.

In the current scope, the only structured data supported are smart contract calls (aka calldata) in EVM transactions and EIP-712 structured messages. 

## 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:

* 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.

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.

> *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

The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

### 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.

```json
{
    "$schema": "https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/specs/erc7730-v1.schema.json",

    "context": {
        "$id": "Tether USD",
        "contract" : {
            "abi": "https://api.etherscan.io/api?module=contract&action=getabi&address=0xdac17f958d2ee523a2206206994597c13d831ec7",
            "deployments": [ 
                {
                    "chainId": 1,
                    "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
                },
                {
                    "chainId": 137,
                    "address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
                },
                {
                    "chainId": 42161,
                    "address": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"
                }
            ]
        }
    }, 

    "metadata": {
        "owner": "Tether",
        "info": {
            "legalName": "Tether Limited",
            "url": "https://tether.to/",
            "lastUpdate": "2017-11-28T12:41:21Z"  
        }
    },

    "display": {
        "formats": {
            "transfer(address _to,uint256 _value)": {
                "intent": "Send",
                "fields": [
                    {
                        "path": "_to",
                        "label": "To",
                        "format": "addressOrName"
                    },
                    {
                        "path": "_value",
                        "label": "Amount",
                        "format": "tokenAmount",
                        "params": {
                            "tokenPath": "@.to"
                        }
                    }
                ],
                "required": ["_to", "_value"],
                "excluded": []
            }
        }
    }
}
```

The `$schema` key refers to the latest version of this specification json schema (currently version 1).

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.

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

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.

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 `_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). 

### Common concepts

#### Key naming convention

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

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.

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
```

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

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).

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.

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. 

It is sometime necessary for formatting of fields of the structured data to reference values of the *container structure*. These values are dependent on the container structure itself and are defined in the [Reference](#reference) section.

#### Path references

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.
* 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 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. 

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.

*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

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)

#### Organizing files

Smart contracts and EIP-712 messages are often re-using common interfaces or types that share similar display formatting. This specification supports a basic inclusion mechanism that enables sharing files describing specific interfaces or types.

The `includes` top-level key is a URL pointing to an ERC-7730 file that MUST follow this specification.

A wallet using an ERC-7730 file including another file SHOULD merge those files into a single reference file. When merging, conflicts between common unique keys are resolved by prioritizing the including file.

*Merging field format specifications*

Special care must be taken when merging [field format specifications](#field-format-specification). These objects are grouped in an array under the `fields` key, allowing ordering of field formatters. When merging the two arrays, a wallet SHOULD:
* Merge together objects sharing the same `path` value, overriding parameters of the included file with those of the including file.
* Append objects with `path` values not part of the included file to the resulting array.

*Example*

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)": {
                "intent": "Approve",
                "fields": [
                    {
                        "path": "_spender",
                        "label": "Spender",
                        "format": "addressOrName"
                    },
                    {
                        "path": "_value",
                        "label": "Amount",
                        "format": "tokenAmount",
                        "params": {
                            "tokenPath": "@.to",
                            "threshold": "0x8000000000000000000000000000000000000000000000000000000000000000",
                            "thresholdLabel": "Unlimited"
                        }
                    }
                ],
                "required": ["_spender", "_value"]
            }
        }
    }
}
```
Note that there are no keys for binding the contract to a specific address or owner, nor any contract specific metadata.

The following file would include this generic interface and bind it to the specific USDT contract, overriding the threshold value to one relative to USDT:
```json
{
    "context": {
        "$id": "Tether USD",
        "contract" : {
            "deployments": [
                {
                "chainId": 1,
                "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
                }
            ]
        }
    }, 

    "includes": "../ercs/calldata-ERC-20-tokens.json",
    
    "metadata": {
        "owner": "Tether",
        "info": {
            "legalName": "Tether Limited",
            "url": "https://tether.to/",
            "lastUpdate": "2017-11-28T12:41:21Z"  
        },
        "token": {
            "ticker": "USDT",
            "name": "Tether USD",
            "decimals": 6
        }
    },

    "display": {
        "formats": {
            "approve(address _spender,uint256 _value)": {
                "fields": [
                    {
                        "path": "_value",
                        "params" : {
                            "threshold": "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

The `context` section describes a set of *constraints* that must be verified by the structured data and container structure before formatting them using the ERC-7730 file. A wallet MUST verify that the structured data and container it is trying to sign matches the constraints of the `context` section.

The current version of this specification only supports two types of binding context, EVM smart contracts and EIP-712 domains. 

All context support an `$id` sub-key as an internal identifier (only relevant to provide a human-readable name to the ERC-7730 file)

#### EVM smart contract binding context (denoted 'calldata')

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.

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.

A wallet MAY use this URL to check unknown addresses, default is to fail constraint on unknown addresses.

**`contract.factory`**

An object describing the factory used to deploy smart contracts that can be clear signed using the ERC-7730 file.

A factory is a json object with:
* `deployEvent` key, specifying the solidity signature of the events emitted when deploying a clear-signable contract.
* `deployments` key: an array of deployment options as in `contract.deployments`. These deployments represent the addresses at which the *factory* is deployed.  

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. 

A wallet MUST verify that the message being signed matches the `types` and `primaryType` of the schema.

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. 

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 
* `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).

**`eip712.deployments`**

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`

A deployment option is an object with:
* `chainId`: an EIP-155 identifier of the chain the described contract is deployed on.
* `address`: the address of the deployed contract on specified `chainId` chain.

**`eip712.domainSeparator`**

An hex string containing the value of the *domainSeparator* to check. 

Wallet MUST verify that the message *EIP-712 Domain* hashes (as defined in EIP-712) to the value in `eip712.domainSeparator`. 

When the exact construction of the EIP-712 domain is not known (for instance, when the smart contract code only contains the hash value of the domain separator), `domainSeparator` and `domain.verifyingContract` can still be used to target the right message recipients. 

*Examples*

```json
{
    "context" : {
        "eip712": {
            "schemas": [
                {
                    "types": {
                        "EIP712Domain": [],
                        "PermitDetails": [],
                        "PermitSingle": []
                    },
                    "primaryType": "PermitSingle"
                }
            ],
            "domain": {
                "name": "Permit2"
            },
            "deployments": [
                {
                    "chainId": 1,
                    "address": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
                },
                {
                    "chainId": 42161,
                    "address": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
                }
            ]
        }
    }
}
```
The previous snippet defines a context for a `Permit2` EIP-712 message (types have been omitted for readability).

The `domain` key indicates that the message signed domain MUST contain a `name` key of value `Permit2`. The `deployments` key means that the domain must contain both `chainId` and `verifyingContract` keys and they MUST match one of the deployment options (here, on ETH mainnet and Arbitrum).

### `Metadata` section

The `metadata` section contains information about constant values relevant in the scope of the current contract / message (as matched by the `context` section). 

In the context of wallets and clear signing, these constant values are either used to construct the UI when approving the signing operation, or to provide parameters / checks on the data being signed. But these constant values are relevant outside of the scope of wallets, and should be understood as reference values concerning the bound contract / message.

All keys but the `metadata.owner` key are optional.

**`metadata.owner`**

A key containing a displayable name of the *owner* 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
  * `lastUpdate` is the date of the contract (or verifying contract) last update 
  * `url` is the official URL of the owner

A wallet MAY use this information to display additional details about the targeted contract.

**`metadata.token`**

The `token` key is only relevant for `contract` ERC-7730 files and only for contracts supporting an ERC-20 interface. 

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`

**`metadata.constants`**

This key contains in a json object all the constants that can be re-used as parameters in the formatters, or that make sense in the context of this contract / message. 

It is a list of key / value pairs, the key being used to reference this constant (as a *path* starting with a root node `$.` i.e. `$.metadata.constants.KEY_NAME`).

*Example*

```json
{
    "metadata": {
        "constants": {
            "nativeAssetAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
        }
    }
}
```
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.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. 

Each key of the `enums` object is an *enumeration* name. Enumeration names can be referred to in the `display` section formatters by using a path starting with root node `$.` (i.e. `$.metadata.enums.ENUM_NAME`). 

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
{
    "metadata": {
        "enums": {
            "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`.

### `Display` section

The `display` section contains the actual formatting instructions for each field of the bound structured data. It is split into two parts, a `display.definitions` key that contains common formats that can be re-used in the other parts and a `display.formats` key containing the actual format instructions for each function / message type bound to the specification file.

**`display.definitions`**

The `definitions` key is an object in which each sub-key is a [*field format specification*](#field-format-specification). The sub-key name is the name of the common definition and is used to refer to this object in the form of a path starting with root node `$.` (i.e. `$.display.definitions.DEF_NAME`).

Definitions don't usually include the `path` or `value` key of a [*field format specification*](#field-format-specification), since they are intended for re-use in other fields specifications, that will specify locally what path they apply to.

*Example*

```json
{
    "display": {
        "definitions": {
            "sendAmount": {
                "label": "Amount to Send",
                "format": "tokenAmount",
                "params": {
                    "tokenPath": "fromToken",
                    "nativeCurrencyAddress": "$.display.constants.addressAsEth"
                }
            }
        }
    }
}
```
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`**

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*.

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.

> 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. 

For EIP-712, the key names MUST be one of the primary type names included in `context.eip712.schemas`.

#### Structured data format specification

A *Structured data format specification* is used to describe how to format all the fields of a single function or EIP-712 message. It is contained in a single json object under each sub-keys of `display.formats`.

**`$id`**

This key is purely internal and used to specify a human-readable identifier for this specification.

**`intent`** 

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.

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. 

```json
{
    "display": {
        "formats": {
            "withdraw(uint256)": {
                "intent": {
                    "Native Staking": "Withdraw",
                    "Rewards": "Consensus & Exec"
                }
            }
        }
    }
}
```
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.

**`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. 

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. 

*Examples*

Let's assume the following solidity contract

```solidity
pragma solidity ^0.8.0;

contract MyContract {

    struct MyParamType {
        string name;
        uint256 value;
    }

    // Function declaration
    function myFunction(address _address, uint256 _amount, MyParamType memory _param) public {
        // Function logic here
    }
}
```

The following ERC-7730 shows examples for the three kinds of `fields` options
```json
{
    "display": {
        "formats": {
            "myFunction(address _address,uint256 _amount,MyParamType _param)" : {
                "fields": [
                    {
                        "path": "_address",
                        "$ref": "$.display.definitions.sendTo",
                        "params": { "type": "eoa" }
                    },
                    {
                        "path": "_amount",
                        "label": "Number of items",
                        "format": "raw"
                    },
                    {
                        "path": "_param",
                        "fields": [
                            { "path": "name", "$ref": "$.display.definitions.itemName" },
                            { "path": "value", "$ref": "$.display.definitions.itemReference" }
                        ]
                    }
                ]
            }
        }
    }
}
```
* 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`

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)" : {
                "fields": [
                    { "path":"_param.name", "$ref": "$.display.definitions.itemName" },
                    { "path":"_param.value", "$ref": "$.display.definitions.itemReference" }
                ]
            }
        }
    }
}
```

**`required`** 

Required fields, as an array of *paths* referring to specific fields. 

Wallets SHOULD display at least all the fields referred by the `required` key.

**`excluded`** 

Intentionally excluded fields, as an array of *paths* referring to specific fields. 

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.

**`screens`**

Wallet specific grouping information. The structure of the `screens` sub-keys is wallet maker specific and referenced in the [Wallets](#wallets) section.

The current ERC-7730 specification does not contain layout or ordering information, since capabilities for controlling those are highly wallet dependent. 

It is expected that wallets supporting advanced re-ordering and layout features would enable those in the `screens` object.

Wallets SHOULD NOT override formatting information in the `fields` key in the wallet specific `screens` key.

#### Field format specification

A *field format specification* is a json object defining how to format a single field of the structured data.

* 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 

#### Slices in paths

A slice can be applied at the end of paths.

A slice on a primitive type like uint256, bytes and string means that the associated [field format specification](#field-format-specification) MUST only be applied to the corresponding slice of bytes of the underlying data.

A slice on an array type means that the associated [field format specification](#field-format-specification) or recursive [structured data format specification](#structured-data-format-specification) MUST be applied to ALL the array elements part of the slice.

*Example*

```json
{
    "display": {
        "formats": {
            "0x09b81346": {
                "fields": [
                    {
                        "path": "params.amountInMaximum",
                        "label": "Maximum Amount to Send",
                        "format": "tokenAmount",
                        "params": {
                            "tokenPath": "params.path.[0:20]"
                        }
                    }
                ]
            }
        }
    }
}
```
The `tokenPath` parameter uses a slice on a `bytes` value, indicating only the first 20 bytes should be taken as the address and used as the reference for the formatting of the corresponding token amount.

```json
{
    "display": {
        "formats": {
              "0xb2f1e6db": {
                "fields": [
                    {
                        "path": "pools.[-1]",
                        "label": "Last pool",
                        "format": "raw"
                    }
                ]
            }
        } 
    }
}
```
This examples uses an array slice to indicate that only the last element of the `pools` array should be displayed using the `raw` format.

```json
{
    "display": {
        "formats": {
            "PermitBatch": {
                "fields": [
                    {
                        "path": "details.[]",
                        "fields": [
                            {
                                "path": "amount",
                                "label": "Amount allowance",
                                "format": "tokenAmount",
                                "params": {
                                    "tokenPath": "token"
                                }
                            },
                            {
                                "path": "expiration",
                                "label": "Approval expires",
                                "format": "date",
                                "params": {
                                    "encoding": "timestamp"
                                }
                            }
                        ]
                    }
                ]
            }
        }
    }
}
```
This example uses a full array selector `details.[]` to apply the list of the underlying two underlying formats to ALL the elements of the `details` array.

### Reference

### Container structure values

This section describes all container structure supported by this specification and possible references path to relevant values.

#### 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 |          |

#### 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 |          |

### Field formats

In the following references, the format title is the value to use under the `format` key of a [*field format specification*](#field-format-specification).


#### Integer formats

Formats usable for 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 |
| `nativeCurencyAddress` | 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 <br> `tokenPath` =0x6B17...1d0F (DAI, 6 decimals)                                                                                                                                                                              |
| `Unlimited DAI`        | Field value = 0xFFFFFFFF <br> `tokenPath` =0x6B17...1d0F (DAI, 6 decimals) <br> `threshold` "0xFFFFFFFF"                                                                                                                                             |
| `Max DAI`              | Field value = 0xFFFFFFFF <br> `tokenPath` =0x6B17...1d0F (DAI, 6 decimals) <br> `threshold` "0xFFFFFFFF" <br> `message` = "Max"                                                                                                                      |

---
| **`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 |
| *Parameters*                            | ---                                                                                                           |
| `collectionPath` or `collection`        | A path reference, or constant value for the collection address                                                |
| *Examples*                              | ---                                                                                                           |
| `ETH-USD December 10, 2021 3:48 PM GMT` | Field Value = 674 <br> `collectionPath` = "0xaa3a...84ea" (from rarible)                                      |

---
| **`date`**                  |                                                                                           |
|-----------------------------|-------------------------------------------------------------------------------------------|
| *Description*               | Display int as a date, using specified encoding. Date display RECOMMENDED use of RFC 3339 |
| *Parameters*                | ---                                                                                       |
| `"encoding": "timestamp"`   | value is encoded as a unix timestamp                                                      |
| `"encoding": "blockheight"` | value is a blockheight and is converted to an approximate unix timestamp                  |
| *Examples*                  | ---                                                                                       |
| `2024-02-29T08:27:12`       | Field Value = 1709191632 <br> `encoding` = "timestamp"                                    |
| `2024-02-29T09:00:35`       | Field Value = 19332140 <br> `encoding` = "blockheight"                                    |

---
| **`duration`** |                                                                                         |
|----------------|-----------------------------------------------------------------------------------------|
| *Description*  | Display int as a duration interpreted in seconds and represented as a duration HH:MM:ss |
| *Parameters*   | None                                                                                    |
| *Examples*     | ---                                                                                     |

---
| **`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*    | ---                                                                                                                                                                                                                                                                                                               |

---
| **`enum`**    |                                                                                            |
|---------------|--------------------------------------------------------------------------------------------|
| *Description* | Value is converted using referenced constant enumeration values                            |
| *Parameters*  | ---                                                                                        |
| `$ref`        | An internal path (starting with root node `$.`) to an enumerations in `metadata.constants` |
| *Examples*    |                                                                                            |

#### String formats

Formats usable for strings.

---
| **`raw`**     |                                               |
|---------------|-----------------------------------------------|
| *Description* | Display as an UTF-8 encoded string            |
| *Parameters*  | None                                          |
| *Examples*    | ---                                           |
| `Ledger`      | Field Value = ['4c','65','64','67','65','72'] |

#### Bytes formats

Formats usable for bytes

---
| **`raw`**     |                                                |
|---------------|------------------------------------------------|
| *Description* | Display byte array as an hex-encoded string    |
| *Parameters*  | None                                           |
| *Examples*    | ---                                            |
| `123456789A`  | Field Value = Value ['12','34','56','78','9a'] |

---
| **`calldata`**           |                                                                                                                                                                                                                                                                                                                                                                           |
|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *Description*            | Data contains a call to another 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*             | ---                                                                                                                                                                                                                                                                                                                                                                       |
| `selector`               | Optional selector, if not present the first 4 bytes of the calldata are interpreted as the selector                                                                                                                                                                                                                                                                       |
| `calleePath` or `callee` | A path reference or constant value for the contract being called                                                                                                                                                                                                                                                                                                          |
| *Examples*               |                                                                                                                                                                                                                                                                                                                                                                           |

#### Address

Formats usable for address

---
| **`raw`**       |                                                                                              |
|-----------------|----------------------------------------------------------------------------------------------|
| *Description*   | Display address as an [EIP-55](./eip-55.md) formatted string. Truncation is device dependent |
| *Parameters*    | None                                                                                         |
| *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                          |
| *Examples*        | ---                                                                                                                                                                                    |
| `vitalik.eth`     | Field Value 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045                                                                                                                                 |

#### Address types and sources

Address names trusted sources specify which type and source of trusted names SHOULD be used to replace an address with a human-readable names. 

When specified a wallet MUST only use specified sources to resolve address names. Wallet MUST verify the type of the address if able to. 

When omitted, a wallet MAY use any source to resolve an address.

| Address type | Description                                    |
|--------------|------------------------------------------------|
| wallet       | Address is an account controlled by the wallet |
| eoa          | Address is an Externally Owned Account         |
| contract     | Address is a well known smart contract         |
| token        | Address is a well known ERC-20 token           |
| collection   | Address is a well known NFT collection         |

A wallet MAY verify that a `wallet` address is in fact controlled by the wallet, and reject signing if not the case.

Sources values are wallet manufacturer specific. Some example values could be:

| Source type | Description                                                                                                                        |
|-------------|------------------------------------------------------------------------------------------------------------------------------------|
| 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

[TBD] Link to ledger specification


## Rationale

### Human readability

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 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

### Wallet limitations

Wide support by wallets is key for adoption of this specification. 

Hardware wallets tend to have more limited capabilities that will impact what they can display securely, especially since the intention of the specification is to create a strong security binding between the spec and the data being reviewed.

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.  

## Test Cases

More examples can be found on the Ledger ERC-7730 public repository

## Security Considerations

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

### 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.

dApps developers MUST make sure that constraints in the binding context are restrictive enough to apply only to relevant data.

Wallets MUST ensure that all constraints are met before formatting any data for review by the end user. 

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.

### Curation model

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. 

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.


```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]

  c_cur -- approve --> pub_repo
  dev -- publish --> pub_repo
  pub_repo -- fetch --> wal_repo
  w_cur -- check & approve --> wal_repo  
  wal_repo -- use --> wallet

  classDef human fill:#555
  class dev,c_cur,w_cur human
```

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).