> ## Documentation Index
> Fetch the complete documentation index at: https://visualsign.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Chain Metadata

> Supply ABIs and IDLs per request at runtime, the alternative to contributing a preset upstream

Chain metadata helps the parser understand smart contract interactions. Without metadata, contract calls may appear as raw hex data. With metadata, they're parsed into meaningful field names and values.

## When to provide metadata

| Scenario | Metadata needed? |
| - | - |
| Native transfers (ETH, SOL, SUI) | No |
| Known protocols (Uniswap, Jupiter, Aave) | No (built-in support) |
| Standard token transfers (ERC-20, SPL) | No (built-in support) |
| Custom smart contracts | Yes |
| Anchor programs without public IDL | Yes |

## Ethereum: Contract ABI

For Ethereum and EVM chains, provide the contract ABI to decode function calls. ABIs are passed as a map keyed by contract address, so wallets can supply one ABI per contract the transaction touches.

For an end-to-end Ethereum integration walkthrough (supported networks, what's auto-decoded, the rendered payload), see [Ethereum for Wallets](https://visualsign.dev/wallet-integration/chains/ethereum.md).

### gRPC example

```go theme={null}
request := &pb.ParseRequest{
    UnsignedPayload: rawTxHex,
    Chain: pb.Chain_CHAIN_ETHEREUM,
    ChainMetadata: &pb.ChainMetadata{
        Metadata: &pb.ChainMetadata_Ethereum{
            Ethereum: &pb.EthereumMetadata{
                NetworkId: proto.String("ETHEREUM_MAINNET"),
                AbiMappings: map[string]*pb.Abi{
                    "0xdac17f958d2ee523a2206206994597c13d831ec7": &pb.Abi{
                        Value: contractABI, // JSON ABI string
                    },
                },
            },
        },
    },
}
```

### Address casing

`abi_mappings` keys are 0x-prefixed contract addresses. The parser parses each key into a numeric address before registration, so lookup is case-insensitive: `0xdAC17...` and `0xdac17...` resolve to the same contract. Use a consistent casing convention (all lowercase or EIP-55 checksummed) across your ABI store to avoid duplicate entries for the same address.

### ABI format

The ABI is standard Solidity ABI JSON:

```json theme={null}
[
  {
    "name": "transfer",
    "type": "function",
    "inputs": [
      { "name": "recipient", "type": "address" },
      { "name": "amount", "type": "uint256" }
    ]
  },
  {
    "name": "approve",
    "type": "function",
    "inputs": [
      { "name": "spender", "type": "address" },
      { "name": "amount", "type": "uint256" }
    ]
  }
]
```

### Specifying network

For EVM chains other than mainnet, set `network_id`. The parser recognizes a broad set of values (Ethereum mainnet and testnets, BSC, Polygon, Avalanche, Gnosis, Celo, Fantom, Optimism, Arbitrum, Base, Blast, Mantle, World Chain, zkSync Era, Linea, Scroll, Zora, Unichain, and their testnet variants where applicable). The authoritative list is defined by `network_id_to_chain_id` in [`src/chain_parsers/visualsign-ethereum/src/networks.rs`](https://github.com/anchorageoss/visualsign-parser/blob/main/src/chain_parsers/visualsign-ethereum/src/networks.rs).

Built-in token metadata is preloaded only for `ETHEREUM_MAINNET`, `POLYGON_MAINNET`, `ARBITRUM_MAINNET`, `OPTIMISM_MAINNET`, and `BASE_MAINNET`. On other recognized chains, token symbol and decimal resolution is limited to the built-in registry; `chain_metadata` currently only accepts `network_id` and `abi_mappings`, so wallet-supplied token metadata is not yet supported. Always pass the **actual** `network_id` for the chain you're parsing; the value overrides the transaction's chain ID and reusing an unrelated one will mislabel the Network field.

```go theme={null}
ChainMetadata: &pb.ChainMetadata{
    Metadata: &pb.ChainMetadata_Ethereum{
        Ethereum: &pb.EthereumMetadata{
            NetworkId: proto.String("POLYGON_MAINNET"),
            AbiMappings: map[string]*pb.Abi{
                "0x...": &pb.Abi{ Value: contractABI },
            },
        },
    },
}
```

## Solana: Program IDL and fee cap

For Solana programs, provide the Anchor IDL to decode instructions:

### gRPC example

```go theme={null}
request := &pb.ParseRequest{
    UnsignedPayload: rawTxHex,
    Chain: pb.Chain_CHAIN_SOLANA,
    ChainMetadata: &pb.ChainMetadata{
        Metadata: &pb.ChainMetadata_Solana{
            Solana: &pb.SolanaMetadata{
                Idl: &pb.Idl{
                    Value: anchorIDL, // JSON IDL string
                    IdlType: pb.SolanaIdlType_SOLANA_IDL_TYPE_ANCHOR,
                },
            },
        },
    },
}
```

### Multiple programs

If your transaction interacts with multiple programs, use the IDL mappings:

```go theme={null}
ChainMetadata: &pb.ChainMetadata{
    Metadata: &pb.ChainMetadata_Solana{
        Solana: &pb.SolanaMetadata{
            IdlMappings: map[string]*pb.Idl{
                "Program1111111111111111111111111111111111111": {
                    Value: program1IDL,
                    IdlType: pb.SolanaIdlType_SOLANA_IDL_TYPE_ANCHOR,
                },
                "Program2222222222222222222222222222222222222": {
                    Value: program2IDL,
                    IdlType: pb.SolanaIdlType_SOLANA_IDL_TYPE_ANCHOR,
                },
            },
        },
    },
}
```

### Supported IDL types

| Type | Description |
| - | - |
| `SOLANA_IDL_TYPE_ANCHOR` | Anchor framework IDL |
| `SOLANA_IDL_TYPE_SHANK` | Shank IDL format |

### Fee cap

Every Solana payload shows the fee cap in force: the most the transaction can pay in base fee plus priority fee. Send `max_fee_lamports` to set it for one transaction, for example to the fee your wallet quoted, and the row is labelled "Max Fee". Without it the parser applies its default cap of 0.1 SOL and labels the row "Max Fee (default)". The parser rejects the request when the transaction commits to more than the cap in force, so the row is always a true upper bound. With `include_intermediate_output`, the cap in force is also the trailing `max_fee_lamports` field of the Solana intermediate output. If you rewrite the compute budget instructions after parsing, for example to re-price at signing time, the fee this cap bounds changes, so re-check the final fee against the cap yourself.

```go theme={null}
ChainMetadata: &pb.ChainMetadata{
    Metadata: &pb.ChainMetadata_Solana{
        Solana: &pb.SolanaMetadata{
            MaxFeeLamports: proto.Uint64(5105000), // 0.005105 SOL
        },
    },
}
```

Over the HTTP gateway the same field is the camelCase key `maxFeeLamports`, as a JSON number or a decimal string.

## NEAR: Token Metadata

For NEAR Intents, provide a symbol and decimals for asset ids not already in the parser's compiled-in seed table (see [NEAR](https://visualsign.dev/chains/near.md#visualization-strategy)). Unlike Ethereum ABIs and Solana IDLs, entries are keyed by NEAR Intents asset id (e.g. `nep141:wrap.near`), not by a contract or program address.

### gRPC example

```go theme={null}
request := &pb.ParseRequest{
    UnsignedPayload: rawTxHex,
    Chain: pb.Chain_CHAIN_NEAR,
    ChainMetadata: &pb.ChainMetadata{
        Metadata: &pb.ChainMetadata_Near{
            Near: &pb.NearMetadata{
                NetworkId: proto.String("NEAR_MAINNET"),
                TokenMappings: map[string]*pb.TokenMetadataEntry{
                    // An asset the parser's compiled-in table doesn't cover.
                    // Unsigned entries fill gaps only, so an override of a
                    // known asset has to carry a signature (see below).
                    "nep141:514910771af9ca656af840dff83e8264ecf986ca.factory.bridge.near": &pb.TokenMetadataEntry{
                        Value: `{"symbol":"LINK","decimals":18}`,
                    },
                },
            },
        },
    },
}
```

### Signed entries (optional)

A `TokenMetadataEntry` can carry an optional signature, so a wallet can trust an entry beyond the raw request itself. Unlike ABI/IDL signing (one curve per chain), the curve here is chosen per entry by `origin_chain`, since a NEAR Intents asset id can describe a NEAR-native asset or one bridged in from another chain:

| `origin_chain` | Curve | Same curator identity family as |
| - | - | - |
| `TOKEN_ORIGIN_CHAIN_NEAR` (or unset) | ed25519 | A NEAR-only curator key |
| `TOKEN_ORIGIN_CHAIN_ETHEREUM` | secp256k1 | Ethereum ABI signing |
| `TOKEN_ORIGIN_CHAIN_SOLANA` | ed25519 | Solana IDL signing |

Over the JSON/HTTP gateway the field carries these protobuf names, not the underlying numbers, and an unrecognized name is rejected rather than defaulted:

```json theme={null}
{
  "networkId": "NEAR_MAINNET",
  "tokenMappings": {
    "nep141:514910771af9ca656af840dff83e8264ecf986ca.factory.bridge.near": {
      "value": "{\"symbol\":\"LINK\",\"decimals\":18}",
      "originChain": "TOKEN_ORIGIN_CHAIN_ETHEREUM"
    }
  }
}
```

The signature covers `value`'s bytes verbatim (the same convention as `Abi.value`/`Idl.value`), bound to both the asset id and the NEAR network so it cannot be replayed under a different asset or on the other network. Sign the exact bytes you put in `value` — the parser hashes them as supplied and never re-encodes them, so canonicalizing the JSON before signing produces a different prehash and the signature will not verify.

The network is part of the signed scope because a NEAR implicit account id (a 64-hex address) is the same string on mainnet and testnet. Sign against the canonical id — `NEAR_MAINNET` or `NEAR_TESTNET` — matching the network the request resolves to:

* The request's `network_id`, when it supplies one. An unrecognized value (`testnet` rather than `NEAR_TESTNET`, say) fails the whole request rather than falling back.
* Otherwise the network the parser was configured for, which is mainnet unless the deployment says otherwise.

This is the same resolution the rendered `Network` field uses, so the network a signature is checked against is always the network the payload displays.

The resolved network must also agree with the accounts in the request. An account whose suffix contradicts it — `.testnet` under mainnet, or `.near` under testnet — fails the whole request rather than rendering under a network it does not belong to. This applies to a transaction's own `signer_id`/`receiver_id` and to every rendered intents envelope's `signer_id`/`verifying_contract`, on both the standalone and the on-chain batch path. Implicit (64-hex) accounts carry no suffix and are not constrained. So a wallet that sets `network_id` must set it to the network its accounts actually live on; supplying `NEAR_TESTNET` for a mainnet envelope is an error, not a relabelling.

An entry whose signature fails to verify is rejected outright, never silently downgraded to unsigned.

An entry the parser cannot attribute to a curator it recognizes — one carrying **no** signature, or one whose signature verifies under a key the deployment has not enrolled — is accepted only when both of these hold:

* The deployment runs a permissive trust posture. `parser_cli` runs the strict posture and rejects both cases.
* The asset is not already in the parser's compiled-in token table. Unattributed metadata fills gaps for assets the parser doesn't know; it cannot override a curated entry, since `decimals` scales the amount a signer sees.

The two cases are worth the same in trust terms — signing with a key nobody enrolled buys nothing over omitting the signature — so a correction to an already-known asset has to be signed **by a recognized curator**. Where such an entry is accepted as a gap fill, its rendered amount carries an `unverified-token-metadata` warning naming which of the two it was, so the signer is never told an entry was unsigned when it was signed by an unrecognized key. Which keys count as recognized is set per origin chain by the deployment — see [NEAR](https://visualsign.dev/chains/near.md#configuring-curator-keys).

See [`visualsign::signing`](https://github.com/anchorageoss/visualsign-parser/blob/main/src/visualsign/src/signing.rs) for the exact prehash construction.

## Library integration

When using the library directly (not gRPC), pass metadata via `VisualSignOptions`. The Ethereum metadata shape mirrors the proto: a `network_id` and an `abi_mappings` map keyed by contract address.

```rust theme={null}
use std::collections::HashMap;
use generated::parser::{Abi, ChainMetadata, EthereumMetadata, chain_metadata::Metadata};
use visualsign::vsptrait::VisualSignOptions;

let mut abi_mappings = HashMap::new();
abi_mappings.insert(
    "0xdac17f958d2ee523a2206206994597c13d831ec7".to_string(),
    Abi { value: contract_abi_json, signature: None },
);

let options = VisualSignOptions {
    metadata: Some(ChainMetadata {
        metadata: Some(Metadata::Ethereum(EthereumMetadata {
            network_id: Some("ETHEREUM_MAINNET".to_string()),
            abi_mappings,
        })),
    }),
    ..Default::default()
};
```

See [Library Integration](https://visualsign.dev/wallet-integration/library/getting-started.md) for the full end-to-end example.

## Obtaining metadata

### Ethereum ABIs

| Source | How to get |
| - | - |
| Etherscan | View verified contract → Contract tab → ABI |
| Compilation | `solc --abi Contract.sol` |
| Hardhat | `artifacts/contracts/Contract.sol/Contract.json` |
| Foundry | `out/Contract.sol/Contract.json` |

### Solana IDLs

| Source | How to get |
| - | - |
| Anchor build | `target/idl/program_name.json` |
| On-chain | Some programs publish IDL on-chain |
| Program repo | Check the program's source repository |

### NEAR token metadata

| Source | How to get |
| - | - |
| Token contract | `ft_metadata` view call on the NEP-141 account |
| Omni Bridge registry | `get_token_id` / `get_native_token_id` view calls on `omni.bridge.near` resolve to a NEP-141 account, whose `ft_metadata` then gives symbol/decimals |

## Without metadata

If you don't provide metadata for a contract interaction, the parser will:

1. Decode what it can (addresses, amounts in native token)
2. Show the function selector (first 4 bytes) as hex
3. Display raw calldata for unknown parameters

This is safe but less informative. Users see something like:

```
Contract Interaction
Method: 0xa9059cbb (unknown)
To: 0x1234...
Data: 0x...
```

## Next steps

* [How Parsing Works](https://visualsign.dev/wallet-integration/core-concepts/how-parsing-works.md): Understand the parsing pipeline
* [Error Handling](https://visualsign.dev/wallet-integration/core-concepts/error-handling.md): Handle cases where parsing fails
* [gRPC API Reference](https://visualsign.dev/api-reference.md): Full request/response formats


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.