EIP-7730: Proposal for a clear signing standard format for wallets

Hi All,

As Ledger, we want to improve the way user interacts with their wallets by displaying more clearly to them what message / transaction they are going to sign (what we call clear signing).

Basing the UI on the ABI does not lead to the best experience, the types are too broad and not easily interpreted as is. This proposal complements the definitions in the ABI with metadata targeted at specifying how to format the display of a transaction / messages for review.

Our goal is to make it easy for contract developers to define & control the interaction of their end users when calling their contract. Making it an EIP will enable all wallets to benefit from this information.

The ERC pull request is here: Add ERC: Structured Data Clear Signing Format by lcastillo-ledger ¡ Pull Request #509 ¡ ethereum/ERCs ¡ GitHub

And for more info, we’ve created an intro video: https://www.youtube.com/watch?v=-O7aX6vUvs8

Thanks!

8 Likes

gm @lcastillo-ledger, how does this EIP handle multi-call and recursive parsing? For example, the uniswap router works via a multicall in many of the cases, so if we rely on the simple 7730 descriptors, it won’t be able to go into detail of what’s going on inside the multicall.

Let me know if this is something you’ve thought about

1 Like

Hi @Ivshti and thanks for the feedback!

Indeed this is a case we’ve considered, and currently you’d handle it using the calldata format when defining your function, associated with a path of the form data.[], specifying that the formatter should be applied to all the elements of the data array. The calldata format tells the wallet that the parameter is a nested calldata, and should come with its own ERC 7730 file to describe how to clear sign it (can be the same file).

For example this is how we’d define the ERC 7730 file for the ERC-6357 multicall:

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

    "context": {
        "$id": "ERC-6357 Multicall",
        "contract": {
            "abi": [ ... ]
        }
    },

    "display": {
        "formats": {
            "multicall(bytes[] data)": {
                "intent": "Execute Multiple Transactions",
                "fields": [
                    {
                        "path": "data.[]",
                        "format": "calldata",
                        "params": {
                            "calleePath": "@.to"
                        }
                    }
                ],
                "required": ["calls"]
            }
        }
    }
}

Note that this is not yet supported on Ledger devices, so this form has not been tested extensively. Most notable caveats:

  • I’m realising we’re missing a way to specify the values passed in the nested calls, and should probably be added in parameters
  • We’re also not distinguishing delegate calls from normal calls. Using a target of “@.to” wold work, but we might want to show that a call is a delegate call in the UI.
  • Packing / serializing calldata in unusual formats wouldn’t work without embedding complex logic in the ERC 7730, which defeats the purpose of having a descriptive model rather than a programmatic one
1 Like

@lcastillo-ledger thanks for the answer

have you thought about how the multiple calls will be displayed to the user? Would the UI need to show nested intent texts, or will they be concatenated with “and” or just flattened at the top level?

Like, what if the multicall contains a swap and a transfer, would this show “Swap X for Y and send Z to …”

1 Like

Replying for ledger here (since this is clearly wallet specific and will probably never be specific by the erc 7730).
Given hardware wallets constraints on memory and screen size, the very first implementation will probably be flattened with a clear separator between each calls until we know we whether we can do better.
So probably something like
Embedded Call 1 -----
Swap X for Y
Embedded Call 2 -----
Send Z to A

1 Like

@lcastillo-ledger How would someone support signing 712 messages for multisig or smart contract wallet like gnosis safe? We have thousands of safe wallets deployed and more being deployed everyday with same EIP712 signature structure. Would not be feasible to keep adding addresses all safe wallets across all chains in PR

1 Like

Ah I love this!!! I’d like to add some functionality that I think will make all of this “just work”.

May I recommend a new ERC-7730 function called getWalletDocsUri. Here is an example solidity implementation

string constant DOCS_URI = "...."; // string here
uint256 public latestDocsVersion = 0;

function getWalletDocsUri(uint256 docsVersion) external view returns(string memory) {
   // Could easily add conditionals/mappings/etc here....
   return DOCS_URI;
}

This would return a URI (similar to that of an NFT) which has the JSON object with all the data. There are a few nice things about this:

  1. The JSON could be base64 encoded, so the entire documentation could be placed on-chain.
  2. It could be easily updatable, and map new versions of the docs based on the latestDocsVersion.

If you don’t wish to have your docs on-chain, then you could still host them on a site. Many tokens like ERC20s, could just point to another contract that already has the ERC20 ABI implemented, and would not have to spend any additional gas other than adding this additional function.

