Jesse A. Green / notebook

Union — Administrator & Developer Guide

Generated May 2026 from unionlabs/union at 6f56186

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Union is a zero-knowledge IBC infrastructure that enables trust-minimized cross-chain message passing, asset transfers, and DeFi across heterogeneous blockchains including Cosmos-SDK chains, EVM networks, Sui, Starknet, and Movement/Aptos. This guide covers architecture, deployment, configuration, security, operations, and developer internals for operators, architects, and contributors working with the Union protocol stack.


1. Architecture Overview

What the system does

Union implements the Inter-Blockchain Communication (IBC) protocol with a ZK-proof-based consensus verification layer. Instead of relying on multisig committees or trusted third parties, Union uses BLS aggregate signatures with Groth16 zero-knowledge proofs to verify cross-chain state transitions. The core chain (uniond) runs CometBFT consensus with BN254 BLS curve support (CometBLS), enabling efficient on-chain proof verification.

The relayer system (Voyager) orchestrates IBC packet lifecycle across chains — indexing events, updating light clients, constructing proofs, batching transactions, and relaying packets. The ZK proving service (Galois) generates Groth16 proofs that attest to validator set transitions, allowing any chain to verify Union consensus without replaying the full validator set logic.

Smart contract deployments span CosmWasm (for Cosmos chains), Solidity (for EVM chains), Cairo (for Starknet), and Move (for Aptos/Sui), each implementing the IBC core protocol and the ZKGM (UCS03) token transfer specification natively in their respective VMs.

Architecture diagram

┌─────────────────────────────────────────────────────────────┐
│                        Union Chain                           │
│  ┌─────────┐  ┌──────────┐  ┌───────────┐  ┌────────────┐  │
│  │ uniond  │  │ CometBLS │  │ CosmWasm  │  │ IBC Core   │  │
│  │ (Go)    │  │ (BN254)  │  │ Contracts │  │ (ibc-go v8)│  │
│  └────┬────┘  └────┬─────┘  └─────┬─────┘  └─────┬──────┘  │
│       └────────────┼──────────────┼───────────────┘          │
└─────────────────────┼──────────────┼─────────────────────────┘
                      │              │
         ┌────────────┘              │
         ▼                           ▼
┌─────────────────┐        ┌──────────────────┐
│    Galois (ZK)  │        │     Voyager      │
│  ┌────────────┐ │        │  ┌────────────┐  │
│  │ galoisd    │ │        │  │ Relayer    │  │
│  │ (Go/Gnark) │ │        │  │ (Rust)     │  │
│  │ gRPC API   │ │        │  │ PostgreSQL │  │
│  │ BLS12-381  │ │        │  │ Plugin Sys │  │
│  │ BN254      │ │        │  └────────────┘  │
│  └────────────┘ │        │                  │
└─────────────────┘        │  Modules:        │
                           │  ├── client      │
┌─────────────────┐        │  ├── finality    │
│  unionvisor     │        │  ├── proof       │
│  (Rust)         │        │  ├── state       │
│  Process Mgr    │        │  └── bootstrap   │
└─────────────────┘        │                  │
                           │  Plugins:        │
                           │  ├── event-source│
                           │  ├── client-upd  │
                           │  ├── transaction │
                           │  ├── packet-*    │
                           │  └── zkgm-filter │
                           └──────────────────┘
                                    │
            ┌───────────┬───────────┼───────────┬───────────┐
            ▼           ▼           ▼           ▼           ▼
      ┌──────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐ ┌──────┐
      │ Ethereum │ │Arbitrum │ │  Base  │ │  BSC    │ │ Sui  │
      │ Solidity │ │ Solidity│ │Solidity│ │Solidity │ │ Move │
      │ IBC Core │ │ IBC Core│ │IBC Core│ │IBC Core │ │ IBC  │
      └──────────┘ └─────────┘ └────────┘ └─────────┘ └──────┘

Component inventory

Component Path Language Role
uniond uniond/ Go Cosmos-SDK chain node with CometBLS consensus, custom BLS staking, IBC-go v8
Voyager voyager/ Rust Modular IBC relayer with PostgreSQL-backed queue and plugin architecture
Galois galoisd/ Go ZK proving service (Gnark circuits for BLS12-381 + BN254 Groth16 proofs)
unionvisor unionvisor/ Rust Process supervisor for uniond binary upgrades (similar to cosmovisor)
CosmWasm contracts cosmwasm/ Rust IBC core, light clients, ZKGM (UCS03), token minters, access management
EVM contracts evm/contracts/ Solidity IBC core handler, client registry, channel management, ZKGM app
Cairo contracts cairo/ Cairo CometBLS light client, ZK verifier, ZKGM, and IBC for Starknet
Move modules aptos/ Move IBC and ZKGM implementations for Aptos
Sui modules sui/ Move (Sui) IBC, ZKGM, escrow vaults, ping-pong for Sui
TypeScript SDK ts-sdk/ TypeScript Cross-chain transfer intents using Effect-TS
Web UI (app2) app2/ SvelteKit Bridge interface at app.union.build
MPC ceremony ceremony/, mpc/ Rust/TS Multi-party computation for trusted setup
Lib crates lib/ Rust 80+ shared libraries (types, verifiers, encodings, primitives)
Tools tools/ Rust Utilities: u (swiss-army CLI), tidy, move-bindgen, devnet-utils
Drip drip/ Rust Faucet service for testnet token distribution

Technology stack

Layer Technology
Consensus CometBFT with BN254 BLS aggregate signatures (CometBLS)
Chain framework Cosmos SDK v0.50+ with depinject
IBC ibc-go v8, custom IBC-Union spec
ZK proving Gnark (Go), Groth16 over BN254, BLS12-381 pairing
Relayer runtime Rust async (tokio), PostgreSQL queue (pg-queue), JSON-RPC plugins
Smart contracts CosmWasm, Solidity (Foundry), Cairo, Move
Build system Nix flakes (reproducible), Cargo workspace (200+ crates), Go modules
Frontend SvelteKit (app2), Astro (docs site)
Monitoring OpenTelemetry (Voyager metrics), tracing crate

Data flow: cross-chain transfer

1. User submits transfer intent (Web UI / TS SDK / CLI)
       │
2. Source chain: ZKGM contract escrows tokens, emits SendPacket event
       │
3. Voyager event-source plugin indexes the event
       │
4. Voyager client-update plugin fetches latest header from source chain
       │
5. Galois generates Groth16 ZK proof of source chain consensus
       │
6. Voyager submits UpdateClient tx on destination chain with ZK proof
       │
7. Voyager constructs membership proof for the packet commitment
       │
8. Voyager submits PacketRecv tx on destination chain
       │
9. Destination chain ZKGM contract verifies proof, mints/releases tokens
       │
10. Voyager relays acknowledgement back to source chain

Note

