Skip to main content

Chain Metadata

Chain Metadata is shared across all proposal types and contains the information that allow transactions to be hashed and executed for it's respective chain. It is a map of chain-specific configuration for each blockchain involved in the proposal. The key of the object is the chain selector ID, and the value is the metadata object. An entry is required for every chain referenced in the proposal's operations.

Chain Metadata Structure​

{
"16015286601757825753": {
"startingOpCount": 1,
"mcmAddress": "0x0"
}
}

Chain Selector ID​

Key uint64
The chain selector ID matching the chain based on the Chain Selectors library.

Metadata Object​

startingOpCount uint64
The starting operation count, typically used for parallel signing processes.


mcmAddress string
The MCM contract address that will process this proposal on the respective chain.


additionalFields object optional
Chain-family-specific fields encoded as JSON. Structure depends on the chain family (see below).

Solana Additional Fields​

Solana chain metadata uses additionalFields for the Timelock role access-controller accounts and, for bypass proposals, the execute fee payer.

FieldRequiredWhen used
proposerRoleAccessControlleryesschedule conversion
cancellerRoleAccessControlleryescancel conversion
bypasserRoleAccessControlleryesbypass conversion
executePayernobypass only — account that pays (and therefore signs) the outer MCM execute transaction

Example Solana chainMetadata entry:

"5013781088424303360": {
"startingOpCount": 0,
"mcmAddress": "<programId>.<seed>",
"additionalFields": {
"proposerRoleAccessController": "...",
"cancellerRoleAccessController": "...",
"bypasserRoleAccessController": "...",
"executePayer": "<base58 execute-payer pubkey>"
}
}

executePayer​

When the execute payer also appears in a bypass operation's remaining_accounts (for example as a BPF upgrade spill / close recipient), the Solana runtime always presents the fee payer as IsSigner=true at execution time. Off-chain conversion otherwise defaults remaining accounts to non-signer. Without recording executePayer in chain metadata, the Merkle leaf hashed off-chain does not match on-chain proof verification and execution fails with ProofCannotBeVerified.

When to set it: Solana bypass proposals where the fee-payer pubkey is listed as a writable remaining account. Omit for schedule/cancel; the converter ignores executePayer for non-bypass actions.

Go helper: AdditionalFieldsMetadata.WithExecutePayer(pk) in sdk/solana/chain_metadata.go.

Reference scenario: e2e/tests/solana/timelock_bypass_payer_collision.go.

Deriving cancel/bypass metadata​

TimelockProposal.DeriveCancellationProposal and DeriveBypassProposal replace each chain's metadata wholesale with what the caller supplies. They never rewrite additionalFields. Each chain family therefore implements sdk.RoleMetadataDeriver (sdk/role_metadata_deriver.go). It derives the metadata a cancel or bypass proposal needs from the schedule proposal's metadata:

deriver, err := chainwrappers.BuildRoleMetadataDeriver(chainAccessor, selector)
md, err := deriver.DeriveRoleMetadata(ctx, selector, scheduleMetadata, types.TimelockActionCancel, resolver)
md.StartingOpCount = opCountForRole // read with an inspector for the target action

The caller supplies an sdk.RoleAddressResolver: given an sdk.RoleAddressRequest (chain selector, timelock role and the schedule proposal's mcmAddress), it returns the MCM address that holds that role, for example from a datastore or from explicit addresses. The source MCM address lets a resolver pick the role MCM deployed alongside it, such as the one with the same datastore qualifier. Each family decides whether it needs to call the resolver:

FamilyBehaviour
EVM, TON, StellarResolves the role's MCM address; additionalFields unchanged
SolanaResolves the role's MCM address; for bypass, sets executePayer (from ChainAccessor.SolanaSigner)
AptosKeeps mcmAddress; sets role, preserving mcmsType
SuiKeeps mcmAddress; sets role, preserving the object IDs
CantonKeeps mcmAddress; rewrites the multisigId role suffix (...-proposer → ...-canceller/...-bypasser)