# Union — Administrator & Developer Guide

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`):

```json
{
  "$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):**

```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):**

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

**From source:**

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

**Validator registration:**

```bash
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:

```bash
# 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

```bash
# 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:**

```sql
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

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

# Start the prover service
galoisd serve --tls yes
```

For MPC ceremony setup:

```bash
# 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:**

```bash
# 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:**

```bash
# 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:**

```bash
# 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:**

```bash
# 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:**

```bash
# 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:

```rust
// 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:

```rust
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:

```rust
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.

```bash
# 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):**

```bash
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

```bash
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:**

```typescript
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:

```bash
# 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:

```rust
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:**

```bash
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

```rust
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:

```json
{
  "<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:

```bash
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:**
```bash
# 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:**
```bash
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.

---

_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. Unofficial; not affiliated with or reviewed by the project._