The ZK proof generation step (5) is what distinguishes Union from traditional IBC relaying. Rather than submitting full validator set and signature data, only a compact Groth16 proof is verified on-chain, reducing gas costs significantly on EVM chains.


2. System Requirements

Hardware requirements

Component CPU Memory Storage Network
uniond (validator) 4+ cores 16 GB+ 500 GB+ SSD (NVMe recommended) 100 Mbps+, low latency
uniond (full node) 2+ cores 8 GB+ 500 GB+ SSD 50 Mbps+
Voyager 2+ cores 4 GB+ 50 GB (PostgreSQL) Stable connections to all relayed chains
Galois 8+ cores 32 GB+ 10 GB Low latency to uniond
unionvisor Minimal (wraps uniond) Minimal Same as uniond Same as uniond

Important

Galois ZK proof generation is CPU-intensive. The example-prove command with BN254 curves can take significant time. Production deployments should run Galois on dedicated hardware with high single-thread performance.

Software requirements

Dependency Version Purpose
Nix 2.18+ with flakes Reproducible builds for all components
Go 1.22+ uniond, galoisd
Rust nightly (via rust-toolchain.toml) Voyager, unionvisor, CosmWasm contracts, all lib crates
PostgreSQL 14+ Voyager queue backend
Node.js 20+ TypeScript SDK, web frontend
Foundry Latest EVM contract compilation and testing
Docker 24+ Optional: containerized deployment

Supported chain connections (production)

Chain Chain ID IBC Interface Light Client Type
Union (mainnet) union-1 IBC-Union CometBLS
Ethereum 1 EVM (Solidity) Ethereum sync protocol
Arbitrum 42161 EVM (Solidity) Arbitrum L2 (state-lens)
Base 8453 EVM (Solidity) Base L2 (state-lens)
Berachain 80094 EVM (Solidity) Berachain (ethermint-based)
Bob 60808 EVM (Solidity) Bob L2 (state-lens)
BSC 56 EVM (Solidity) Parlia consensus
Corn 21000000 EVM (Solidity) Corn L2
Babylon bbn-1 CosmWasm Tendermint
Osmosis osmosis-1 CosmWasm Tendermint
Sei 1329 EVM/CosmWasm Sei consensus
Starknet — Cairo Starknet
Aptos — Move Movement/Aptos
Sui — Move (Sui) Sui consensus

Note

Channel configurations, gas fees, and SLA targets for each chain connection are defined in deployments/channels.json. SLA values use ISO 8601 duration format (e.g., PT3H = 3 hours, PT20M = 20 minutes).


3. Configuration Reference

uniond configuration

uniond uses standard Cosmos SDK configuration files generated via uniond init:

File Location Purpose
config.toml ~/.union/config/ CometBFT consensus, P2P, RPC settings
app.toml ~/.union/config/ Application-level settings (API, gRPC, state sync, pruning)
genesis.json ~/.union/config/ Chain genesis state
client.toml ~/.union/config/ CLI client defaults

Key environment variables:

Variable Type Default Description
MEMPOOL_PRIORITY string "" Mempool priority mode (read in app.go)

The address prefix is union (Bech32). The chain uses standard Cosmos SDK v0.50 configuration plus:

  • Custom BLS staking module (uniond/x/staking/) — validators must provide proof-of-possession for BLS keys
  • CosmWasm — enabled with stargate queries
  • IBC-go v8 — full IBC handler with CometBLS light client support
  • Skip feemarket module — dynamic fee market for gas pricing

Important

Validators must use the create-union-validator command instead of the standard create-validator. This command requires a proof_of_possession argument — a non-prefixed hex-encoded BLS proof binding the validator's consensus key to their operator address.

Voyager configuration

Voyager uses a JSON/JSONC configuration file specified via --config-file-path (-c):

{
  "$schema": "...",
  "chains": {
    "union-1": { ... },
    "ethereum.1": { ... }
  },
  "plugins": [ ... ],
  "queue": {
    "database_url": "postgresql://user:pass@localhost/voyager",
    "max_connections": 10,
    "min_connections": 1,
    "idle_timeout": null,
    "max_lifetime": null,
    "optimize_batch_limit": null,
    "retryable_error_expo_backoff_max": 3600.0,
    "retryable_error_expo_backoff_multiplier": 2.0,
    "vacuum_on_boot": false
  }
}

Global CLI flags:

Flag Short Default Description
--config-file-path -c Required Path to config file (JSON or JSONC)
--log-format -l text Log output format: text or json
--stack-size — 2097152 (2 MB) Thread stack size in bytes

Queue configuration (PgQueueConfig):

Key Type Default Description
database_url string Required PostgreSQL connection string
max_connections u32 10 Connection pool maximum
min_connections u32 1 Connection pool minimum
idle_timeout duration null Connection idle timeout
max_lifetime duration null Connection max lifetime
optimize_batch_limit i64 null Batch optimization limit
retryable_error_expo_backoff_max f64 3600.0 Max backoff seconds for retryable errors
retryable_error_expo_backoff_multiplier f64 2.0 Exponential backoff multiplier
vacuum_on_boot bool false Run VACUUM on queue tables at startup

Environment variables used by Voyager plugins:

Variable Description
RUST_LOG_FORMAT Override log format at runtime (used in lib/voyager-plugin)

unionvisor configuration

unionvisor reads its configuration from CLI arguments and environment variables:

Parameter Env Var Default Description
--root UNIONVISOR_ROOT Required Path to unionvisor root directory (contains chain data)
--bundle / -b UNIONVISOR_BUNDLE Required Path to bundle directory containing versioned binaries
--poll-interval UNIONVISOR_POLL_INTERVAL — Interval to poll for upgrade signals

Bundle structure (meta.json):

{
  "binary_name": "uniond",
  "fallback_version": "genesis",
  "versions_directory": "versions"
}
bundle/
├── genesis.json
├── meta.json
└── versions/
    ├── genesis/
    │   └── uniond
    ├── v0.8.0/
    │   └── uniond
    └── v0.9.0/
        └── uniond

Warning

Each version binary is validated by calling --help before activation. If the binary is not executable or does not respond to --help, unionvisor will report a ValidateVersionPathError. Ensure binaries have execute permissions (chmod +x) before deployment.

Galois configuration

galoisd is configured entirely via CLI flags:

Command Description
galoisd serve Start gRPC prover service
galoisd serve-bls12381 Start BLS12-381 specific prover
galoisd example-prove [uri] [nb_validators] Simulation proof request
galoisd example-verify [uri] Verify a proof
galoisd export-vk Export verification key
galoisd gen-contract Generate on-chain verifier contract

gRPC connection flags:

Flag Values Description
--tls yes/true/1 or anything else Enable TLS for gRPC connection

The gRPC API is defined in galoisd/proto/api/v3/ and exposes the UnionProverAPI service. Client connections have a 1-hour timeout by default.

