How to write metadata manually
Craft ERC-7730 metadata files manually for complex contracts and EIP-712 messages when the builder tool is not enough.
When to use manual implementation
- • You need one file to cover multiple deployments or contract variants
- • Your contract uses EIP-712 or other structured messages
- • Your display logic relies on nested paths, byte slices, or conditional rules
- • You want to reuse enums, constants, or shared definitions across contracts
- • You need the
addressMatcherorfactorycontext options for proxy patterns
- • You target a single contract deployment
- • Function signatures decode cleanly (no nested tuples or calldata slicing)
- • Parameter formatting is minimal
- • You want a quick start (under 10 minutes)
Prerequisites
- Contract ABI or EIP-712 schema for every interaction you plan to support
- Chain IDs and contract addresses
- A list of user-facing fields each signer must display
What you will build
A JSON metadata file that transforms raw calldata into a readable display. For example:
0x23b872dd000000000000000000000000…
000000000000000000000000a0b86991…
000000000000000000000000000001f4
Recipient: alice.eth
Amount: 500 USDT
Write the metadata file
Step 1: Define the context
The context section binds the metadata to one or more contract deployments. For this walkthrough, we use Tether (USDT) deployed on three chains.
{
"$schema": "https://eips.ethereum.org/assets/eip-7730/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" }
]
}
}
}Working with EIP-712? Replace the contract block with an eip712 definition. See the Context section reference for the full schema including addressMatcher and factory options for proxy patterns.
Step 2: Add protocol metadata
The metadata section introduces the protocol to users and stores reusable values such as token definitions and enums.
"metadata": {
"owner": "Tether",
"info": {
"legalName": "Tether Limited",
"url": "https://tether.to/",
"deploymentDate": "2017-11-28T12:41:21Z"
},
"token": {
"ticker": "USDT",
"name": "Tether USD",
"decimals": 6
}
}The metadata section also supports enums (value-to-label mappings) and constants (reusable values). See the Metadata section reference.
Step 3: Define the display rules
The display.formats section maps contract function parameters to the labels and formats shown on the signer.
"display": {
"formats": {
"transfer(address,uint256)": {
"intent": "Send",
"fields": [
{ "path": "_to", "label": "Recipient", "format": "addressOrName" },
{
"path": "_value",
"label": "Amount",
"format": "tokenAmount",
"params": { "tokenPath": "@.to" }
}
],
"required": ["_to", "_value"],
"excluded": []
}
}
}You can key each entry in display.formats by Solidity declaration, canonical signature, or raw selector—use whichever form your tooling already produces:
"transfer(address _to,uint256 _value)": { }, // Solidity declaration
"transfer(address,uint256)": { }, // Canonical signature
"0xa9059cbb": { } // 4-byte selectorEvery function parameter must be listed in either fields or excluded. Validation fails if any parameter is left unaccounted for.
For the full list of format types, path syntax, array helpers, and advanced patterns, see the Format types reference and Path system guide.
Step 4: Reuse definitions with includes (optional)
To share display logic across multiple files, extract common snippets into a helper file and reference it with includes:
{
"$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-v1.schema.json",
"includes": "common-swap.json",
"context": {
"contract": {
"deployments": [{ "chainId": 324, "address": "0x6fd4383cB451173D5f9304F041C7BCBf27d561fF" }]
}
}
}Shared definitions live under display.definitions in the included file:
{
"display": {
"definitions": {
"sendAmount": {
"label": "Amount to Send",
"format": "tokenAmount",
"params": { "tokenPath": "desc.srcToken" }
}
},
"formats": {
"swap(...)": {
"intent": "Swap",
"fields": [
{ "path": "desc.amount", "$ref": "$.display.definitions.sendAmount" }
]
}
}
}
}When the including file redeclares the same key, its version takes precedence. Use this to override only the fields that differ per chain while keeping shared logic in one place.
Step 5: Validate and submit
Run the ERC-7730 validator on your completed file and then follow the Validate & Submit guide to open a pull request to the registry.
Final JSON
The complete file combining all sections above:
{
"$schema": "https://eips.ethereum.org/assets/eip-7730/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/",
"deploymentDate": "2017-11-28T12:41:21Z"
},
"token": {
"ticker": "USDT",
"name": "Tether USD",
"decimals": 6
}
},
"display": {
"formats": {
"transfer(address,uint256)": {
"intent": "Send",
"fields": [
{ "path": "_to", "label": "Recipient", "format": "addressOrName" },
{
"path": "_value",
"label": "Amount",
"format": "tokenAmount",
"params": { "tokenPath": "@.to" }
}
],
"required": ["_to", "_value"],
"excluded": []
}
}
}
}Complete examples
{
"$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-v1.schema.json",
"context": {
"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/",
"deploymentDate": "2017-11-28T12:41:21Z"
}
},
"display": {
"formats": {
"transfer(address,uint256)": {
"intent": "Send",
"fields": [
{ "path": "_to", "label": "To", "format": "addressOrName" },
{ "path": "_value", "label": "Amount", "format": "tokenAmount", "params": { "tokenPath": "@.to" } }
],
"required": ["_to", "_value"],
"excluded": []
}
}
}
}Troubleshooting
Validation fails with “unaccounted parameter”
Every parameter in the function signature must appear in either fields or excluded. Check the error message for the parameter name, then either add it to fields with an appropriate format, or add it to the excluded array.
The function signature in my display key does not match
The key in display.formats must exactly match the function’s canonical signature, Solidity declaration, or 4-byte selector. Common mistakes:
- • Parameter names included when canonical form is expected: use
transfer(address,uint256)nottransfer(address to,uint256 value) - • Tuple syntax differs from ABI: use the full tuple expansion, for example
(address,uint256)notSwapParams - • Extra whitespace around commas or parentheses
Path expression returns nothing or fails validation
Check the path root identifier:
- • Use
#to reference calldata parameters (for example,#.amount) - • Use
$to reference metadata constants (for example,$.constants.maxUint256) - • Use
@to reference transaction envelope fields (for example,@.to,@.value)
See the Path system reference for full syntax.
ABI URL returns an error during validation
If the ABI URL is not reachable or returns an error during validation, embed the ABI directly in the file as a JSON array instead of a URL string. You can copy the ABI from Etherscan’s contract page under the Contract tab.
Your metadata file is complete. Validate it with the CLI tool and open a pull request to the registry.
Go deeper
- Study the full Context reference for factories, matchers, and typed data options
- Reuse enums, constants, and includes using the Metadata reference and Display reference
- Browse real-world patterns in the public registry