Skip to main content

Integrating a New Chain Family

This guide provides a comprehensive overview for engineers adding MCMS (Many Chain Multi-Sig) support to a new blockchain family.

Introduction

What is MCMS?

MCMS is a cross-chain governance system that allows for secure execution of operations across multiple blockchain families. The chain family SDK provides the necessary abstractions to integrate MCMS with different blockchain ecosystems.

Integration Overview

Adding support for a new chain family involves:

  1. Implementing core SDK interfaces for contract interaction
  2. Creating chain-specific encoders and decoders
  3. Writing comprehensive unit and end-to-end tests
  4. Optionally implementing simulation and timelock functionality

Prerequisites

Before starting, you should have:

  • Strong understanding of Go programming
  • Deep knowledge of the target blockchain's architecture, transaction format, and smart contract capabilities
  • Familiarity with MCMS core concepts (see Key Concepts)
  • Understanding of the target chain's RPC interfaces and client libraries

SDK Interfaces Overview

All chain family integrations must implement interfaces defined in the /sdk folder. Here's a complete overview:

InterfaceStatusPurposeDefinition
ExecutorRequiredExecute MCMS operations on-chainexecutor.go
InspectorRequiredQuery MCMS contract stateinspector.go
EncoderRequiredHash operations and metadataencoder.go
ConfigTransformerRequiredConvert between chain-agnostic and chain-specific configsconfig_transformer.go
ConfigurerRequiredUpdate MCMS contract configurationconfigurer.go
DecoderOptionalDecode transaction data for human readabilitydecoder.go
SimulatorOptionalSimulate transactions before executionsimulator.go
TimelockExecutorRequiredExecute timelock operationstimelock_executor.go
TimelockInspectorRequiredQuery timelock contract statetimelock_inspector.go
TimelockConverterRequiredConvert batch operations to timelock operationstimelock_converter.go

Required Interfaces

Executor Interface

The Executor is the primary interface for executing MCMS operations on your chain. It embeds both Inspector and Encoder interfaces.

Interface Definition: sdk/executor.go

Key Methods:

  • ExecuteOperation(ctx, metadata, nonce, proof, op) - Executes a single MCMS operation
  • SetRoot(ctx, metadata, proof, root, validUntil, signatures) - Sets a new Merkle root with signatures

Implementation Examples:

Key Considerations:

  • Return types.TransactionResult with transaction hash and chain family
  • Handle chain-specific transaction signing and submission
  • Properly format proofs and signatures for your chain

Inspector Interface

The Inspector queries on-chain state of MCMS contracts.

Interface Definition: sdk/inspector.go

Key Methods:

  • GetConfig(ctx, mcmAddr) - Retrieves current MCMS configuration
  • GetOpCount(ctx, mcmAddr) - Gets the current operation count
  • GetRoot(ctx, mcmAddr) - Returns the current Merkle root and valid until timestamp
  • GetRootMetadata(ctx, mcmAddr) - Gets metadata for the current root

Implementation Examples:

Key Considerations:

  • Use chain-specific RPC clients to query contract state
  • Parse chain-specific data structures into common types.Config format
  • Handle chain-specific address formats

Encoder Interface

The Encoder creates chain-specific hashes for operations and metadata.

Interface Definition: sdk/encoder.go

Key Methods:

  • HashOperation(opCount, metadata, op) - Creates a hash of an operation
  • HashMetadata(metadata) - Creates a hash of chain metadata

Implementation Examples:

Key Considerations:

  • Match the exact hashing algorithm used by your on-chain contract
  • Ensure byte-level encoding matches contract expectations
  • Test hash compatibility with contract thoroughly

ConfigTransformer Interface

The ConfigTransformer converts between chain-agnostic types.Config and chain-specific configuration structures.

Interface Definition: sdk/config_transformer.go

Key Methods:

  • ToChainConfig(cfg, chainSpecificConfig) - Converts to chain-specific format
  • ToConfig(onchainConfig) - Converts from chain-specific format to common format

Implementation Examples:

Key Considerations:

  • Handle address format conversions between chain-specific and common formats
  • Preserve all configuration fields during round-trip conversion
  • Support chain-specific configuration parameters via the generic C type parameter

Configurer Interface

The Configurer updates MCMS contract configuration on-chain.

Interface Definition: sdk/configurer.go

Key Methods:

  • SetConfig(ctx, mcmAddr, cfg, clearRoot) - Updates the MCMS configuration

Implementation Examples:

Key Considerations:

  • Requires administrative/owner privileges on the MCMS contract
  • Handle the clearRoot parameter to optionally clear the current root
  • Return transaction result with hash and status

Optional Interfaces

Decoder Interface

Implement Decoder if your chain supports decoding transaction calldata into human-readable format.

Interface Definition: sdk/decoder.go

Key Methods:

  • Decode(op, contractInterfaces) - Decodes transaction data using contract interfaces

Return Type: sdk/decoded_operation.go

Implementations:

When to Implement:

  • Your chain has a standardized interface definition language (like ABI)
  • Transaction calldata can be parsed into method names and arguments
  • Useful for debugging and operation visualization

Simulator Interface

Implement Simulator if your chain supports transaction simulation/dry-run before execution.

Interface Definition: sdk/simulator.go

Key Methods:

  • SimulateSetRoot(ctx, originCaller, metadata, proof, root, validUntil, signatures) - Simulates setting a root
  • SimulateOperation(ctx, metadata, operation) - Simulates executing an operation

Implementations:

When to Implement:

  • Your chain's RPC supports simulation/dry-run capabilities
  • Allows validation before actual on-chain execution
  • Helps detect errors early without spending gas