Tip

Use galoisd gen-contract to generate Solidity verifier contracts that can be deployed on EVM chains. This is the recommended approach for adding Union ZK verification to new EVM deployments.


4. Deployment & Scaling

uniond deployment

From Nix (recommended):

nix build .#uniond
./result/bin/uniond init <moniker> --chain-id union-1

From source:

cd uniond
go build -o uniond ./cmd/uniond
./uniond init <moniker> --chain-id union-1

Validator registration:

uniond tx union-staking create-union-validator \
  /path/to/validator.json \
  <proof_of_possession_hex> \
  --from <key_name> \
  --chain-id union-1

Caution

The proof_of_possession must be a non-prefixed hex-encoded string. Prefixed hex (0x...) will fail with: failed to decode proof of possession. The value must be a non-prefixed hex-encoded string.

uniond ante handler chain (18 decorators):

The transaction processing pipeline in uniond/app/ante.go chains these decorators in order:

  1. SetUpContextDecorator — initialize context (must be first)
  2. LimitSimulationGasDecorator — enforce CosmWasm simulation gas limits
  3. CountTXDecorator — CosmWasm TX counter
  4. TxContractsDecorator — CosmWasm contract validation
  5. GasRegisterDecorator — CosmWasm gas registration
  6. CircuitBreakerDecorator — circuit breaker for emergency halts
  7. ExtensionOptionsDecorator — validate extension options
  8. ValidateBasicDecorator — basic tx validation
  9. TxTimeoutHeightDecorator — block height timeout
  10. ValidateMemoDecorator — memo validation
  11. ConsumeGasForTxSizeDecorator — charge gas for tx size
  12. DeductFeeDecorator — deduct transaction fees
  13. SetPubKeyDecorator — set public keys (must precede sig verification)
  14. ValidateSigCountDecorator — validate signature count
  15. SigGasConsumeDecorator — consume gas for signatures
  16. SigVerificationDecorator — verify transaction signatures
  17. IncrementSequenceDecorator — increment account sequence
  18. RedundantRelayDecorator — IBC redundant relay prevention
  19. FeeMarketCheckDecorator — Skip feemarket dynamic pricing check

unionvisor deployment

unionvisor wraps uniond to handle chain upgrades automatically:

# Initialize unionvisor with a bundle
unionvisor --root /data/union --bundle /opt/union-bundle init

# Run uniond under unionvisor supervision
unionvisor --root /data/union --bundle /opt/union-bundle run

# Manually set the active uniond version
unionvisor --root /data/union --bundle /opt/union-bundle set-uniond-version v0.9.0

# Call uniond through unionvisor
unionvisor --root /data/union --bundle /opt/union-bundle call -- status

Note

unionvisor monitors for on-chain upgrade signals and automatically switches to the new binary version. The bundle must contain the target version binary before the upgrade height is reached.

Voyager deployment

# Build Voyager
cargo build --release -p voyager

# Print config schema for editor support
voyager -c config.json config schema > voyager-schema.json

# Print default config
voyager -c config.json config default

# Start the relayer
voyager -c config.json start

PostgreSQL setup:

CREATE DATABASE voyager;
CREATE USER voyager WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE voyager TO voyager;

The queue automatically creates its schema on first run. The schema uses:

  • id SERIAL AUTO INCREMENT — message ordering
  • status 0..2 — message state (pending/processing/done)
  • item JSONB — serialized operation
  • error TEXT — failure details

Warning

The PgQueue is designed for ~1k items/sec throughput. For high-volume relaying across many chains, monitor PostgreSQL performance and adjust max_connections accordingly. Consider running vacuum_on_boot: true for long-running instances.

Galois deployment

# Build galoisd
cd galoisd
go build -o galoisd ./cmd/galoisd

# Start the prover service
galoisd serve --tls yes

For MPC ceremony setup:

# Coordinator
cargo run --release -p mpc-coordinator -- --format json <subcommand>

# Client
cargo run --release -p mpc-client

Scaling considerations

Component Scaling Strategy
uniond Horizontal: add more validator nodes. Vertical: increase CPU/RAM for faster block processing
Voyager One instance per relaying path. Multiple instances share PostgreSQL with separate queue tables
Galois CPU-bound: scale vertically (faster cores) or run multiple instances behind load balancer
PostgreSQL Standard PostgreSQL scaling (connection pooling, read replicas for monitoring)

5. Operations

uniond operations

Key commands:

# Check node status
uniond status

# Query blockchain state
uniond query <module> <query>

# Submit transactions
uniond tx <module> <msg> --from <key>

# Export state
uniond export

# Prove BLS key possession (for validator setup)
uniond prove-possession <private_key>

# Multi-node testnet setup
uniond multi-node

Upgrade workflow:

  1. Governance proposal passes with upgrade height
  2. unionvisor detects upgrade signal at the specified height
  3. unionvisor stops current uniond process
  4. unionvisor validates new binary version from bundle
  5. unionvisor starts new uniond version
  6. Node resumes consensus with upgraded binary

Upgrade history (from source):

Version Module Path
v1.1.0 Upgrade handler uniond/app/upgrades/v1_1_0/
v1.2.0 Upgrade handler uniond/app/upgrades/v1_2_0/

Tip

Run uniond under unionvisor in production. This eliminates manual intervention during chain upgrades and provides automatic rollback if a version fails validation.

Voyager operations

Queue management:

# View queue statistics
voyager -c config.json queue stats

# Enqueue a new operation
voyager -c config.json queue enqueue '<json_op>'

# Truncate specific queue tables
voyager -c config.json queue truncate --queue --done --failed

# Vacuum queue tables
voyager -c config.json queue vacuum --queue --done --failed --optimize

# Query failed messages
voyager -c config.json queue query-failed --page 1 --per-page 50

Index operations:

# Start indexing events from latest height
voyager -c config.json index <chain_id>

# Index from a specific height
voyager -c config.json index <chain_id> --from <height>

# Index a specific block range
voyager -c config.json index <chain_id> --from <start> --to <end>

# Index a single block
voyager -c config.json index <chain_id> --exact <height>

# Auto-enqueue the index op
voyager -c config.json index <chain_id> --enqueue

Plugin operations:

# List loaded plugins
voyager -c config.json plugin list

# Check plugin interest
voyager -c config.json plugin interest

# Get plugin info
voyager -c config.json plugin info

# Call plugin directly
voyager -c config.json plugin call <args>

RPC operations:

# Query client state
voyager -c config.json rpc --rpc-url <url> client-state <chain_id> <client_id>

# Query consensus state
voyager -c config.json rpc --rpc-url <url> consensus-state <chain_id> <client_id> <height>

# Get latest height
voyager -c config.json rpc --rpc-url <url> latest-height <chain_id>