2 Likes

They could all just point to a docs_uri based on the safeVersion:

So it would make your life actually really easy, almost no lift on your end.

2 Likes

You can use the same concept from ENS contenthash, so it maps the off-chain storage (likely IPFS or similar) to the contract address itself.

Mapping that DOCS_URI to a smart contract means that old contracts would be vulnerable because even if we map it to any other addresses without hardcoding it to the actual wallets, it needs to fetch that smart contract address from somewhere.

Now, new contracts are okay to include the DOCS_URI, but they’re dependent on the developers of their contracts to make the intents really correct and available.

What about we create a singleton contract that registers new DOCS_URI for every contract address, so it can complement already deployed contracts?

In that case, we need a new standard for the registry itself. The pattern is very similar to ERC-7484 but is specialized for clear signing.

As far as I know, one of the issues with the proposals (including ERC-7484) is that it will be one of the first standards that implement non-deterministic attestations, which are directly used for other smart contracts. If we can agree on how attestation works for the registry, then we can move to write the actual standard for clear signing descriptors.

1 Like

@lcastillo-ledger

Would it be possibe if the hardware wallet can support hashing the clear signing data?
If we can run checks at the smart contract level, we can avoid corrupted or replaced data in the pipeline.

2 Likes

Continuing from the discussion on AllWalletDevs…

In this example from the ERC:

{
   "display": {
       "formats": {
           "approve(address spender,uint256 value)": {
               "intent": "Approve",
               "fields": [
                   {
                       "path": "spender",
                       "label": "Spender",
                       "format": "addressName"
                   },
                   {
                       "path": "value",
                       "label": "Amount",
                       "format": "tokenAmount",
                       "params": {
                           "tokenPath": "@.to",
                           "threshold": "0x8000000000000000000000000000000000000000000000000000000000000000",
                           "thresholdLabel": "Unlimited"
                       }
                   }
               ]
           }
       }
   }
}

How are the keys for display.formats["approve(address spender,uint256 value)"].fields[1].params declared? Like where does tokenPath come from? It seems like it’s from the EIP-712 structured data?

1 Like

How about format? Would an ordered array be better than a single string, where the wallet picks the first format it recognizes?

For formats we never really found a use case in which multiple formats would make sense for a single parameter, but if we ever see this case it would be a possible addition

A simple example would be ["name", "address"] (your addressName). Instead of combining them into a single format that internally defines fallback, you’d define a general fallback process.

1 Like

I’ve submitted a companion ERC that adds an optional integrity field to ERC-7730 descriptor files, enabling signer attestation (EOA via EIP-191, contract via ERC-1271). Multiple parties (wallet vendors, auditors) can independently sign a descriptor without altering its content, letting wallets verify which trusted parties have reviewed it.

PR: Add ERC: Integrity Verification for ERC-7730 by llbartekll ¡ Pull Request #1576 ¡ ethereum/ERCs ¡ GitHub

3 Likes

I’m a bit late to the party here, but doesn’t this shift the security model to “trust the host”?

1 Like

You are trusting the registry a bit yes, this is why it must be in a credibly neutral 3rd party’s repo, with independent auditors marking the projects involved.

You are trusting the protocol developers a little in the ERC’s current state… But I have a suggested improvement I think that can fix it.

2 Likes

Ok, a big issue of this ERC in it’s current state is upgrades. Take the following example:

uint256 a = 1;                                                                                
// Original intent: deposit collateral                                                        
// Updated intent: steal funds                                                                
function hi() {                                                                               
                                                                                              
  if (a==1){                                                                                  
    // intent: deposit funds                                                                           
  } else {                                                                                    
    // intent: steal funds                                                                            
  }                                                                                           
} 

In this scenario, the intent can change based on some state variable. I chose this example specifically because it highlights that a function’s intent can change outside of a simple proxy upgrade (which are easier to detect).

The same pattern shows up in:

  • Proxy upgrades — the implementation slot points to new code; selector and parameters are identical, executed bytecode is entirely different.

  • Admin-controlled pause/blacklist — transfer reverts for blacklisted accounts or in paused mode; the displayed “Send tokens” intent becomes false.

  • Fee or routing parameters — a function “swap X for at least Y” silently honors a new fee schedule, or delegatecalls a target whose address is stored in mutable state.

What I’m proposing

A new optional top-level intentMutability field that declares the storage slots whose values bound the displayed intent’s correctness, plus the values witnessed at authoring time:

"intentMutability": {
  "functions": {
    "hi()": {
      "stable": false,
      "stateRefs": [
        {
          "slot": "0x0000000000000000000000000000000000000000000000000000000000000000",
          "expectedValue": "0x0000000000000000000000000000000000000000000000000000000000000001",
          "description": "When slot 0 (`a`) == 1, function deposits collateral. When a != 1, function transfers caller funds to the owner. The owner can change `a`."
        }
      ]
    }
  }
}

Wallets CAN (but not required to, they can do with this information as they please) read each declared slot’s live value and compare to expectedValue before applying the descriptor’s formatting. On mismatch, they downgrade to opaque signing or warn. The check is local — one eth_getStorageAt and a memcmp — so it’s cheap enough to run on every signing.

Absence of intentMutability is treated as “unknown,” not “stable” — wallets warn or downgrade. This is deliberate; the alternative would silently grant every legacy descriptor a trust label it wasn’t authored to bear.

HOWEVER, a wallet can still do whatever they want. If they want to trust an address (like USDC for example, which is behind a proxy and can technically change intent at any time), they may. This will help us prevent lesser-known projects from having their intent verified on the registry, only to rug pull users later once enough wallets have opted in. This also avoids any issues where a wallet wants to upgrade a contract (without malicious intent) and makes the mistake of repurposing a function to do something else (this would be dumb of them, but this is web3, and people do dumb things very often).

The PR

PR: Update ERC-7730: added intent mutability specification by PatrickAlphaC ¡ Pull Request #1738 ¡ ethereum/ERCs ¡ GitHub

Please take a look and review. It includes some examples.

2 Likes

ERC-7730 currently matches descriptors mainly against a contract address, for example tx.to, or against an EIP-712
domain.verifyingContract.

With EIP-7702, a user may interact with their own EOA address, while that account delegates execution to an implementation address:

tx.to = user account
eth_getCode(tx.to) = 0xef0100 || implementationAddress

In this case, the clear-signing descriptor should usually be bound to the implementation address, not to every individual user
account address.

Would the registry be open to supporting an explicit EIP-7702 delegation context, for example:

{
  "context": {
    "contract": {
      "eip7702Delegation": {
        "deployments": [
          {
            "chainId": 1,
            "address": "0xImplementationAddress"
          }
        ]
      }
    }
  }
}

Wallet matching would be:

1. Read tx.to.
2. Fetch eth_getCode(tx.to).
3. If the code is 0xef0100 || implementationAddress, match the descriptor against implementationAddress.
4. Decode tx.data using the descriptor only after that match.

A similar case may exist for EIP-712 when domain
1 Like

Hi @lcastillo-ledger - the ERC-7730 integration is the clear-signing anchor in ERC-8265 (Prepared Transaction Envelope), now on Magicians and at PR review: thread 28557, ethereum/ERCs#1753.

§5.7 routes envelope-level decoded calldata through ERC-7730: each evm-tx content slot carries either a decoderRef URI pointing to an ERC-7730 descriptor on the Clear Signing Alliance registry, or an inline clearSigning block conforming to the ERC-7730 schema for offline wallets that cannot resolve URIs. (The inline variant is the producer-side answer to registry-binding cases like the EIP-7702 delegation @wenzhenxiang raises above.)

10 minutes of your read on §5.7 if you have it: is decoderRef-or-inline the integration shape Ledger wants here, or would you prefer inline-only so the registry stays the canonical fetch path? Either answer simplifies §5.7. Happy to fold any feedback into PR #1753.

1 Like

Hi everyone,

First of all, I would like thank you for ERC-7730 - clear signing is easily one of the most important features for the wallets and it is long overdue for wide adoption.

I would also be glad to try contributing to this effort in any way I can.

I am only starting to learn about this ERC now, coming into clear signing from the point of view of Account Abstraction (ERC-4337/EIP-8141) and Transaction Assertion (EIP-7906) background, and looking for ways to expand and improve the ERC-7730 protocol to make sure it is a complete and a future-proof solution.

I tried to catch up with the last couple of years of discussion around this ERC where possible and the V3 roadmap, but this is not an easy thing to do so I apologize if I end up retreading some old debates. And sorry for the long post btw.

1. Native Account Abstraction support (ERC-4337, EIP-8141 and also EIP-7702)

In its current state ERC-7730 has some support for these, however it seems to explicitly mark these features to be outside of the scope for clear signing. In that case we can at least start working on a companion ERC for clear signing with Account Abstraction.