Timelock Interfaces

If your chain supports timelock functionality (scheduled/delayed execution), implement these interfaces:

TimelockExecutor Interface

Interface Definition: sdk/timelock_executor.go

Embeds TimelockInspector and adds the Execute method for executing scheduled operations.

Implementations:

TimelockInspector Interface

Interface Definition: sdk/timelock_inspector.go

Queries timelock contract state including roles, operation status, and minimum delay.

Key Methods:

  • GetProposers, GetExecutors, GetBypassers, GetCancellers - Query role members
  • IsOperation, IsOperationPending, IsOperationReady, IsOperationDone - Check operation status
  • GetMinDelay - Get minimum timelock delay

Implementations:

TimelockConverter Interface

Interface Definition: sdk/timelock_converter.go

Converts batch operations into chain-specific timelock operations.

Key Methods:

  • ConvertBatchToChainOperations - Converts batch to chain operations with timelock scheduling

Implementations:

Implementation Guidelines

Package Structure

Create a new package under /sdk/<chain-family>/ with the following typical structure:

sdk/
└── <chain-family>/
├── encoder.go # Encoder implementation
├── encoder_test.go # Encoder tests
├── executor.go # Executor implementation
├── executor_test.go # Executor tests
├── inspector.go # Inspector implementation
├── inspector_test.go # Inspector tests
├── configurer.go # Configurer implementation
├── configurer_test.go # Configurer tests
├── config_transformer.go # ConfigTransformer implementation
├── config_transformer_test.go # ConfigTransformer tests
├── decoder.go # Decoder implementation (optional)
├── decoder_test.go # Decoder tests
├── simulator.go # Simulator implementation (optional)
├── simulator_test.go # Simulator tests
├── timelock_executor.go # TimelockExecutor (if supported)
├── timelock_executor_test.go
├── timelock_inspector.go # TimelockInspector (if supported)
├── timelock_inspector_test.go
├── timelock_converter.go # TimelockConverter (if supported)
├── timelock_converter_test.go
├── transaction.go # Transaction utilities
├── transaction_test.go
├── utils.go # Chain-specific helpers
├── utils_test.go
└── mocks/ # Generated mocks for testing
└── ...

Reference: See sdk/evm/ for a complete example.

Chain-Specific Considerations

Transaction Formatting

  • Each chain has unique transaction structures (e.g., EVM calldata, Solana instructions, Move entry functions)
  • Ensure your implementation correctly formats transactions for contract calls
  • Handle nonce/sequence numbers according to chain requirements

Address Encoding

  • Implement conversion between chain-specific address formats (hex, base58, bech32, etc.) and common representations
  • Store addresses in a consistent format within types.Config
  • See types/chain_metadata.go for metadata requirements

Signature Handling

  • Different chains use different signature schemes (ECDSA, EdDSA, etc.)
  • Ensure signature verification matches on-chain expectations
  • Handle signature serialization correctly (r,s,v format vs compact format, etc.)

Chain Metadata Requirements

  • Define required metadata fields in types.ChainMetadata
  • Include MCMS contract address and chain selector
  • Add chain-specific parameters as needed

Additional Fields in Operations

  • Use types.Transaction.AdditionalFields (JSON) for chain-specific data
  • Examples: Solana account lists, Aptos type arguments, Sui object references
  • Document expected structure for your chain

Error Handling

Use the /sdk/errors/ package for standardized error handling:

Reference: sdk/errors/errors.go

Common Error Patterns:

  • Wrap errors with context using fmt.Errorf("operation failed: %w", err)
  • Return specific errors for common failure cases (insufficient signatures, invalid proof, etc.)
  • Use typed errors for cases that callers may need to handle specifically

Testing Requirements

Unit Tests

Each interface implementation needs a corresponding _test.go file with comprehensive coverage (>80%). Test all public methods with both success and failure cases using table-driven tests. Mock external dependencies (RPC clients, contracts) in sdk/<chain>/mocks/.

Test Examples:

E2E Tests

Create test suite under /e2e/tests/<chain-family>/ covering:

Test CategoryExampleKey Coverage
Config Managementsolana/set_config.goSet/update config, retrieve and verify, clearRoot flag
Root Operationssolana/set_root.goSet root with signatures, quorum requirements, expiration
Operation Executionsolana/execute.goExecute with valid proof, verify effects, test invalid proofs
Contract Inspectionsolana/inspection.goQuery config, op count, root, metadata
Simulation (optional)solana/simulator.goSimulate valid/invalid ops, verify no state changes
Timelock Conversion (optional)solana/timelock_converter.goConvert batch to timelock ops, verify IDs and actions
Timelock Execution (optional)solana/timelock_execution.goSchedule with delay, execute after delay, predecessors
Timelock Inspection (optional)solana/timelock_inspection.goQuery roles, operation status, minimum delay
Timelock Cancellation (optional)aptos/timelock_cancel.goCancel pending ops, verify cancellation

Test Suite Setup:

  1. Create e2e/config.<chain>.toml (example)
  2. Update e2e/tests/setup.go with blockchain node and RPC clients
  3. Add suite to e2e/tests/runner_test.go
  4. Create helpers in common.go (example)

Reference Implementations

When implementing your integration, refer to these existing implementations:

  1. EVM: sdk/evm/ - Most mature, includes all features
  2. Solana: sdk/solana/ - Excellent example of chain-specific complexity
  3. Aptos: sdk/aptos/ - Move-based chain without simulation
  4. Sui: sdk/sui/ - Recent addition with good patterns