# Query IBC state/proof
voyager -c config.json rpc --rpc-url <url> ibc-state <chain_id> <path>
voyager -c config.json rpc --rpc-url <url> ibc-proof <chain_id> <path> <height>

# Create client
voyager -c config.json msg create-client <args>

Note

The queue subcommand can be aliased as q. The enqueue subcommand can be aliased as e. The stats subcommand can be aliased as s.

Monitoring

Voyager OpenTelemetry metrics:

Voyager emits OpenTelemetry metrics through the opentelemetry crate. Key metrics from the cache and queue systems:

Metric Type Description
cache.state.size Gauge Current state cache size
cache.state.hit Counter State cache hits
cache.state.miss Counter State cache misses
cache.client_info.size Gauge Client info cache size
cache.client_info.hit Counter Client info cache hits
cache.client_info.miss Counter Client info cache misses
chain.latest_height Gauge Latest known height per chain

Galois health endpoint:

GET /health

This is the only HTTP endpoint exposed by galoisd (defined in galoisd/cmd/galoisd/cmd/query_stats_health.go).

Tip

Configure alerting on cache.state.miss rate increases — a spike may indicate chain RPC issues or configuration problems that prevent Voyager from reading on-chain state efficiently.

Log configuration

Component Log Framework Configuration
uniond Cosmos SDK logger --log_level flag, config.toml log settings
Voyager tracing crate RUST_LOG env var, --log-format flag (text/json)
galoisd Go log package Standard Go logging
unionvisor tracing crate RUST_LOG env var

6. Performance

Voyager queue tuning

The PostgreSQL-backed queue (lib/pg-queue) is the performance-critical path for Voyager:

// Default configurations from source
max_connections: 10,
min_connections: 1,
retryable_error_expo_backoff_max: 3600.0,  // 1 hour max
retryable_error_expo_backoff_multiplier: 2.0,

Optimization strategies:

  1. Connection pool sizing — set max_connections to match expected concurrent operations. For single-chain relaying, 5-10 is sufficient. For multi-chain, increase proportionally.

  2. Batch optimization — set optimize_batch_limit to group related operations for fewer database round-trips.

  3. Vacuum policy — enable vacuum_on_boot: true for long-running instances to reclaim storage from processed messages. For active relayers, schedule periodic queue vacuum operations.

  4. Backoff tuning — reduce retryable_error_expo_backoff_max if you need faster retry cycles, but be cautious of overwhelming failing endpoints.

Voyager caching

The Voyager cache system (lib/voyager-core/src/cache.rs) uses the Moka LRU cache with configurable TTLs:

Cache Capacity TTL Purpose
State cache Configurable Configurable time_to_live + time_to_idle On-chain state lookups
Client info cache Same as state ~1000 years (effectively never expires) Immutable client metadata
Latest height 100 entries ~1000 years Per-chain latest heights (updated externally)
Latest timestamp 100 entries ~1000 years Per-chain latest timestamps

Note

The client info cache uses an extremely long TTL because client info is assumed to be immutable once created. This is a valid assumption for IBC client types, which are registered once and never change their fundamental properties.

Engine processing loop

The Voyager VM engine (lib/voyager-vm/src/engine.rs) processes operations in a spin loop with a 10ms sleep between iterations to prevent 100% CPU usage:

sleep(Duration::from_millis(10)).then(|()| {
    self.queue.process(self.filter, async |op, id| { ... })
})

This provides ~100 operations/second baseline throughput per engine instance.

Channel SLA targets

Production channels define forward and reverse SLA targets in deployments/channels.json:

Route Forward SLA Reverse SLA
Union ↔ Arbitrum 3 hours 20 minutes
Union ↔ Base 3 hours 20 minutes
Union ↔ Sei 20 minutes 20 minutes
Babylon ↔ Osmosis 20 minutes 20 minutes
Berachain ↔ Union 15 hours 20 minutes
Corn ↔ Babylon 29 hours 20 minutes

Warning

SLA targets vary significantly by chain. Routes involving Ethereum L1 finality have longer forward SLAs due to the 12-minute slot times and finalization delay. Monitor relay latency against these targets to detect degradation.


7. Security

Consensus security: CometBLS

Union's consensus uses BN254 BLS aggregate signatures, allowing the entire validator set's signatures to be aggregated into a single proof. The security model:

  • Validator key management — validators use BN254 BLS keys (not Ed25519). Key possession is proven via prove-possession command.
  • ZK verification — Groth16 proofs attest to ≥2/3 voting power signing a block. Proofs are verified on destination chains without accessing the full validator set.
  • Trusted setup — the MPC ceremony (ceremony/, mpc/) generates the proving/verification key pair. The ceremony uses a multi-party computation protocol ensuring security as long as at least one participant is honest.

Caution

The security of all ZK-verified cross-chain operations depends on the integrity of the MPC trusted setup. If the toxic waste (tau) is reconstructed, an attacker could forge proofs. The ceremony is designed so this requires ALL participants to collude.

IBC security model

Light client verification chain:

  1. Source chain light client tracks consensus state on destination
  2. Voyager submits state updates with ZK proofs (for Union) or traditional proofs (for other chains)
  3. Each chain independently verifies proofs before accepting state transitions
  4. No trusted third party in the verification path

Light client types and their verification:

Client Type Verification Method Implementation
CometBLS Groth16 ZK proof of BLS aggregate signature 11-cometbls/ (Go), cosmwasm/lightclient/cometbls/
Ethereum Sync committee signature + execution state proof cosmwasm/lightclient/ethereum/, EVM contracts
Tendermint Standard Tendermint light client verification cosmwasm/lightclient/tendermint/
Parlia (BSC) Parlia consensus proof with MPT state verification cosmwasm/lightclient/parlia/
Arbitrum L1 → L2 state lens (derives security from Ethereum) cosmwasm/lightclient/state-lens-ics23-mpt/
Base L1 → L2 state lens cosmwasm/lightclient/state-lens-ics23-mpt/
Bob L1 → L2 state lens cosmwasm/lightclient/bob/
Berachain Ethermint-based consensus proof cosmwasm/lightclient/berachain/
Movement Movement consensus cosmwasm/lightclient/movement/
Starknet Starknet state verification cosmwasm/lightclient/starknet/
Sui Sui consensus cosmwasm/lightclient/sui/
Attested Attested/witnessed verification cosmwasm/lightclient/attested/
Proof-lens State-lens with proof aggregation cosmwasm/lightclient/proof-lens/

CosmWasm contract security

Access management — contracts use the access-managed crate with role-based access control:

  • Authority enum tracks contract admin/owner
  • EnsureCanCallResult pattern for permission checks
  • Pausable trait for emergency circuit breaking
  • Upgradable trait for controlled contract migrations