As we now have wallets that are purely AA-based, and some EVM chains adding native AA (ZKSync, Starknet, Tempo etc.), with Ethereum mainnet likely to follow very soon with EIP-8141, I believe it is critical for the ERC-7730 ecosystem to support as many specifics of AA transactions as possible.

AA interactions may be a little different from regular transactions and it would be unfortunate if we had to replace ERC-7730 with a different, AA-aware format in the near future.

Some specifics of AA interactions are:

Atomic batching

AA operations may define relationships between executed steps (“execute A and B, but if any of these reverts, execute C or D”).
One of the mechanisms to do so is defined in EIP-7867, an extension to EIP-5792, although there are many other competing approaches.

It would be difficult to express such a relationship using the current ERC-7730 as it only sees A, B, C and D as calldata bytes arrays, not as execution steps with complicated dependencies in their own right.

I expect atomic multi-step actions to become much more ubiquitous on Ethereum relatively soon, and the distinction between approve(MAX_UINT256); swap(100); approve(0); executed atomically and non-atomically is extremely significant - one is guaranteed to spend no more than 100 tokens, the other leaves an infinite dangling approval if the swap fails!

I understand that inter-call relationships are not properties of any individual function call, and how addressing this issue is stretching the scope of ERC-7730.

However, as ERC-7730 is the standardized channel for hardware wallets, so if we do not define at least some way of expressing these relationships, it will be hard to express them later.

My suggestion would be to provide a thin, optional batch-level metadata in ERC-7730 to expose the relationship between calldata elements (atomic, sequential, etc.) so that this information is at least available if needed.

Execution Composability

There is an interesting new proposal, ERC-8211: Smart Batching, that addresses the issue of batched transactions that need some dynamic on-chain data as part of their inputs.
For example, a batch of swap(1000 USDC -> ETH); stake(0.5 ETH) may succeed or revert based on the actual ETH price in the ‘swap’.
With “Smart Batching” inputs of one function can be taken from previous function’s results:

Step 1: swap(100 USDC)           → OK, returns 0.0495
Step 2: supply(amount)           → amount = BALANCE(WETH, account) = 0.0495 ✓

It seems like supporting these kinds of relationships is possible and even relatively easy, but will require the "formats.fields" array to be able to expose outcome variables of the execution step, to be referenced in the following steps as inputs.

It would be very interesting to see if we could achieve clear signing for dynamically composed batches.
I understand how to someone it may all seem like “account abstraction galaxy braining” today, but I believe this will become a normal way to interact with Ethereum in the near future.

Account Modularity

As mentioned already, some smart contract accounts may be modular (ERC-6900, ERC-7579 etc.), which may be similar to a proxy contract with multiple implementations,
and this might make it non-trivial to determine the “context” for the actual execution being used.

The modularization standards are all quite different, but it seems like we would need to extend the “context” matching to go deeper into the ERC-4337 UserOperation structure to discover all the necessary data in general.

Delegate Calls

It may be not obvious if the call is made to a contract or if it is a call delegated to a contract without adding some native indication for delegate calls specifically.
Executing a delegatecall in the context of a smart contract account is one of the most dangerous things you can ever do in Ethereum.

EIP-7702 Authorizations (already discussed by @wenzhenxiang)

2. Explicit extensibility mechanism

While ERC-7730 itself is clearly designed to be an extensible format, the text of the standard itself does not seem to define a clear mechanism for such extensibility.

I suggest adopting an approach from ERC-5792 “capabilities” feature, by explicitly defining a way for writing extension ERCs, specifying what they can and cannot change in the core proposal, and for negotiating the extension set between the wallet and the specification.

Specifically, I believe "context", bytes formatting (raw/calldata), encryption formats, internationalization of display strings, and container value sets (e.g. @.from, @.value and their semantics for novel transaction types) should be explicitly marked as extensible.

3. Transaction Outcome Simulation and Transaction Outcome Assertions

Clearly signing the inputs of a transaction is really important, however the code being executed on-chain is in many ways also an “input” for the state transition of the EVM, and users cannot truly understand it.

They can, however, understand the outcome of their transaction in most cases: balances moved, tokens exchanged, approvals set, etc.

Currently, ERC-7730 treats the transaction simulation feature as being “downstream” of clear signing.

I believe the results of transaction simulation deserve to be displayed on the hardware wallet display as well, under a mechanism and trust model similar to the one already used for clear signing specifications.