IBC core security (cosmwasm/core):

  • Client registration is restricted (MsgRegisterClient)
  • MsgForceUpdateClient exists for emergency situations (governance-gated)
  • Commitment paths use COMMITMENT_MAGIC and COMMITMENT_MAGIC_ACK prefixes to prevent path confusion
  • Non-membership proofs use NON_MEMBERSHIP_COMMITMENT_VALUE sentinel

EVM contract security

EVM IBC contracts (evm/contracts/core/) implement:

  • Client registry (02-client/) — maps client IDs to light client contract addresses
  • Connection management (03-connection/) — IBC connection handshake
  • Channel management (04-channel/) — IBC channel lifecycle
  • Port binding (05-port/) — application module registration
  • Handler (25-handler/) — unified message dispatcher
  • Host (24-host/) — storage layout and commitment paths

ZKGM (UCS03) app (evm/contracts/apps/ucs/03-zkgm/) — handles token escrow, minting, and fungible asset order execution.

Warning

The MsgForceUpdateClient message bypasses normal light client verification and should only be accessible through governance. Misconfiguration of access controls on this message could allow an attacker to set arbitrary consensus states.

Network security

  • uniond — standard Cosmos SDK P2P network with configurable seed nodes, persistent peers, and private peer IDs
  • Voyager — requires outbound RPC/REST connections to all relayed chains. No inbound connections required except for the optional JSON-RPC interface
  • Galois — gRPC service; supports TLS (--tls yes)
  • Inter-component — Voyager communicates with Galois via gRPC, with uniond via CometBFT RPC/REST

8. Developer Internals

Repository structure

unionlabs/union/
├── 11-cometbls/           CometBLS light client (Go, protobuf)
├── aptos/                 Move modules (IBC, UCS03-ZKGM, ping-pong)
├── app2/                  Bridge web UI (SvelteKit)
├── cairo/                 Starknet contracts (CometBLS verifier, IBC, ZKGM)
├── ceremony/              MPC ceremony web interface (SvelteKit)
├── cosmwasm/              CosmWasm contracts
│   ├── access-managed/    Role-based access control
│   ├── access-manager/    Permission management
│   ├── app/               IBC applications (UCS00 ping-pong, UCS03 ZKGM)
│   ├── core/              IBC core handler (contract.rs: main entry point)
│   │   ├── light-client-interface/  Light client query trait
│   │   └── msg/           IBC message types
│   ├── cw-account/        Cross-chain account abstraction
│   ├── cw-escrow-vault/   Token escrow for bridging
│   ├── deployer/          Contract deployment tool
│   ├── gatekeeper/        Access gating
│   ├── lightclient/       Light client implementations (14 types)
│   ├── lst/               Liquid staking token
│   ├── lst-staker/        LST staking operations
│   ├── migrations/        Contract migration scripts
│   ├── multicall/         Batched contract calls
│   ├── pausable/          Emergency pause functionality
│   └── upgradable/        Safe upgrade patterns
├── deployments/           Production channel configs, bridged token mappings
├── devnet-compose/        Docker Compose for local devnet
├── docs/                  Documentation site (Astro)
├── drip/                  Testnet faucet (Rust)
├── e2e/                   End-to-end tests
├── effect-svelte/         Svelte + Effect-TS integration lib
├── evm/                   Solidity contracts
│   ├── contracts/
│   │   ├── apps/ucs/      UCS00 (ping-pong), UCS03 (ZKGM)
│   │   ├── clients/       EVM light client implementations
│   │   ├── core/          IBC core (client, connection, channel, handler, host)
│   │   └── internal/      Shared internal libraries
│   └── tests/             Foundry tests
├── galoisd/               ZK prover service (Go/Gnark)
│   ├── cmd/galoisd/       CLI entry point and commands
│   ├── grpc/              gRPC server and API definitions
│   ├── pkg/               Proving logic (BLS, merkle, lightclient, emulated)
│   └── proto/             Protobuf definitions (v1, v2, v3 API versions)
├── generated/             Auto-generated code (Rust protos, Aptos Move)
├── lib/                   Shared Rust libraries (80+ crates)
│   ├── arbitrum-*/        Arbitrum types, verifier, client
│   ├── beacon-api*/       Ethereum beacon chain API types
│   ├── bob-*/             Bob chain types, verifier, client
│   ├── cometbft-*/        CometBFT types and RPC client
│   ├── cometbls-*/        CometBLS types and Groth16 verifier
│   ├── cosmos-*/          Cosmos SDK client, signer, events
│   ├── cosmwasm-event/    Derive macro for CosmWasm events
│   ├── ethereum-*/        Ethereum types, sync protocol
│   ├── fork-schedules/    Chain fork schedule definitions
│   ├── ics23/             ICS-23 proof verification
│   ├── pg-queue/          PostgreSQL FIFO queue (Voyager backend)
│   ├── voyager-*/         Voyager core, message, VM, SDK, RPC, plugins
│   └── unionlabs*/        Core primitives, encoding, types
├── mpc/                   MPC ceremony infrastructure
│   ├── client/            MPC participant client
│   ├── coordinator/       MPC coordinator service
│   ├── edge/              Supabase edge functions
│   └── shared/            Shared MPC types
├── networks/              Network configurations
│   ├── genesis/           Genesis files (testnet-2 through testnet-10, devnet)
│   ├── services/          Support services (blockscout)
│   └── simulation/        Network simulation configs
├── patches/               Third-party patches
├── sentinel2/             Network monitoring service
├── site/                  Marketing website (Astro)
├── sui/                   Sui Move modules (IBC, ZKGM, vaults, ping-pong, muno)
├── tools/                 Utility tools
│   ├── u/                 Swiss-army CLI (zkgm, cometbft, arbitrum, parlia, codec, vanity)
│   ├── devnet-utils/      Devnet management utilities
│   ├── move-bindgen/      Move binding generator
│   ├── tidy/              Code tidying
│   └── update-deployments/ Deployment config updater
├── ts-sdk/                TypeScript SDK (Effect-TS)
│   ├── src/               Source (aptos, cosmos, evm, sui, ucs03, schema)
│   ├── examples/          23 example files
│   └── dtslint/           Type-level tests
├── ts-sdk-cosmos/         Cosmos-specific TS SDK
├── ts-sdk-evm/            EVM-specific TS SDK
├── ts-sdk-sui/            Sui-specific TS SDK
├── typescript-sdk/        Legacy TypeScript SDK
├── uniond/                Chain node
│   ├── app/               Application wiring (ante.go, app.go, wasm.go, upgrades)
│   ├── cmd/uniond/        CLI entry point and commands
│   ├── proto/             Protobuf definitions (staking, IBC light clients)
│   └── x/staking/         Custom BLS staking module
├── unionvisor/            Process supervisor
│   └── src/               Bundle management, supervisor, CLI
├── versions/              Version tracking
├── voyager/               IBC relayer
│   ├── src/               Core relayer (cli.rs, config, main)
│   ├── modules/           Modular implementations
│   │   ├── client/        Light client modules (15 implementations)
│   │   ├── client-bootstrap/ Client initialization (16 implementations)
│   │   ├── finality/      Finality modules (12 implementations)
│   │   ├── proof/         Proof generation (7 implementations)
│   │   └── state/         State query modules (4: cosmwasm, evm, movement, sui)
│   └── plugins/           Plugin system
│       ├── attestor/      EVM attestation
│       ├── client-update/ Client update handlers (16 chain-specific)
│       ├── event-source/  Event indexing (5: cosmwasm, evm, movement, starknet, sui)
│       ├── packet-*/      Packet processing (batch, filter, index, timeout)
│       ├── periodic-client-update/ Scheduled client updates
│       ├── sui-*/         Sui-specific plugins
│       ├── transaction/   TX submission (4: aptos, cosmos, evm, sui)
│       ├── transaction-batch*/  TX batching
│       └── zkgm-filter/   ZKGM packet filtering
└── zkgm-dev/              ZKGM development playground (SvelteKit)

Voyager plugin architecture

Voyager uses a dynamic plugin system where each module/plugin is a separate Rust binary communicating via JSON-RPC:

Module categories:

Category Purpose Count
client Light client state management 15 (one per chain type)
client-bootstrap Initial client creation 16
finality Finality tracking 12
proof Proof construction 7
state On-chain state queries 4

Plugin categories:

Category Purpose Count
event-source Block/event indexing 5
client-update Light client update logic 16
transaction TX submission to chains 4
packet-* Packet lifecycle (batch, filter, index, timeout) 4
transaction-batch* TX batching optimization 2
attestor EVM attestation 1
periodic-client-update Scheduled updates 1
zkgm-filter ZKGM packet filtering 1
sui-* Sui-specific handling 2

Interest filter system:

Each plugin declares interest in specific operation types. The Voyager VM engine routes operations to plugins based on their declared interest:

pub enum FilterResult<'a> {
    Interest(Interest<'a>),  // Plugin wants this op
    NoInterest,              // Plugin ignores this op
}

Operations matching a plugin's interest are placed in an optimization queue tagged with the plugin's identifier.

IBC-Union specification

Union implements a custom IBC specification (ibc-union-spec) that extends standard IBC with:

  • ClientId, ConnectionId, ChannelId — strongly typed identifiers
  • Batch operations — MsgBatchSend, MsgBatchAcks for multi-packet transactions
  • Intent-based packets — MsgIntentPacketRecv for intent-matching protocols
  • Force update — MsgForceUpdateClient for governance-gated emergency updates
  • Membership proofs — MsgCommitMembershipProof, MsgCommitNonMembershipProof
  • Commitment paths — COMMITMENT_MAGIC (0x01) prefix for packet commitments, COMMITMENT_MAGIC_ACK for ack commitments

ZKGM (UCS03) protocol

The Zero-Knowledge General Message (ZKGM) protocol implements cross-chain token transfers via fungible asset orders:

Intent types (TypeScript SDK):

Intent Source Destination
EvmToEvmIntent EVM chain EVM chain
EvmToCosmosIntent EVM chain Cosmos chain
CosmosToEvmIntent Cosmos chain EVM chain
CosmosToCosmosIntent Cosmos chain Cosmos chain
AptosToCosmosIntent Aptos Cosmos chain
AptosToEvmIntent Aptos EVM chain

Contract implementations:

  • cosmwasm/app/ucs03-zkgm/ — CosmWasm ZKGM with token minter API, solvable intents, pausable, upgradable
  • evm/contracts/apps/ucs/03-zkgm/ — Solidity ZKGM
  • cairo/zkgm/ — Cairo ZKGM for Starknet
  • sui/ucs03_zkgm/ — Sui Move ZKGM
  • aptos/ucs03-zkgm/ — Aptos Move ZKGM

CosmWasm IBC core internals

The IBC core contract (cosmwasm/core/src/contract.rs) manages:

State stores:

Store Purpose
ClientRegistry Maps client types to implementation addresses
ClientTypes Maps client IDs to their types
ClientStates Client state data
ClientConsensusStates Consensus states per client per height
ClientImpls Light client contract addresses
ClientStore Generic client storage
Connections Connection state
Channels Channel state
ChannelOwner Maps channels to owning modules
ContractChannels Channels belonging to a contract
Commitments Packet/ack commitments
NextClientId, NextConnectionId, NextChannelId Auto-increment counters

Events emitted:

register_client, create_client, update_client, force_update_client, connection_open_init, connection_open_try, connection_open_ack, channel_open_init, channel_open_try, channel_open_ack, channel_open_confirm, channel_close_init, channel_close_confirm, packet_send, packet_recv, packet_ack, intent_packet_recv, batch_send, batch_acks, timeout_packet, write_ack, commit_membership_proof, commit_non_membership_proof.

Build system

Nix flakes (primary):

The entire repository uses Nix flakes for reproducible builds. The flake.nix at the root defines outputs for all components.

# Build specific components
nix build .#uniond
nix build .#voyager
nix build .#galoisd
nix build .#unionvisor

# Enter development shell
nix develop

Cargo workspace:

The Rust codebase uses a workspace with 200+ crates. Key workspace settings are inherited (authors, edition, license-file, publish, repository). All crates use version = "0.0.0" (versioning is handled at the release/tag level).

Foundry (EVM):

cd evm
forge build
forge test

Testing strategy