As ERC-7730 already solves the representational challenges of displaying structured transaction data, it would be natural to add an optional “simulation outcome” metadata format. This includes contract storage layouts, their semantic meaning, valid ERC-20 token names etc.

This becomes especially relevant in the context of Transaction Assertions (a.k.a. “Enforced Simulations”, for example: Caveat enforcers | MetaMask developer documentation) - a mechanism by which the smart contract account runs a post-transaction script verifying the actual changes match the original intention.

I am also currently proposing EIP-7906: Transaction Assertions via State Diff Opcode to allow such post-op enforcement natively inside the EVM.

To make this feature actually secure, it is crucial that we can display these assertions directly on the hardware wallet.

4. Attestations

ERC-8176 already proposes a good foundation for this, and I personally like the idea of natively defining a format for ERC-7730 specifications’ attestation as part of the standard.

5. Specifications Registry & Revocation process

I assume this should be a separate ERC, but it seems like a huge missed opportunity if we don’t end up with some kind of an on-chain open public permissionless decentralized censorship-resistant open-source etc. registry for ERC-7730 specifications, and instead end up relying on Microsoft’s GitHub repository moderated by the Ethereum Foundation, and probably mirrored and cached by wallet manufacturers.

But even from the purely practical side, updating a git repo is a relatively slow process.
If a well-known public contract ends up hacked, it may take a long time to revoke its specifications and remove it from the GitHub registry.
This can be made way faster and permissionlessly with on-chain registry with attestations.

Revocation is also something I believe should be explicitly defined in ERC-7730 - what the “revoked” specification looks like?

Please let me know if there is already an ongoing effort to define and implement something like that. I would love to learn more about it.

6. Proxy detection

It seems like there is no universal mechanism to confidently detect a contract as a proxy with a single known implementation.
However, in your opinion, would it be useful to define a universal "eth_getProxyImplementation" heuristic API with a formally defined behavior?

With most contracts out there being some kinds of proxies, this seems like a very significant part of a clear signing process, and it sounds a little bit underspecified.

7. Field-level descriptions

The current field format specification supports a label as a short name like “Amount”, but has no place for a longer plain-language explanation of what a field means in context.

The purpose of this standard is “Clear Signing”, but realistically it may not always be actually clear what the meaning of a field is just from its name.

For example:

Amount: 10 USDC
ⓘ This is the maximum amount of tokens you are willing to sell. The actual amount may be less.

An optional description key on field format specifications would cover this naturally.
Wallets with enough screen space to show a “more info” pop-up could display it, and resource-constrained hardware wallets would simply ignore it.


Please let me know if any of these are already being addressed in other ERCs or were explicitly rejected.

I would love to hear your feedback and will be happy to start prototyping solutions to these observations in the coming days.

Thanks!

4 Likes

@alex-forshtat-tbk - your 7906 work and the AA-side concerns here intersect directly with an off-chain envelope shape open at PR #1753 (ERC-8265, thread 28557; the §5.7 integration with ERC-7730 is what brought us into this thread above at #18). Three notes from that angle.

On atomic batching (point 2): the evm-batch kind in ERC-8265 carries calls[] + atomicRequired + EIP-5792 status codes 1:1. The approve(MAX_UINT256); swap; approve(0) dangling-approval distinction is exactly the case atomicRequired was added to make explicit at preparation time (§8.1), so the producer’s atomic intent travels as a declarative flag rather than being inferred from calldata.

On delegate calls (point 5): each evm-tx content slot carries operation: 'call' | 'delegatecall' as a first-class typed field per call (§5.3). The clear-signing layer (decoded calldata via ERC-7730 decoderRef or inline clearSigning, §5.7) sees this as a typed field rather than a wallet-side heuristic - the producer-side answer to “should the hardware screen show a delegatecall warning here?”.

On transaction outcome (point 3): §11 Appendix B of ERC-8265 reserves a non-normative outcome reporting seam - terminal status + producer-facing disposition + optional reason + tx identifier where applicable - for an automated producer loop deciding whether to re-prepare or stop. That is the post-action info channel back to the envelope creator, distinct from EIP-7906 post-op state-diff assertions inside the EVM. Both touch outcome at different boundaries; the envelope reserves space for a typed reference rather than picking the shape itself.

Spec + reference impl at PR #1753. Curious whether the AA-facing fields compose cleanly with the V3 roadmap.

2 Likes