Test Type Location Framework
Rust unit tests Inline #[cfg(test)] modules cargo test
CosmWasm integration cosmwasm/*/src/tests/ cw-multi-test
EVM tests evm/tests/src/ Foundry (forge test)
Cairo tests cairo/*/tests/ Cairo test framework
Sui tests sui/*/tests/ Sui Move test framework
Aptos tests aptos/*/tests/ Aptos Move test framework
E2E tests e2e/ Custom Rust harness (access-manager-tests, ensure-blocks)
TypeScript tests ts-sdk/test/, ts-sdk-*/test/ Vitest
SSZ conformance lib/ssz/tests/spec_conformance/ Spec test vectors
Fuzz tests galoisd/pkg/*/testdata/fuzz/, lib/unionlabs/fuzz_targets/ Go fuzzing, cargo-fuzz

Code patterns and conventions

Rust patterns:

  • thiserror for error type derivation
  • tracing for structured logging (with info, debug, trace, warn, error macros)
  • serde for JSON/JSONC serialization
  • clap for CLI argument parsing
  • Workspace-level lint configuration
  • crate-type = ["cdylib", "rlib"] for CosmWasm contracts

Go patterns:

  • cobra for CLI commands (uniond, galoisd)
  • depinject for dependency injection (Cosmos SDK)
  • Protobuf for serialization

TypeScript patterns:

  • Effect-TS for composable, type-safe operations
  • Separate SDK packages per chain type (ts-sdk-evm, ts-sdk-cosmos, ts-sdk-sui)

Known issues from GitHub

Issue Description Status
#5163 Login error / user-facing error handling Open
#4962 HTTP method override vulnerability Open
#5428 Error handling improvements needed Open
#738 Voyager documentation gaps Open

Note

Issue #4962 describes an HTTP method override concern. If deploying Voyager's JSON-RPC interface publicly, ensure proper HTTP method validation at the reverse proxy level.


9. Integration Points

TypeScript SDK integration

npm install @unionlabs/ts-sdk

Package structure:

Package Purpose
@unionlabs/ts-sdk Core SDK with cross-chain intents
@unionlabs/ts-sdk-evm EVM-specific operations
@unionlabs/ts-sdk-cosmos Cosmos-specific operations
@unionlabs/ts-sdk-sui Sui-specific operations

Key environment variables for examples:

Variable Purpose
PRIVATE_KEY EVM private key (for Aptos examples)
MNEMONIC Cosmos wallet mnemonic
SUI_MNEMONIC Sui wallet mnemonic
RECIPIENT Transfer recipient address
MEMO Transaction memo

Example: programmatic EVM-to-Cosmos transfer:

import { createEvmToCosmosIntent } from "@unionlabs/ts-sdk/ucs03";

const intent = createEvmToCosmosIntent({
  sourceChainId: "1",           // Ethereum
  destinationChainId: "union-1",
  sender: "0x...",
  receiver: "union1...",
  token: "0x...",
  amount: BigInt("1000000"),
  channelId: 1,
});

Tip

The TypeScript SDK uses Effect-TS for composable error handling and async operations. Wrap SDK calls in Effect pipelines for proper error propagation and resource cleanup.

CosmWasm contract integration

Deploy ZKGM and IBC contracts on Cosmos chains:

# Build contracts
cd cosmwasm
cargo build --release --target wasm32-unknown-unknown

# Deploy using the deployer tool
cargo run --release -p cosmwasm-deployer -- \
  --chain-id <target_chain_id> \
  --rpc <rpc_url>

IBC application integration:

Implement the IbcUnionMsg execute handler in your CosmWasm contract to receive cross-chain messages:

use ibc_union_msg::module::{ExecuteMsg as ModuleMsg, IbcUnionMsg};

Register your contract as a channel owner via MsgChannelOpenInit through the IBC core contract.

EVM contract integration

Deploying IBC on a new EVM chain:

  1. Generate verifier contract: galoisd gen-contract
  2. Deploy IBC core contracts from evm/contracts/core/
  3. Deploy light client contract for the target chain type
  4. Register the light client: call registerClient on the handler
  5. Deploy ZKGM app from evm/contracts/apps/ucs/03-zkgm/
  6. Open channel through IBC handshake

Building the EVM deployer transaction:

cargo run --release -p build-evm-deployer-tx

Voyager plugin development

Create a new Voyager plugin:

  1. Create a new Rust crate under voyager/plugins/<category>/<name>/
  2. Implement the plugin trait from lib/voyager-plugin/
  3. Define interest filter for operation routing
  4. Register in Voyager configuration
use voyager_plugin::Plugin;

// Plugin entry point
fn main() {
    // Plugin initializes and connects via JSON-RPC
}

Environment configuration for plugins:

The lib/voyager-plugin/src/lib.rs reads RUST_LOG_FORMAT for log formatting.

Chain channel configuration

Production channel mappings are defined in deployments/channels.json. Each channel entry specifies:

{
  "<chain_id>": {
    "<channel_number>": {
      "comments": "<counterparty description>",
      "fees": {
        "forward": {
          "PACKET_RECV": "<gas_units>",
          "PACKET_SEND_LC_UPDATE_L0": "<gas_units>",
          "PACKET_SEND_LC_UPDATE_L1": "<gas_units>",
          "PACKET_SEND_LC_UPDATE_L2": "<gas_units>"
        },
        "reverse": { ... }
      },
      "sla": {
        "forward": "<ISO_8601_duration>",
        "reverse": "<ISO_8601_duration>"
      },
      "tags": ["canonical", "tokenorder-v2"]
    }
  }
}

Fee levels:

  • PACKET_RECV — gas for receiving a packet
  • PACKET_SEND_LC_UPDATE_L0 — gas for light client update (level 0)
  • PACKET_SEND_LC_UPDATE_L1 — gas for light client update (level 1, L2 chains)
  • PACKET_SEND_LC_UPDATE_L2 — gas for light client update (level 2, deep verification)

Bridged tokens:

Token mappings between chains are stored in evm/bridged_tokens_v1.json, containing base_token ↔ quote_token pairs with their associated channel IDs and direction (in/out).

Drip faucet integration

The drip service (drip/) provides testnet token distribution:

cargo run --release -p drip

Example request configurations are in drip/example-requests/.

u utility tool

The u tool (tools/u/) is a developer swiss-army knife with subcommands for:

Subcommand Purpose
zkgm make Construct ZKGM test data
cometbft CometBFT debugging utilities
arbitrum Arbitrum state inspection
parlia BSC Parlia consensus tools
codec Encoding/decoding utilities
vanity Vanity address generation

Troubleshooting

Error: account keeper is required for ante builder

Cause: uniond NewAnteHandler is missing a required keeper in the HandlerOptions. This occurs during node initialization if dependency injection fails.

Solution:

  1. Verify the app.go wiring correctly provides all keepers via depinject
  2. Check that all required modules are registered in the app configuration
  3. Verify Cosmos SDK version compatibility

Prevention: Use the Nix build system to ensure consistent dependency versions.

Error: failed to decode proof of possession

Cause: The create-union-validator command received a proof of possession with a 0x prefix or invalid hex encoding.

Solution:

# Remove the 0x prefix
uniond tx union-staking create-union-validator validator.json abc123... --from mykey
# NOT: 0xabc123...

Prevention: Always use non-prefixed hex for BLS proof of possession values.

Error: unable to read the config file

Cause: Voyager cannot find or parse the configuration file specified via --config-file-path.

Solution:

  1. Verify the file path exists: ls -la /path/to/config.json
  2. Check JSON syntax: jq . /path/to/config.json
  3. For JSONC files, ensure the extension is .jsonc

Prevention: Use voyager config schema to generate a JSON Schema for editor validation.

Error: version was not found in bundle

Cause: unionvisor attempted to activate a version that doesn't exist in the bundle directory.

Solution:

  1. Check bundle structure: ls -la /path/to/bundle/versions/
  2. Ensure the target version directory contains the binary: ls -la /path/to/bundle/versions/<version>/uniond
  3. Verify meta.json has correct versions_directory and binary_name

Prevention: Pre-populate bundle with all expected version binaries before upgrade heights.

Error: permission denied when executing version in bundle

Cause: The uniond binary in the bundle lacks execute permissions.

Solution:

chmod +x /path/to/bundle/versions/<version>/uniond

Prevention: Include chmod +x in your binary deployment scripts.

Error: Invalid Sui signer: expected Ed25519Keypair

Cause: The Sui SDK integration received an incorrect keypair type.

Solution: Ensure you're using Ed25519Keypair from the Sui SDK, not other key types.

Error: error while reading wasm config

Cause: uniond failed to read CosmWasm configuration during initialization.

Solution:

  1. Verify app.toml contains valid wasm configuration
  2. Check that the wasm directory exists and has correct permissions
  3. Ensure the CosmWasm module is properly configured in genesis

Prevention: Use uniond init to generate default configuration before modifying wasm settings.

Error: failed to register snapshot extension

Cause: The CosmWasm snapshot extension failed to register during node startup.

Solution:

  1. Check that the state directory is not corrupted
  2. Verify sufficient disk space
  3. Try resetting state with uniond tendermint unsafe-reset-all (destroys local state)

Prevention: Monitor disk space and filesystem health on validator nodes.

Voyager queue grows unbounded

Cause: Failed operations accumulate in the PostgreSQL queue without cleanup.

Solution:

  1. Check failed messages: voyager -c config.json queue query-failed
  2. Inspect error details for recurring failures
  3. Truncate failed queue if errors are transient: voyager -c config.json queue truncate --failed
  4. Run vacuum: voyager -c config.json queue vacuum --queue --done --failed

Prevention: Enable vacuum_on_boot: true in queue config. Monitor queue stats regularly.

Tip

For persistent queue growth, check the retryable_error_expo_backoff_max setting. A value that's too high (default: 3600s) can cause operations to back up during extended chain outages.

Galois proof generation timeout

Cause: ZK proof generation exceeds the 1-hour gRPC client timeout.

Solution:

  1. Check galoisd resource usage (CPU, memory)
  2. Reduce validator count if testing: galoisd example-prove <uri> <smaller_count>
  3. Ensure sufficient RAM (32 GB+ for production proofs)

Prevention: Monitor galoisd with the /health endpoint. Scale hardware for production validator set sizes.

Warning

The default gRPC timeout in galoisd is 1 hour (1 * time.Hour). For very large validator sets, proof generation may approach this limit. Monitor proof generation times and adjust timeouts if necessary.

Error: connection state must be 1..=3

Cause: Voyager's Sui state module encountered an invalid IBC connection state value during state query.

Solution:

  1. Verify the Sui IBC contract is deployed correctly
  2. Check that the connection was properly established through the IBC handshake
  3. Query the connection state directly on Sui to inspect the raw value

Prevention: Ensure IBC channel/connection establishment completes all handshake steps before relaying packets.

Error: channel state must be 1..=4

Cause: Voyager's Sui state module encountered an invalid IBC channel state value.

Solution: Same approach as connection state errors — verify the IBC channel was fully established.


FAQ

Q: How does Union differ from other cross-chain bridges?

Union uses ZK proofs (Groth16) to verify consensus instead of multisig committees or optimistic verification. This provides cryptographic security guarantees — the bridge is as secure as the underlying math, not dependent on honest majority assumptions among bridge operators.

Q: What is CometBLS and why use BN254?

CometBLS is a modified CometBFT consensus that uses BN254 BLS aggregate signatures instead of Ed25519. BN254 was chosen because it enables efficient pairing-based ZK proofs (Groth16), allowing the entire validator set's signatures to be verified on-chain via a single compact proof rather than checking each signature individually.

Q: Can I run Voyager without Galois?

Yes, for chains that don't require ZK proof verification (standard Tendermint-to-Tendermint relaying), Voyager operates independently. Galois is only needed when relaying to/from chains that verify Union consensus via ZK proofs.

Q: How do I add support for a new chain?

  1. Implement a Voyager client module (voyager/modules/client/<chain>/)
  2. Implement finality tracking (voyager/modules/finality/<chain>/)
  3. Implement state queries (voyager/modules/state/<chain>/)
  4. Implement proof construction (voyager/modules/proof/<chain>/)
  5. Implement client-bootstrap (voyager/modules/client-bootstrap/<chain>/)
  6. Add event-source plugin (voyager/plugins/event-source/<chain>/)
  7. Add client-update plugin (voyager/plugins/client-update/<chain>/)
  8. Add transaction plugin (voyager/plugins/transaction/<chain>/)
  9. Deploy IBC contracts on the target chain

Q: What is the u tool?

u is a developer utility tool at tools/u/ that provides subcommands for ZKGM data construction, CometBFT debugging, Arbitrum/Parlia state inspection, codec utilities, and vanity address generation. It's a Swiss-army knife for Union protocol development.

Q: How does the Voyager queue work?

Voyager uses a PostgreSQL-backed FIFO queue (lib/pg-queue). Operations are serialized as JSONB and processed by the engine in a spin loop with 10ms sleep between iterations. Each operation passes through an interest filter that routes it to the appropriate plugin. Failed operations use exponential backoff with configurable maximum delay.

Q: What are the state-lens light clients?

State-lens light clients are Union's approach for L2 chains that derive security from L1. Instead of directly verifying L2 consensus, a state-lens client reads L2 state through the L1 settlement layer's proof system. This means Arbitrum and Base security inherits from Ethereum L1 verification. Implementations exist as state-lens/ics23-mpt (Merkle Patricia Trie), state-lens/ics23-smt (Sparse Merkle Tree), and state-lens/ics23-ics23.

Q: How are EVM contracts organized?

The IBC core is split into modules under evm/contracts/core/: 02-client (client registry), 03-connection (connection handshake), 04-channel (channel lifecycle), 05-port (port binding), 24-host (storage), 25-handler (dispatcher). Applications live under evm/contracts/apps/ucs/. Tests use Foundry under evm/tests/.

Q: What is the MPC ceremony for?

The Multi-Party Computation ceremony generates the trusted setup parameters (proving and verification keys) for the Groth16 ZK circuits used in CometBLS verification. The ceremony ensures that no single participant learns the toxic waste, maintaining the soundness of the proof system. Components: ceremony/ (web UI), mpc/coordinator/ (orchestrator), mpc/client/ (participant).

Q: How do bridged token mappings work?

Token mappings in evm/bridged_tokens_v1.json link base tokens on one chain to wrapped (quote) tokens on another, along with channel IDs and direction. When a user bridges a token, the ZKGM contract uses these mappings to determine the correct wrapped token to mint on the destination chain.

Q: What happened to the legacy TypeScript SDK?

The typescript-sdk/ directory contains the original SDK. It has been superseded by the new modular ts-sdk/ (core), ts-sdk-evm/, ts-sdk-cosmos/, and ts-sdk-sui/ packages that use Effect-TS for composable, type-safe cross-chain operations.