# Union -- User Guide

Union is the hyper-efficient, zero-knowledge Inter-Blockchain Communication (IBC) infrastructure for general message passing, asset transfers, NFTs, and DeFi across heterogeneous blockchains. It connects Cosmos-SDK chains, EVM networks, Sui, Starknet, Movement/Aptos, and more through a trust-minimized ZK-proof-based consensus bridge and an intelligent relay network.

Whether you want to bridge assets through the web UI, build programmatic cross-chain transfers with the TypeScript SDK, run a Union validator, operate a Voyager relay node, or decode on-chain data with the `u` CLI -- this guide covers it all.

---

## Table of Contents

- [1. Quick Start](#1-quick-start)
- [2. Installation](#2-installation)
- [3. Configuration](#3-configuration)
- [4. Core Usage -- Web UI (app.union.build)](#4-core-usage----web-ui-appunionbuild)
- [5. Core Usage -- TypeScript SDK](#5-core-usage----typescript-sdk)
- [6. Core Usage -- uniond (Chain Binary)](#6-core-usage----uniond-chain-binary)
- [7. Core Usage -- Voyager (IBC Relayer)](#7-core-usage----voyager-ibc-relayer)
- [8. Core Usage -- unionvisor (Process Supervisor)](#8-core-usage----unionvisor-process-supervisor)
- [9. Core Usage -- u CLI Tool](#9-core-usage----u-cli-tool)
- [10. Core Usage -- drip (Faucet)](#10-core-usage----drip-faucet)
- [11. Core Usage -- galoisd (ZK Proving)](#11-core-usage----galoisd-zk-proving)
- [12. Core Usage -- Ceremony (MPC)](#12-core-usage----ceremony-mpc)
- [13. Supported Chains and Channels](#13-supported-chains-and-channels)
- [14. Common Tasks and Workflow Recipes](#14-common-tasks-and-workflow-recipes)
- [15. Advanced Usage](#15-advanced-usage)
- [16. Troubleshooting](#16-troubleshooting)
- [17. FAQ](#17-faq)

---

## 1. Quick Start

The fastest way to experience Union is to bridge assets through the web application:

1. Visit [app.union.build](https://app.union.build)
2. Connect your wallet (MetaMask, Keplr, Leap, or a Sui wallet)
3. Select a source chain, destination chain, and token
4. Enter the amount, confirm the transaction, and wait for cross-chain settlement

> [!TIP]
> For testnet experimentation, use the Union faucet (drip service) to get free testnet tokens before bridging. The testnet runs on `union-testnet-10` and connects to Sepolia, Holesky, and Arbitrum Sepolia.

For programmatic access, install the TypeScript SDK:

```bash
npm install @unionlabs/sdk @unionlabs/sdk-evm
# or for Cosmos chains:
npm install @unionlabs/sdk @unionlabs/sdk-cosmos
# or for Sui:
npm install @unionlabs/sdk @unionlabs/sdk-sui
```

For infrastructure operators, continue to [Installation](#2-installation) for `uniond`, `voyager`, and `unionvisor` setup.

---

## 2. Installation

### TypeScript SDK

```bash
# Core SDK (required for all chain types)
npm install @unionlabs/sdk

# Chain-specific packages
npm install @unionlabs/sdk-evm      # EVM chains (Ethereum, Arbitrum, Base, etc.)
npm install @unionlabs/sdk-cosmos    # Cosmos-SDK chains (Union, Babylon, Osmosis, etc.)
npm install @unionlabs/sdk-sui       # Sui
```

The SDK uses [Effect-TS](https://effect.website/) as its foundation. Peer dependencies include `effect`, `viem` (for EVM), `@cosmjs/proto-signing` and `@cosmjs/stargate` (for Cosmos), and `@mysten/sui` (for Sui).

> [!IMPORTANT]
> The SDK requires Node.js 18+ and TypeScript 5.0+. Effect-TS is a core dependency -- you must install `effect` alongside the Union SDK packages.

### uniond (Chain Binary)

`uniond` is a Cosmos-SDK binary written in Go. Build from source:

```bash
git clone https://github.com/unionlabs/union.git
cd union/uniond
go build -o uniond ./cmd/uniond
```

Alternatively, download a pre-built binary from the official releases or use the Nix build system:

```bash
nix build .#uniond
```

### Voyager (IBC Relayer)

Voyager is a Rust binary. Build from source:

```bash
git clone https://github.com/unionlabs/union.git
cd union
cargo build --release -p voyager
```

The resulting binary is at `target/release/voyager`.

### unionvisor (Process Supervisor)

```bash
cd union
cargo build --release -p unionvisor
```

### u CLI Tool

```bash
cd union
cargo build --release -p u
```

### drip (Faucet)

```bash
cd union
cargo build --release -p drip
```

### galoisd (ZK Proving Service)

```bash
cd union/galoisd
go build -o galoisd ./cmd/galoisd
```

> [!WARNING]
> Building from source requires a Rust toolchain (stable), Go 1.21+, and protocol buffer tooling. The Nix flake in the repository provides a reproducible build environment with all dependencies pre-configured. If you encounter build issues, use `nix develop` to enter the development shell.

---

## 3. Configuration

### TypeScript SDK Configuration

The SDK is configured through the Effect-TS dependency injection pattern. Each operation requires specific service layers:

| Service Layer | Purpose | Chain Type |
|---|---|---|
| `Evm.PublicClient` | Read-only EVM chain access | EVM |
| `Evm.WalletClient` | Transaction signing for EVM | EVM |
| `Cosmos.Client` | Read-only Cosmos chain access | Cosmos |
| `Cosmos.SigningClient` | Transaction signing for Cosmos | Cosmos |
| `Sui.PublicClient` | Read-only Sui chain access | Sui |
| `Sui.WalletClient` | Transaction signing for Sui | Sui |
| `ChainRegistry.Default` | Chain metadata resolution | All |

Environment variables used by SDK examples:

| Variable | Purpose |
|---|---|
| `PRIVATE_KEY` | EVM private key (hex-prefixed) |
| `KEY` | Alternative EVM private key |
| `MNEMONIC` / `MEMO` | Cosmos wallet mnemonic |
| `SUI_MNEMONIC` | Sui wallet mnemonic |
| `RECIPIENT` | Default recipient address |
| `ADDRESS` | Default sender/query address |

### Voyager Configuration

Voyager reads its configuration from a JSON or JSONC file specified with `-c`:

```bash
voyager -c /path/to/config.json start
```

Generate a default configuration:

```bash
voyager config default
```

Print the JSON Schema for validation:

```bash
voyager config schema
```

### uniond Configuration

Standard Cosmos-SDK configuration via `config.toml` and `app.toml` in the node home directory (default `~/.union`):

```bash
uniond init my-node --chain-id union-1
```

Key configuration files:
- `~/.union/config/config.toml` -- CometBFT consensus, P2P, RPC settings
- `~/.union/config/app.toml` -- application-level settings (gas, pruning, API)
- `~/.union/config/genesis.json` -- chain genesis state

The address prefix for Union is `union`.

### unionvisor Configuration

unionvisor uses a bundle directory structure:

```
bundle/
├── genesis.json
├── meta.json
└── versions/
    ├── v0.8.0/
    │   └── uniond
    └── v0.9.0/
        └── uniond
```

The `meta.json` defines the binary name, fallback version, and versions directory path.

Environment variables:

| Variable | Purpose |
|---|---|
| `UNIONVISOR_ROOT` | Root directory for unionvisor state |
| `UNIONVISOR_BUNDLE` | Path to the bundle directory |
| `UNIONVISOR_POLL_INTERVAL` | Upgrade poll interval |

### drip (Faucet) Configuration

Drip uses a JSON config file:

```json
{
  "log_format": "text",
  "secret": "captcha-secret",
  "bypass_secret": "bypass-key",
  "max_request_polls": 7,
  "chains": [
    {
      "id": "union-devnet-1",
      "bech32_prefix": "union",
      "memo": "drip drop greetings from union faucet",
      "ws_url": "ws://localhost:26657/websocket",
      "grpc_url": "http://localhost:9090",
      "gas_config": {
        "gas_price": "1.0",
        "gas_denom": "au",
        "gas_multiplier": "1.1",
        "max_gas": 40000000
      },
      "signer": "0x...",
      "coins": [
        { "denom": "au", "amount": 13370 }
      ]
    }
  ]
}
```

---

## 4. Core Usage -- Web UI (app.union.build)

The **app2** web application at [app.union.build](https://app.union.build) is a SvelteKit-based interface for bridging assets across all supported chains.

### Connecting Wallets

The app supports multiple wallet types simultaneously:

| Wallet Type | Supported Wallets | Chain Type |
|---|---|---|
| EVM | MetaMask, WalletConnect, injected | Ethereum, Arbitrum, Base, BSC, etc. |
| Cosmos | Keplr, Leap | Union, Babylon, Osmosis, Sei, Xion |
| Sui | Sui Wallet | Sui |

Click the wallet icon in the header to manage connected wallets. Multiple wallets across different chain types can be connected at once.

### Bridging Assets

1. Navigate to the **Transfer** page
2. Select the **source chain** and **asset** from the dropdown
3. Select the **destination chain**
4. Enter the **transfer amount**
5. The quote amount displays the expected received amount on the destination
6. Confirm and sign the transaction in your wallet

### Transfer Tracking

After submitting a transfer, track its progress through the **Explorer** section:
- `/explorer/transfers` -- view all transfers
- `/explorer/packets/{packet_hash}` -- individual packet details
- `/explorer/channels` -- browse active IBC channels
- `/explorer/clients` -- view light client states

### Additional Features

- **Faucet** (`/faucet`) -- request testnet tokens
- **Liquid Staking** (`/liquid-staking`) -- stake Union tokens
- **Dashboard** (`/dashboard`) -- view balances, missions, achievements, and leaderboard
- **Stake** (`/stake`) -- manage validator delegations

---

## 5. Core Usage -- TypeScript SDK

The TypeScript SDK provides programmatic cross-chain transfer capabilities through the `@unionlabs/sdk` family of packages. All operations are built on Effect-TS for type-safe, composable, and error-tracked workflows.

### EVM-to-EVM Transfer

```typescript
import { Evm, EvmZkgmClient } from "@unionlabs/sdk-evm"
import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry"
import { UniversalChainId } from "@unionlabs/sdk/schema/chain"
import { ChannelId } from "@unionlabs/sdk/schema/channel"
import * as TokenOrder from "@unionlabs/sdk/TokenOrder"
import * as ZkgmClient from "@unionlabs/sdk/ZkgmClient"
import * as ZkgmClientRequest from "@unionlabs/sdk/ZkgmClientRequest"
import * as ZkgmClientResponse from "@unionlabs/sdk/ZkgmClientResponse"
import * as ZkgmIncomingMessage from "@unionlabs/sdk/ZkgmIncomingMessage"
import { Effect, Logger } from "effect"
import { http } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { holesky } from "viem/chains"

const program = Effect.gen(function*() {
  const source = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("ethereum.17000"),
  )
  const destination = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("ethereum.11155111"),
  )

  const tokenOrder = yield* TokenOrder.make({
    source,
    destination,
    sender: "0x06627714f3F17a701f7074a12C02847a5D2Ca487",
    receiver: "0x50A22f95bcB21E7bFb63c7A8544AC0683dCeA302",
    baseToken: "0x685ce6742351ae9b618f383883d6d1e0c5a31b4b",
    baseAmount: 10n,
    quoteToken: "0x80fdbf104ec58a527ec40f7b03f88c404ef4ba63",
    quoteAmount: 10n,
    kind: "escrow",
    metadata: undefined,
    version: 2,
  })

  const request = ZkgmClientRequest.make({
    source,
    destination,
    channelId: ChannelId.make(2),
    ucs03Address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03",
    kind: "simulateAndExecute",
    instruction: tokenOrder,
  })

  const zkgmClient = yield* ZkgmClient.ZkgmClient
  const response: ZkgmClientResponse.ZkgmClientResponse =
    yield* zkgmClient.execute(request)

  yield* Effect.log("Submission Hash", response.txHash)

  const completion = yield* response.waitFor(
    ZkgmIncomingMessage.LifecycleEvent.$is(
      "EvmTransactionReceiptComplete",
    ),
  )
  yield* Effect.log("Completion", completion)
}).pipe(
  Effect.provide(EvmZkgmClient.layerWithoutWallet),
  Effect.provide(Evm.WalletClient.Live({
    account: privateKeyToAccount(process.env.KEY as any),
    chain: holesky,
    transport: http("https://rpc.17000.ethereum.chain.kitchen"),
  })),
  Effect.provide(Evm.PublicClient.Live({
    chain: holesky,
    transport: http("https://rpc.17000.ethereum.chain.kitchen"),
  })),
  Effect.provide(ChainRegistry.Default),
  Effect.provide(Logger.replace(
    Logger.defaultLogger,
    Logger.prettyLoggerDefault,
  )),
)

Effect.runPromise(program).then(console.log).catch(console.error)
```

### Cosmos-to-EVM Transfer

```typescript
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"
import { GasPrice } from "@cosmjs/stargate"
import { TokenOrder, ZkgmClient, ZkgmClientRequest, ZkgmClientResponse } from "@unionlabs/sdk"
import { Cosmos, CosmosZkgmClient } from "@unionlabs/sdk-cosmos"
import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry"
import { UniversalChainId } from "@unionlabs/sdk/schema/chain"
import { ChannelId } from "@unionlabs/sdk/schema/channel"
import { Effect, Logger } from "effect"

const program = Effect.gen(function*() {
  const source = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("union.union-testnet-10"),
  )
  const destination = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("ethereum.11155111"),
  )

  const tokenOrder = yield* TokenOrder.make({
    source,
    destination,
    sender: "union122ny3mep2l7nhtafpwav2y9e5jrslhek76hsjl",
    receiver: "0x50A22f95bcB21E7bFb63c7A8544AC0683dCeA302",
    baseToken: "au",
    baseAmount: 10n,
    quoteToken: "0xba5eD44733953d79717F6269357C77718C8Ba5ed",
    quoteAmount: 10n,
    kind: "solve",
    metadata: "0x...",
    version: 2,
  })

  const request = ZkgmClientRequest.make({
    source,
    destination,
    channelId: ChannelId.make(1),
    ucs03Address:
      "union1336jj8ertl8h7rdvnz4dh5rqahd09cy0x43guhsxx6xyrztx292qpe64fh",
    instruction: tokenOrder,
  })

  const zkgmClient = yield* ZkgmClient.ZkgmClient
  const response: ZkgmClientResponse.ZkgmClientResponse =
    yield* zkgmClient.execute(request)
  yield* Effect.log("TX Hash:", response.txHash)
}).pipe(
  Effect.provide(CosmosZkgmClient.layerWithoutSigningClient),
  Effect.provide(Cosmos.SigningClient.Live(
    "union122ny3mep2l7nhtafpwav2y9e5jrslhek76hsjl",
    "https://rpc.union-testnet-10.union.chain.kitchen",
    await DirectSecp256k1HdWallet.fromMnemonic(
      "your mnemonic here",
      { prefix: "union" },
    ),
    { gasPrice: GasPrice.fromString("100000au") },
  )),
  Effect.provide(Cosmos.Client.Live(
    "https://rpc.union-testnet-10.union.chain.kitchen",
  )),
  Effect.provide(ChainRegistry.Default),
  Effect.provide(Logger.replace(
    Logger.defaultLogger,
    Logger.prettyLoggerDefault,
  )),
)

Effect.runPromise(program).then(console.log).catch(console.error)
```

### Cosmos-to-Cosmos Transfer

```typescript
import { Effect } from "effect"
import { toHex } from "viem"
import {
  createCosmosToCosmosFungibleAssetOrder,
} from "@unionlabs/sdk/ucs03/fungible-asset-order"
import {
  CosmWasmClientSource,
  CosmWasmClientDestination,
  createCosmWasmClient,
} from "@unionlabs/sdk/cosmos/client"
import {
  CosmosChannelSource,
  CosmosChannelDestination,
} from "@unionlabs/sdk/cosmos/channel"
import { AddressCosmosZkgm } from "@unionlabs/sdk/schema/address"
import { UniversalChainId, ChannelId } from "@unionlabs/sdk/schema"

const program = Effect.gen(function*() {
  const sourceClient = yield* createCosmWasmClient(
    "https://rpc.bbn-test-5.babylon.chain.kitchen",
  )
  const destClient = yield* createCosmWasmClient(
    "https://rpc.osmo-test-5.osmosis.chain.kitchen",
  )

  return yield* createCosmosToCosmosFungibleAssetOrder({
    sender: AddressCosmosZkgm.make(toHex("bbn1...")),
    receiver: AddressCosmosZkgm.make(toHex("osmo1...")),
    baseToken: "ubbn",
    baseAmount: 1000000n,
    quoteAmount: 1000000n,
    sourceChainId: UniversalChainId.make("babylon.bbn-test-5"),
    sourceChannelId: ChannelId.make(2),
  }).pipe(
    Effect.provideService(CosmWasmClientSource, { client: sourceClient }),
    Effect.provideService(CosmWasmClientDestination, { client: destClient }),
    Effect.provideService(CosmosChannelDestination, {
      ucs03address: "osmo1336jj8ertl8h7rdvnz4dh5rqahd09cy0x43guhsxx6xyrztx292qs2uecc",
      channelId: 1,
    }),
    Effect.provideService(CosmosChannelSource, {
      ucs03address: "bbn1336jj8ertl8h7rdvnz4dh5rqahd09cy0x43guhsxx6xyrztx292q77945h",
      channelId: 5,
    }),
  )
})
```

### EVM-to-Cosmos Transfer

```typescript
import { Effect } from "effect"
import { createPublicClient, http, parseEther, toHex } from "viem"
import { holesky } from "viem/chains"
import {
  createEvmToCosmosFungibleAssetOrder,
} from "@unionlabs/sdk/ucs03/fungible-asset-order"
import { ViemPublicClientSource } from "@unionlabs/sdk/evm/client"
import { CosmWasmClientDestination, createCosmWasmClient } from "@unionlabs/sdk/cosmos/client"
import { CosmosChannelDestination } from "@unionlabs/sdk/cosmos/channel"
import { AddressEvmZkgm, AddressCosmosZkgm } from "@unionlabs/sdk/schema/address"
import { TokenRawDenom, UniversalChainId, ChannelId } from "@unionlabs/sdk/schema"

const program = Effect.gen(function*() {
  const cosmosClient = yield* createCosmWasmClient(
    "https://rpc.bbn-test-5.babylon.chain.kitchen",
  )

  return yield* createEvmToCosmosFungibleAssetOrder({
    sender: AddressEvmZkgm.make("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"),
    receiver: AddressCosmosZkgm.make(toHex("bbn122ny3mep2l7nhtafpwav2y9e5jrslhekrn8frh")),
    baseToken: TokenRawDenom.make("0xb476983cc7853797fc5adc4bcad39b277bc79656"),
    baseAmount: parseEther("100"),
    quoteAmount: 1000000n,
    sourceChainId: UniversalChainId.make("ethereum.17000"),
    sourceChannelId: ChannelId.make(2),
  }).pipe(
    Effect.provideService(ViemPublicClientSource, {
      client: createPublicClient({ chain: holesky, transport: http() }),
    }),
    Effect.provideService(CosmWasmClientDestination, { client: cosmosClient }),
    Effect.provideService(CosmosChannelDestination, {
      ucs03address: "bbn1336jj8ertl8h7rdvnz4dh5rqahd09cy0x43guhsxx6xyrztx292q77945h",
      channelId: 3,
    }),
  )
})
```

### Sui Transfer

```typescript
import { getFullnodeUrl } from "@mysten/sui/client"
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519"
import { Sui, SuiZkgmClient } from "@unionlabs/sdk-sui"
import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry"
import { UniversalChainId } from "@unionlabs/sdk/schema/chain"
import { ChannelId } from "@unionlabs/sdk/schema/channel"
import * as TokenOrder from "@unionlabs/sdk/TokenOrder"
import * as ZkgmClient from "@unionlabs/sdk/ZkgmClient"
import * as ZkgmClientRequest from "@unionlabs/sdk/ZkgmClientRequest"
import * as ZkgmClientResponse from "@unionlabs/sdk/ZkgmClientResponse"
import { Effect, Logger } from "effect"
import * as Layer from "effect/Layer"

const keypair = Ed25519Keypair.deriveKeypair(process.env.SUI_MNEMONIC ?? "...")

const program = Effect.gen(function*() {
  const source = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("ethereum.17000"),
  )
  const destination = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("union.union-1"),
  )

  const wallet = yield* Sui.WalletClient
  const sender = wallet.signer.toSuiAddress()

  const tokenOrder = yield* TokenOrder.make({
    source,
    destination,
    sender,
    receiver: "union1wycy8g8v5sff6gsjl9yhjs43q98xpl05p3gn2s",
    baseToken: "0x2::sui::SUI",
    baseAmount: 10000000n,
    quoteToken: "union1y05e0p2jcvhjzf7kcqsrqx93d4g3u93hc2hykaq8hrvkqrp5ltrssagzyd",
    quoteAmount: 10000000n,
    kind: "solve",
    metadata: "0x...",
    version: 2,
  })

  const request = ZkgmClientRequest.make({
    source,
    destination,
    channelId: ChannelId.make(5),
    ucs03Address: "0x3078...",
    instruction: tokenOrder,
    transport: {
      sui: {
        vaultId: "0xc3ac8618f622fc70ea30eaec5b45d504e239af668033d07e396be44d45f8f45d",
        ibcStoreId: "0xdc5f20df5f143a06772c073e9c30dacd30e31f6788885cf478d0fd40f92766c4",
        coins: [{
          typeArg: "0x2::sui::SUI",
          objectId: "0x266d00c4b329111255339c041cc57a1b616cfeddafdae47df8f814002578e95b",
          baseAmount: BigInt(3),
        }],
      },
    },
  })

  const zkgmClient = yield* ZkgmClient.ZkgmClient
  const response: ZkgmClientResponse.ZkgmClientResponse =
    yield* zkgmClient.execute(request)
  yield* Effect.log("Submission Hash", response.txHash)
}).pipe(
  Effect.provide(
    Layer.mergeAll(
      SuiZkgmClient.layerWithoutWallet,
      ChainRegistry.Default,
      Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault),
    ).pipe(
      Layer.provideMerge(Sui.PublicClient.Live({
        url: getFullnodeUrl("testnet"),
      })),
      Layer.provideMerge(Sui.WalletClient.Live({
        url: getFullnodeUrl("testnet"),
        signer: keypair,
      })),
    ),
  ),
)

Effect.runPromise(program).catch(console.error)
```

### Cosmos Contract Call (UCS05)

Execute a cross-chain contract call from Babylon to an Ethereum contract:

```typescript
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"
import { GasPrice } from "@cosmjs/stargate"
import { Call, Ucs05, ZkgmClientRequest, ZkgmClientResponse } from "@unionlabs/sdk"
import { Cosmos, CosmosZkgmClient } from "@unionlabs/sdk-cosmos"
import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry"
import { UniversalChainId } from "@unionlabs/sdk/schema/chain"
import { ChannelId } from "@unionlabs/sdk/schema/channel"
import { Effect, Logger } from "effect"

const signer = await DirectSecp256k1HdWallet.fromMnemonic(
  process.env.MEMO ?? "your mnemonic",
  { prefix: "bbn" },
)

const program = Effect.gen(function*() {
  const source = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("babylon.bbn-1"),
  )
  const destination = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("ethereum.1"),
  )

  const call = Call.make({
    sender: Ucs05.CosmosDisplay.make({
      address: "bbn122ny3mep2l7nhtafpwav2y9e5jrslhekrn8frh",
    }),
    eureka: false,
    contractAddress: Ucs05.EvmDisplay.make({
      address: "0x921e5b5091f431f84f14423ec487783a853bc4b0",
    }),
    contractCalldata: "0xDEADBEEF",
  })

  const request = ZkgmClientRequest.make({
    source,
    destination,
    channelId: ChannelId.make(3),
    ucs03Address:
      "bbn1336jj8ertl8h7rdvnz4dh5rqahd09cy0x43guhsxx6xyrztx292q77945h",
    instruction: call,
  })

  const client = yield* CosmosZkgmClient.make.pipe(
    Effect.provide(Cosmos.SigningClient.Live(
      "bbn122ny3mep2l7nhtafpwav2y9e5jrslhekrn8frh",
      "https://rpc.bbn-1.babylon.chain.kitchen",
      signer,
      { gasPrice: GasPrice.fromString("0.0007ubbn") },
    )),
    Effect.provide(Cosmos.Client.Live(
      "https://rpc.bbn-1.babylon.chain.kitchen",
    )),
  )

  const response: ZkgmClientResponse.ZkgmClientResponse =
    yield* client.execute(request)
  yield* Effect.log("TX Hash:", response.txHash)
}).pipe(
  Effect.provide(ChainRegistry.Default),
  Effect.provide(Logger.replace(
    Logger.defaultLogger,
    Logger.prettyLoggerDefault,
  )),
)

Effect.runPromise(program).then(console.log).catch(console.error)
```

### SDK Instruction Types

The SDK supports several instruction types for ZKGM (UCS03):

| Instruction | Purpose |
|---|---|
| `Instruction.FungibleAssetOrder` | Transfer fungible tokens cross-chain |
| `Instruction.Batch` | Bundle multiple instructions into one packet |
| `Instruction.Forward` | Route a transfer through an intermediate chain |
| `Instruction.Multiplex` | Call a smart contract on the destination chain |

```typescript
import { Instruction } from "@unionlabs/sdk/ucs03"
import { toHex } from "viem"

// Batch two transfers in a single packet
const batch = new Instruction.Batch({
  operand: [
    new Instruction.FungibleAssetOrder({
      operand: [
        toHex("union1jk9psyhvgkrt2cumz8eytll2244m2nnz4yt2g2"),
        "0xBe68fC2d8249eb60bfCf0e71D5A0d2F2e292c4eD",
        toHex("muno"), 4n, "muno", "muno", 18, 0n,
        "0x74d5b8eacfeb0dadaaf66403f40e304b3ef968b3", 4n,
      ],
    }),
    new Instruction.FungibleAssetOrder({
      operand: [/* second transfer params */],
    }),
  ],
})

// Forward through an intermediate chain
const forward = new Instruction.Forward({
  operand: [
    0n,          // path
    0n,          // timeoutHeight
    1234567890n, // timeoutTimestamp
    new Instruction.FungibleAssetOrder({ operand: [/* ... */] }),
  ],
})

// Multiplex: call a remote smart contract
const multiplex = new Instruction.Multiplex({
  operand: [
    "0xBe68fC2d8249eb60bfCf0e71D5A0d2F2e292c4eD",
    true,
    "0x74d5b8eacfeb0dadaaf66403f40e304b3ef968b3",
    toHex("some smart contract data"),
  ],
})
```

---

## 6. Core Usage -- uniond (Chain Binary)

`uniond` is the Cosmos-SDK chain binary for the Union blockchain. It handles node operation, validator management, transactions, and queries.

### Initializing a Node

```bash
uniond init my-node --chain-id union-1
```

This creates the node home directory at `~/.union` with default configuration.

### Starting the Node

```bash
uniond start
```

### Key Management

```bash
# Create a new key
uniond keys add my-wallet

# List all keys
uniond keys list

# Show key address
uniond keys show my-wallet

# Recover a key from mnemonic
uniond keys add my-wallet --recover
```

### Querying the Chain

```bash
# Check node status
uniond status

# Query account balance
uniond query bank balances union1...

# Query a specific transaction
uniond query tx <TXHASH>

# Query transactions by events
uniond query txs --events 'message.action=/cosmos.bank.v1beta1.MsgSend'

# Query block at height
uniond query block <HEIGHT>
```

### Submitting Transactions

```bash
# Send tokens
uniond tx bank send <from-key> union1<recipient> 1000000au \
  --chain-id union-1 \
  --gas auto \
  --gas-adjustment 1.3

# Sign a transaction
uniond tx sign unsigned_tx.json --from my-wallet

# Broadcast a signed transaction
uniond tx broadcast signed_tx.json

# Simulate a transaction (dry run)
uniond tx simulate signed_tx.json
```

### Validator Operations

```bash
# Create a Union validator
uniond create-union-validator \
  --moniker "My Validator" \
  --identity "<keybase-id>" \
  --commission-rate 0.10 \
  --commission-max-rate 0.20 \
  --commission-max-change-rate 0.01 \
  --min-self-delegation 1

# Prove BLS key possession
uniond prove-possession <bls-key>
```

### Genesis Commands

```bash
# Validate genesis file
uniond genesis validate-genesis

# Add genesis account
uniond genesis add-genesis-account union1... 1000000000au

# Generate gentx
uniond genesis gentx my-wallet 1000000000au --chain-id union-1
```

### Node Configuration and Maintenance

```bash
# Update app configuration
uniond config set <key> <value>

# Manage pruning
uniond pruning

# Create/restore snapshots
uniond snapshot export
uniond snapshot restore <snapshot-file>
```

---

## 7. Core Usage -- Voyager (IBC Relayer)

Voyager is Union's trust-minimized IBC relayer with a plugin architecture supporting ZK-proof-based consensus verification across heterogeneous chains.

### Starting the Relayer

```bash
voyager -c config.json start
```

### Configuration Management

```bash
# Print current config
voyager -c config.json config print

# Generate default config
voyager config default

# Print config JSON Schema
voyager config schema
```

### Global Options

| Flag | Short | Description |
|---|---|---|
| `--config-file-path` | `-c` | Path to config file (JSON or JSONC) |
| `--log-format` | `-l` | Log format: `text` (default) or `json` |
| `--stack-size` | -- | Thread stack size (default: 2MB) |

### Indexing Events

Start indexing blockchain events for relay processing:

```bash
# Index from latest block (infinite)
voyager -c config.json index <chain-id>

# Index from a specific height
voyager -c config.json index <chain-id> --from 1000000

# Index a range of blocks
voyager -c config.json index <chain-id> --from 1000000 --to 1001000

# Index a single block
voyager -c config.json index <chain-id> --exact 1000500

# Index and automatically enqueue
voyager -c config.json index <chain-id> --from 1000000 --enqueue
```

### Queue Management

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

# Enqueue a custom operation
voyager -c config.json queue enqueue '<JSON operation>'

# Query failed messages
voyager -c config.json queue query-failed

# Query a specific failed message by ID
voyager -c config.json queue query-failed-by-id <ID>

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

# Vacuum queue database
voyager -c config.json queue vacuum --queue --done --failed
```

### Plugin System

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

# Query plugin interest filter
voyager -c config.json plugin interest <plugin-name>

# Get plugin info
voyager -c config.json plugin info <plugin-name>

# Call a plugin directly
voyager -c config.json plugin call <plugin-name> '<JSON args>'
```

### RPC Commands

Query a running Voyager instance via JSON-RPC:

```bash
# Get Voyager info
voyager rpc --rpc-url http://localhost:7042 info

# Query client state
voyager rpc client-state <chain-id> <client-id>

# Query consensus state
voyager rpc consensus-state <chain-id> <client-id> <height>

# Get latest height
voyager rpc latest-height <chain-id>

# Get latest timestamp
voyager rpc latest-timestamp <chain-id>

# Query IBC state
voyager rpc ibc-state <chain-id> <height> <path>

# Get IBC proof
voyager rpc ibc-proof <chain-id> <height> <path>
```

### Creating Clients

```bash
voyager -c config.json msg create-client <chain-id> <client-type>
```

> [!IMPORTANT]
> Voyager's plugin architecture means you must have the appropriate plugins loaded in your configuration for the chains you want to relay between. Each chain type (EVM, Cosmos, Sui, etc.) requires its own set of client, finality, proof, state, and event-source plugins.

### Voyager Plugin Types

| Plugin Category | Purpose | Examples |
|---|---|---|
| `client-bootstrap` | Initialize light clients | arbitrum, cometbls, ethereum, sui |
| `client-update` | Update light client states | arbitrum, ethereum, tendermint, sui |
| `event-source` | Watch for IBC events | cosmwasm, evm, movement, starknet, sui |
| `finality` | Track block finality | arbitrum, cometbls, ethereum, sui |
| `proof` | Generate state proofs | cosmwasm, evm-mpt, movement, sui |
| `state` | Query on-chain state | cosmwasm, evm, movement, sui |
| `transaction` | Submit transactions | aptos, cosmos, evm, sui |
| `packet-batch` | Batch packet operations | -- |
| `packet-filter` | Filter relevant packets | -- |
| `packet-index` | Index packet metadata | -- |
| `packet-timeout` | Handle packet timeouts | -- |
| `zkgm-filter` | Filter ZKGM-specific events | -- |

---

## 8. Core Usage -- unionvisor (Process Supervisor)

unionvisor is a process supervisor that manages `uniond` binary upgrades automatically. It monitors for on-chain upgrade proposals and swaps the binary at the correct block height.

### Initialization

```bash
unionvisor --root /path/to/unionvisor init \
  -b /path/to/bundle \
  --moniker "my-node" \
  --seeds "seed1@ip:port,seed2@ip:port"
```

| Flag | Description |
|---|---|
| `-b`, `--bundle` | Path to the bundle directory |
| `--seeds` | Comma-separated seed node addresses |
| `--moniker` | Node display name |
| `--allow-dirty` | Allow initialization in a non-empty directory |

### Running the Supervisor

```bash
unionvisor --root /path/to/unionvisor run \
  -b /path/to/bundle \
  -p 5000
```

| Flag | Description |
|---|---|
| `-b`, `--bundle` | Path to bundle directory |
| `-p`, `--poll-interval` | Upgrade poll interval in milliseconds |

### Setting a Specific Version

```bash
unionvisor --root /path/to/unionvisor set-uniond-version \
  -b /path/to/bundle
```

### Calling uniond Through unionvisor

```bash
unionvisor --root /path/to/unionvisor call \
  -b /path/to/bundle \
  -- status
```

This passes all arguments after `--` directly to the managed `uniond` binary.

### Bundle Structure

The bundle contains versioned binaries and genesis configuration:

```
bundle/
├── genesis.json          # Chain genesis file
├── meta.json             # Bundle metadata
└── versions/
    ├── genesis/          # Fallback / initial version
    │   └── uniond
    ├── v1.1.0/
    │   └── uniond
    └── v1.2.0/
        └── uniond
```

The `meta.json` format:

```json
{
  "binary_name": "uniond",
  "fallback_version": "genesis",
  "versions_directory": "versions"
}
```

---

## 9. Core Usage -- u CLI Tool

The `u` CLI is Union's Swiss Army knife for developers. It provides codec operations, ZKGM utilities, deployment info, vanity address generation, and chain-specific tooling.

### Command Overview

```
u [OPTIONS] <COMMAND>

Commands:
  arbitrum (arb)        Arbitrum-specific tools (v1/v2)
  parlia                BSC/Parlia chain tools
  codec (c)             Codec operations (light-client decoding)
  cometbft (comet, cmt) CometBFT RPC tools
  zkgm (z, ucs03)       ZKGM/UCS03 utilities
  deployments (d)       Print deployment configurations
  path                  Path utilities
  packet                Packet inspection
  vanity (v)            Vanity address generation
  hex (h)               Hex encode/decode
```

### Global Options

| Flag | Short | Description |
|---|---|---|
| `--log-format` | `-l` | Log format: `text` (default) or `json` |

### Hex Encoding/Decoding

```bash
# Encode to hex
echo "hello" | u hex

# Decode from hex
u hex --decode 0x68656c6c6f

# Encode without 0x prefix
u hex --no-prefix "hello"
```

### ZKGM Utilities

```bash
# Predict a wrapped token address
u zkgm predict-wrapped-token <args>

# Encode/decode ZKGM instructions
u zkgm make encode <instruction-json>
u zkgm make decode <hex-data>
```

### Deployment Info

```bash
# Print all deployments
u deployments print

# Print deployment for a specific chain
u deployments print ethereum.1
```

### Codec Operations

```bash
# Decode light client data
u codec light-client <subcommand>
```

### CometBFT RPC

```bash
# Query CometBFT RPC
u cometbft rpc <subcommand>
```

### Vanity Address Generation

```bash
# Generate vanity wallet addresses
u vanity wallet <pattern>

# Generate vanity ICS23 keys
u vanity ics23 <pattern>

# Generate vanity CREATE3 addresses
u vanity create3 <pattern>
```

---

## 10. Core Usage -- drip (Faucet)

The drip faucet distributes testnet tokens via a GraphQL API.

### Running the Faucet

```bash
drip -c config.json
```

| Flag | Short | Default | Description |
|---|---|---|---|
| `--config` | `-c` | required | Path to config file |
| `--batch-size` | `-b` | 6000 | Number of requests to process per batch |
| `--max-per-day` | `-m` | 50 | Maximum requests per address per day |

The faucet starts an HTTP server on `0.0.0.0:8000` with:
- `GET /` -- GraphiQL interactive explorer
- `POST /` -- GraphQL endpoint (queries and mutations)

### Requesting Tokens via GraphQL

```graphql
mutation {
  send(
    chainId: "union-testnet-10"
    denom: "au"
    toAddress: "union1..."
  ) {
    id
    txHash
  }
}
```

The faucet supports rate limiting per address (configurable via `ratelimit_seconds` in config) and optional CAPTCHA verification.

### Checking Request Status

```graphql
query {
  request(id: 42) {
    id
    chainId
    denom
    address
    time
    txHash
  }
}
```

> [!WARNING]
> The faucet uses a SQLite database (`db.sqlite3`) in the working directory. Ensure the process has write permissions to the current directory.

---

## 11. Core Usage -- galoisd (ZK Proving)

galoisd is the zero-knowledge proving service that generates BLS12-381 proofs for Union's consensus verification.

### Commands

| Command | Description |
|---|---|
| `serve` | Start the gRPC proving server |
| `serve-bls12381` | Start BLS12-381 specific server |
| `example-prove` | Generate an example proof |
| `example-verify` | Verify an example proof |
| `export-vk` | Export the verification key |
| `gen-contract` | Generate a Solidity verifier contract |
| `query-stats` | Query proof generation statistics |
| `query-stats-health` | Health check for statistics service |

### MPC Ceremony Commands

| Command | Description |
|---|---|
| `mpc-phase1-init` | Initialize MPC Phase 1 |
| `mpc-phase2-init` | Initialize MPC Phase 2 |
| `mpc-phase2-contrib` | Submit Phase 2 contribution |
| `mpc-phase2-extract` | Extract Phase 2 parameters |
| `mpc-phase2-verify` | Verify Phase 2 contribution |

### Starting the Proving Server

```bash
galoisd serve --listen 0.0.0.0:9090
```

The server exposes a `/health` endpoint for monitoring.

---

## 12. Core Usage -- Ceremony (MPC)

The ceremony application is a SvelteKit web UI for participating in Union's Multi-Party Computation (MPC) trusted setup ceremony. This ceremony generates the cryptographic parameters used by the ZK proving system.

### Components

- **ceremony** -- SvelteKit web app for ceremony participation
- **mpc/client** -- Rust CLI client for contributing to the ceremony
- **mpc/coordinator** -- Rust coordination server
- **mpc/edge** -- Supabase edge functions for ceremony API

### Participating

1. Visit the ceremony web app
2. Connect your wallet (for identity verification)
3. Download and run the MPC client binary
4. Follow the on-screen instructions to generate and submit your contribution
5. View your contribution hash in the contributions list

---

## 13. Supported Chains and Channels

### Mainnet Chains and Channels

| Chain | Chain ID | Channels |
|---|---|---|
| **Union** | `union.union-1` | 1 (Ethereum), 2 (Osmosis), 3 (Base), 4 (Arbitrum), 7 (BSC), 8 (Corn), 9 (Sei), 10 (Berachain), 11 (Bob), 12 (Xion), 13 (Babylon) |
| **Ethereum** | `ethereum.1` | 1 (Babylon), 2 (Union), 3 (Osmosis), 4 (Sei), 5 (Base), 6 (Berachain), 7 (Arbitrum), 8 (BSC), 9 (Corn), 10 (Bob) |
| **Arbitrum** | `arbitrum.42161` | 1 (Union), 2 (Ethereum) |
| **Base** | `base.8453` | 1 (Union), 2 (Sei), 3 (Ethereum) |
| **BSC** | `bsc.56` | 1 (Union), 2 (Ethereum) |
| **Berachain** | `berachain.80094` | 1 (Union), 2 (Ethereum) |
| **Bob** | `bob.60808` | 1 (Union), 2 (Ethereum) |
| **Corn** | `corn.21000000` | 1 (Union), 2 (Ethereum) |
| **Osmosis** | `osmosis.osmosis-1` | 1 (Union), 2 (Ethereum) |
| **Sei** | `sei.pacific-1` | 1 (Union), 2 (Ethereum), 3 (Base) |
| **Babylon** | `babylon.bbn-1` | 3 (Ethereum) |
| **Xion** | `xion.xion-mainnet-1` | 1 (Union) |

### Testnet Chains

| Chain | Chain ID |
|---|---|
| Union Testnet | `union.union-testnet-10` |
| Ethereum Sepolia | `ethereum.11155111` |
| Ethereum Holesky | `ethereum.17000` |
| Arbitrum Sepolia | `arbitrum.421614` |
| Bob Testnet | `bob.808813` |
| Corn Testnet | `corn.21000001` |
| Movement Bardock | `movement.bardock-testnet` |

### Channel Tags

Channels are tagged with metadata:
- `canonical` -- official, production-grade channel
- `tokenorder-v2` -- supports TokenOrder v2 instruction format

### SLA Expectations

Each channel direction has an SLA (Service Level Agreement) for transfer completion:

| Direction | Typical SLA |
|---|---|
| Cosmos-to-EVM | ~20 minutes |
| EVM-to-Cosmos | ~3 hours (depends on finality) |
| EVM-to-EVM (via L1) | ~45 minutes |

---

## 14. Common Tasks and Workflow Recipes

### Recipe 1: Bridge ETH from Ethereum to Union

Use the web UI at [app.union.build](https://app.union.build):
1. Connect MetaMask (Ethereum Mainnet)
2. Connect Keplr (Union)
3. Select Ethereum as source, Union as destination
4. Select ETH, enter amount
5. Approve and sign the transaction

### Recipe 2: Bridge Tokens Programmatically (EVM-to-EVM)

```typescript
import { Evm, EvmZkgmClient } from "@unionlabs/sdk-evm"
import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry"
import { UniversalChainId } from "@unionlabs/sdk/schema/chain"
import { ChannelId } from "@unionlabs/sdk/schema/channel"
import * as TokenOrder from "@unionlabs/sdk/TokenOrder"
import * as ZkgmClient from "@unionlabs/sdk/ZkgmClient"
import * as ZkgmClientRequest from "@unionlabs/sdk/ZkgmClientRequest"
import { Effect } from "effect"
import { http, parseEther } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { holesky } from "viem/chains"

const transfer = Effect.gen(function*() {
  const source = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("ethereum.17000"),
  )
  const destination = yield* ChainRegistry.byUniversalId(
    UniversalChainId.make("ethereum.11155111"),
  )
  const tokenOrder = yield* TokenOrder.make({
    source, destination,
    sender: "0xYourAddress",
    receiver: "0xRecipientAddress",
    baseToken: "0xTokenOnHolesky",
    baseAmount: parseEther("1"),
    quoteToken: "0xTokenOnSepolia",
    quoteAmount: parseEther("1"),
    kind: "escrow",
    metadata: undefined,
    version: 2,
  })
  const request = ZkgmClientRequest.make({
    source, destination,
    channelId: ChannelId.make(2),
    ucs03Address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03",
    kind: "simulateAndExecute",
    instruction: tokenOrder,
  })
  const zkgmClient = yield* ZkgmClient.ZkgmClient
  return yield* zkgmClient.execute(request)
}).pipe(
  Effect.provide(EvmZkgmClient.layerWithoutWallet),
  Effect.provide(Evm.WalletClient.Live({
    account: privateKeyToAccount("0xYourPrivateKey" as any),
    chain: holesky,
    transport: http(),
  })),
  Effect.provide(Evm.PublicClient.Live({ chain: holesky, transport: http() })),
  Effect.provide(ChainRegistry.Default),
)
```

### Recipe 3: Read Token Metadata on Aptos/Movement

```typescript
import { AptosConfig, Network } from "@aptos-labs/ts-sdk"
import { Effect } from "effect"
import { AptosPublicClient, createAptosPublicClient } from "@unionlabs/sdk/aptos/client"
import { readFaDecimals, readFaName, readFaSymbol } from "@unionlabs/sdk/aptos/fa"

Effect.runPromiseExit(
  Effect.gen(function*() {
    const config = new AptosConfig({
      fullnode: "https://aptos.testnet.bardock.movementlabs.xyz/v1",
      network: Network.CUSTOM,
    })
    const publicClient = yield* createAptosPublicClient(config)
    const tokenAddress = "0x19842c145c835df3bb4daa4fb3914bb9bdbd4ff4bcf53b53a014eb54d39e875e"

    const name = yield* readFaName(tokenAddress).pipe(
      Effect.provideService(AptosPublicClient, { client: publicClient }),
    )
    const decimals = yield* readFaDecimals(tokenAddress).pipe(
      Effect.provideService(AptosPublicClient, { client: publicClient }),
    )
    const symbol = yield* readFaSymbol(tokenAddress).pipe(
      Effect.provideService(AptosPublicClient, { client: publicClient }),
    )
    yield* Effect.log("Token:", name, symbol, decimals)
  }),
)
```

### Recipe 4: Read CW20 Token Balance on Cosmos

```typescript
import { Effect } from "effect"
import { CosmWasmClientContext, createCosmWasmClient } from "@unionlabs/sdk/cosmos/client"
import { readCw20Balance, readCw20TokenInfo } from "@unionlabs/sdk/cosmos/cw20"

Effect.runPromiseExit(
  Effect.gen(function*() {
    const client = yield* createCosmWasmClient(
      "https://rpc.elgafar-1.stargaze-apis.com",
    )
    const tokenAddr = "stars1qrde534d4jwk44dn7w7gu9e2rayutr7kqx8lfjhsk3rd7z9rzxhq2gh3lr"
    const walletAddr = "stars1qcvavxpxw3t8d9j7mwaeq9wgytkf5vwputv5x4"

    const info = yield* readCw20TokenInfo(tokenAddr).pipe(
      Effect.provideService(CosmWasmClientContext, { client }),
    )
    const balance = yield* readCw20Balance(tokenAddr, walletAddr).pipe(
      Effect.provideService(CosmWasmClientContext, { client }),
    )
    yield* Effect.log(`${info.name}: ${balance} ${info.symbol}`)
  }),
)
```

### Recipe 5: Execute a CosmWasm Contract

```typescript
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"
import { Effect } from "effect"
import { createSigningCosmWasmClient } from "@unionlabs/sdk/cosmos/client"
import { executeContract } from "@unionlabs/sdk/cosmos/contract"

Effect.runPromiseExit(
  Effect.gen(function*() {
    const wallet = yield* Effect.tryPromise(() =>
      DirectSecp256k1HdWallet.fromMnemonic("your mnemonic", { prefix: "stars" }),
    )
    const [account] = yield* Effect.tryPromise(() => wallet.getAccounts())
    const client = yield* createSigningCosmWasmClient(
      "https://rpc.elgafar-1.stargaze-apis.com",
      wallet,
    )
    return yield* executeContract(
      client,
      account.address,
      "stars1contract...",
      { transfer: { recipient: "stars1recipient...", amount: "1000000" } },
    )
  }),
)
```

### Recipe 6: Set Up a Union Validator

```bash
# 1. Initialize the node
uniond init my-validator --chain-id union-1

# 2. Replace genesis.json with the official one
# Download from the Union network repository

# 3. Set persistent peers and seeds in config.toml
# Edit ~/.union/config/config.toml

# 4. Start the node and wait for sync
uniond start

# 5. Create wallet
uniond keys add validator-wallet

# 6. Fund wallet with UNO tokens

# 7. Create validator transaction
uniond create-union-validator \
  --moniker "My Validator" \
  --commission-rate 0.10 \
  --commission-max-rate 0.20 \
  --commission-max-change-rate 0.01 \
  --min-self-delegation 1 \
  --from validator-wallet \
  --chain-id union-1
```

### Recipe 7: Set Up unionvisor for Automatic Upgrades

```bash
# 1. Prepare the bundle directory
mkdir -p /opt/union/bundle/versions/genesis
cp uniond /opt/union/bundle/versions/genesis/

# 2. Create meta.json
cat > /opt/union/bundle/meta.json << 'EOF'
{
  "binary_name": "uniond",
  "fallback_version": "genesis",
  "versions_directory": "versions"
}
EOF

# 3. Copy genesis.json into bundle
cp genesis.json /opt/union/bundle/

# 4. Initialize unionvisor
unionvisor --root /opt/union/unionvisor init \
  -b /opt/union/bundle \
  --moniker "my-node" \
  --seeds "seed1@ip:port"

# 5. Run the supervisor
unionvisor --root /opt/union/unionvisor run \
  -b /opt/union/bundle \
  -p 5000
```

### Recipe 8: Run a Voyager Relay Node

```bash
# 1. Generate a default config
voyager config default > voyager-config.json

# 2. Edit the config to add:
#    - Chain endpoints (RPC, gRPC, WebSocket)
#    - Signing keys for transaction submission
#    - Plugin configuration for your target chains

# 3. Start indexing events for the chains you want to relay
voyager -c voyager-config.json index union.union-1 --from latest --enqueue
voyager -c voyager-config.json index ethereum.1 --from latest --enqueue

# 4. Start the relayer
voyager -c voyager-config.json start
```

### Recipe 9: Monitor Relay Queue Health

```bash
# Check queue statistics
voyager -c config.json queue stats

# Check for failed messages
voyager -c config.json queue query-failed

# Investigate a specific failure
voyager -c config.json queue query-failed-by-id 42

# Clean up completed operations
voyager -c config.json queue truncate --done
voyager -c config.json queue vacuum --done
```

### Recipe 10: Predict a Wrapped Token Address

Use the `u` CLI to predict the address of a wrapped token on the destination chain:

```bash
u zkgm predict-wrapped-token <base-token> <channel-id>
```

Or use the TypeScript SDK:

```typescript
import { predictQuoteToken } from "@unionlabs/sdk/aptos/quote-token"
```

### Recipe 11: Batch Multiple Transfers in One Packet

```typescript
import { Instruction } from "@unionlabs/sdk/ucs03"
import { toHex } from "viem"

const batchInstruction = new Instruction.Batch({
  operand: [
    new Instruction.FungibleAssetOrder({
      operand: [/* transfer 1 params */],
    }),
    new Instruction.FungibleAssetOrder({
      operand: [/* transfer 2 params */],
    }),
  ],
})
```

### Recipe 12: Forward a Transfer Through an Intermediate Chain

```typescript
const forwardInstruction = new Instruction.Forward({
  operand: [
    0n,          // path (intermediate chain channel)
    0n,          // timeoutHeight
    1234567890n, // timeoutTimestamp
    new Instruction.FungibleAssetOrder({
      operand: [/* inner transfer params */],
    }),
  ],
})
```

### Recipe 13: Call a Remote Smart Contract (Multiplex)

```typescript
import { toHex } from "viem"

const multiplexInstruction = new Instruction.Multiplex({
  operand: [
    "0xTargetContractAddress",
    true,  // eureka flag
    "0xFallbackAddress",
    toHex("calldata for the smart contract"),
  ],
})
```

### Recipe 14: Query Voyager RPC for Chain State

```bash
# Get the latest height of a chain
voyager rpc --rpc-url http://localhost:7042 latest-height union.union-1

# Query a client's state
voyager rpc client-state union.union-1 07-tendermint-0

# Get IBC proof for verification
voyager rpc ibc-proof ethereum.1 18000000 "connections/connection-0"
```

### Recipe 15: Export Verification Key for On-Chain Verifier

```bash
# Export the ZK verification key
galoisd export-vk --output vk.json

# Generate a Solidity verifier contract from the VK
galoisd gen-contract --vk vk.json --output Verifier.sol
```

### Recipe 16: Check Sui Coin Metadata

```typescript
import { getFullnodeUrl } from "@mysten/sui/client"
import { Sui } from "@unionlabs/sdk-sui"
import { Effect, Logger } from "effect"

const program = Effect.gen(function*() {
  const meta = yield* Sui.readCoinMetadata("0x2::sui::SUI")
  yield* Effect.log("SUI metadata:", meta)

  const [name, symbol, decimals] = yield* Effect.all([
    Sui.getCoinName("0x2::sui::SUI"),
    Sui.readCoinSymbol("0x2::sui::SUI"),
    Sui.getCoinDecimals("0x2::sui::SUI"),
  ])
  yield* Effect.log("Granular:", { name, symbol, decimals })
}).pipe(
  Effect.provide(Sui.PublicClient.Live({
    url: getFullnodeUrl("mainnet"),
  })),
  Effect.provide(Logger.replace(
    Logger.defaultLogger,
    Logger.prettyLoggerDefault,
  )),
)
```

### Recipe 17: Read Sui Coin Balances

```typescript
import { Sui } from "@unionlabs/sdk-sui"
import { Effect } from "effect"

const balances = Effect.gen(function*() {
  const balance = yield* Sui.getCoinBalance(
    "0x2::sui::SUI",
    "0xYourSuiAddress",
  )
  yield* Effect.log("Balance:", balance)
})
```

### Recipe 18: Use Token Order v2 with Solve Intent

The `kind: "solve"` option enables market-maker settlement:

```typescript
const tokenOrder = yield* TokenOrder.make({
  source,
  destination,
  sender: "union1...",
  receiver: "0xRecipient",
  baseToken: "au",
  baseAmount: 10n,
  quoteToken: "0xWrappedTokenAddress",
  quoteAmount: 10n,
  kind: "solve",       // Market-maker fills the order
  metadata: "0x...",   // Solver-specific metadata
  version: 2,
})
```

### Recipe 19: Use Token Order v2 with Escrow

```typescript
import { parseEther } from "viem"

const tokenOrder = yield* TokenOrder.make({
  source,
  destination,
  sender: "0xSender",
  receiver: "0xReceiver",
  baseToken: "0xTokenAddress",
  baseAmount: parseEther("100"),
  quoteToken: "0xQuoteTokenOnDest",
  quoteAmount: parseEther("0.05"),
  kind: "escrow",       // Standard lock-and-mint
  metadata: undefined,
  version: 2,
})
```

### Recipe 20: Decode Hex Data with u CLI

```bash
# Decode hex-encoded data
u hex --decode 0x68656c6c6f

# Encode string to hex
echo "hello world" | u hex

# Pipe hex data for processing
u hex --decode 0x... | u zkgm make decode
```

### Recipe 21: Run a Multi-Node Testnet

```bash
# Start a multi-node testnet
uniond multi-node testnet \
  --v 4 \
  --output-dir ./testnet \
  --chain-id union-testnet-local
```

### Recipe 22: Manage Node Keys

```bash
# List keys
uniond keys list

# Export a key (encrypted)
uniond keys export my-key

# Import a key
uniond keys import my-key keyfile.json

# Delete a key
uniond keys delete my-key
```

### Recipe 23: Debug Node Issues

```bash
# Check node status
uniond status

# Debug address conversion
uniond debug addr union1...

# Debug raw bytes
uniond debug raw-bytes <hex>

# Debug pubkey
uniond debug pubkey <pubkey>
```

### Recipe 24: Print Deployment Configuration

```bash
# Print all deployments
u deployments print

# Print deployment for a specific chain
u deployments print union.union-1

# Print for Ethereum mainnet
u deployments print ethereum.1
```

### Recipe 25: Generate Vanity Addresses

```bash
# Generate a union address starting with "union1abc"
u vanity wallet union1abc

# Generate a CREATE3 address with a prefix
u vanity create3 0xDEAD
```

---

## 15. Advanced Usage

### Custom Effect-TS Layers

The SDK's Effect-TS architecture enables sophisticated composition. Create custom layers that combine multiple chain clients:

```typescript
import { Effect, Layer } from "effect"
import { Evm, EvmZkgmClient } from "@unionlabs/sdk-evm"
import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry"

// Compose a layer with both EVM and registry capabilities
const multiChainLayer = Layer.mergeAll(
  EvmZkgmClient.layerWithoutWallet,
  ChainRegistry.Default,
)

// Use ManagedRuntime for reusable program execution
import { ManagedRuntime, Logger, LogLevel } from "effect"

const { runPromiseExit } = ManagedRuntime.make(
  Logger.minimumLogLevel(LogLevel.Error),
)

runPromiseExit(yourProgram).then(exit => {
  console.log(JSON.stringify(exit, null, 2))
})
```

### Error Handling with Effect-TS

```typescript
const safeTransfer = createEvmToEvmFungibleAssetOrder({
  // ... transfer params
}).pipe(
  Effect.tapErrorCause(cause =>
    Effect.logError("Transfer failed with cause", cause),
  ),
  Effect.retry({ times: 3 }),
  Effect.timeout("30 seconds"),
)
```

> [!TIP]
> When developing custom integrations, use `Effect.tapErrorCause` to log full error details including the cause chain. This is invaluable for debugging cross-chain transfer failures.

### Voyager Plugin Development

Voyager plugins implement a standardized interface. Each plugin is a standalone Rust binary that communicates with the Voyager core via JSON-RPC:

- **Interest filter** -- declares which message types the plugin handles
- **Call handler** -- processes incoming plugin calls
- **Info endpoint** -- reports plugin metadata

See the Admin Guide for the complete plugin development reference.

### UCS03 (ZKGM) Protocol Details

The ZKGM protocol (UCS03) is Union's cross-chain messaging standard. It supports:

| Instruction Opcode | Name | Purpose |
|---|---|---|
| 0x03 | FungibleAssetOrder | Token transfers |
| 0x01 | Forward | Multi-hop routing |
| 0x02 | Multiplex | Smart contract calls |
| 0x00 | Batch | Multiple instructions in one packet |

Each instruction is ABI-encoded and wrapped in a ZKGM packet that includes sender, receiver, token info, and the instruction payload.

> [!IMPORTANT]
> When constructing cross-chain transfers, always use `UniversalChainId.make()` with the format `<ecosystem>.<chain-id>` (e.g., `"ethereum.1"`, `"union.union-1"`, `"osmosis.osmosis-1"`). This format is standardized across the Union SDK.

### Chain ID Format

Union uses a Universal Chain ID scheme across all its tooling:

| Format | Example | Ecosystem |
|---|---|---|
| `ethereum.<number>` | `ethereum.1`, `ethereum.11155111` | EVM (Ethereum, L2s) |
| `arbitrum.<number>` | `arbitrum.42161` | Arbitrum |
| `base.<number>` | `base.8453` | Base |
| `bsc.<number>` | `bsc.56` | BNB Smart Chain |
| `union.<name>` | `union.union-1` | Union |
| `osmosis.<name>` | `osmosis.osmosis-1` | Osmosis |
| `babylon.<name>` | `babylon.bbn-1` | Babylon |
| `sei.<name>` | `sei.pacific-1` | Sei |
| `berachain.<number>` | `berachain.80094` | Berachain |
| `bob.<number>` | `bob.60808` | Bob |
| `corn.<number>` | `corn.21000000` | Corn |
| `xion.<name>` | `xion.xion-mainnet-1` | Xion |

---

## 16. Troubleshooting

### Issue 1: SDK -- "Effect is not defined" or Module Import Errors

**Error:** `Cannot find module 'effect'` or `Effect is not defined`

**Cause:** The `effect` peer dependency is not installed.

**Solution:**
```bash
npm install effect
```

**Prevention:** Always install `effect` alongside `@unionlabs/sdk`.

---

### Issue 2: SDK -- BigInt Serialization Error

**Error:** `TypeError: Do not know how to serialize a BigInt`

**Cause:** `JSON.stringify` does not natively handle BigInt values.

**Solution:** Add BigInt serialization at the top of your script:
```typescript
if (typeof BigInt.prototype.toJSON !== "function") {
  BigInt.prototype.toJSON = function() {
    return this.toString()
  }
}
```

**Prevention:** Include this polyfill in your application entry point.

---

### Issue 3: SDK -- Service Layer Not Provided

**Error:** `MissingService: ViemPublicClientSource` (or similar)

**Cause:** An Effect-TS service dependency was not provided to the program.

**Solution:** Ensure all required service layers are provided via `Effect.provide()` or `Effect.provideService()`. Each transfer type requires specific layers:
- EVM: `Evm.PublicClient`, `Evm.WalletClient`, `ChainRegistry.Default`
- Cosmos: `Cosmos.Client`, `Cosmos.SigningClient`, `ChainRegistry.Default`
- Sui: `Sui.PublicClient`, `Sui.WalletClient`, `ChainRegistry.Default`

**Prevention:** Follow the examples in this guide, ensuring every `pipe()` chain includes all necessary `Effect.provide()` calls.

---

### Issue 4: Voyager -- "config file must be specified"

**Error:** `Error: config file must be specified`

**Cause:** The `-c` flag was omitted when running Voyager commands that require a config.

**Solution:**
```bash
voyager -c /path/to/config.json start
```

**Prevention:** Always pass `-c <config-path>` to Voyager commands (except `config default` and `config schema`).

---

### Issue 5: Voyager -- Plugin Not Picking Up Events

**Error:** Voyager starts but no events are processed.

**Cause:** The event-source plugin's interest filter does not match the chain's events, or the index operation was not enqueued.

**Solution:**
1. Verify plugins are loaded: `voyager -c config.json plugin list`
2. Check interest filters: `voyager -c config.json plugin interest <plugin-name>`
3. Enqueue an index operation: `voyager -c config.json index <chain-id> --from latest -e`

**Prevention:** Ensure your config includes event-source plugins for every chain you want to relay and that you have started indexing.

---

### Issue 6: unionvisor -- "cannot read bundle/meta.json"

**Error:** `cannot read bundle/meta.json`

**Cause:** The unionvisor bundle directory is missing `meta.json` or the path is incorrect.

**Solution:** Verify the bundle structure contains `meta.json`:
```bash
ls /path/to/bundle/
# Should show: genesis.json  meta.json  versions/
```

**Prevention:** Always create the bundle with the correct structure before running unionvisor.

---

### Issue 7: unionvisor -- "version was not found in bundle"

**Error:** `version was not found in bundle: /path/to/versions/vX.Y.Z/uniond`

**Cause:** The upgrade version binary is not present in the bundle's versions directory.

**Solution:**
```bash
mkdir -p /path/to/bundle/versions/vX.Y.Z
cp new-uniond-binary /path/to/bundle/versions/vX.Y.Z/uniond
chmod +x /path/to/bundle/versions/vX.Y.Z/uniond
```

**Prevention:** Pre-download upgrade binaries before the upgrade height is reached.

---

### Issue 8: unionvisor -- "permission denied when executing version"

**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/*/uniond
```

**Prevention:** Always set execute permissions when adding binaries to the bundle.

---

### Issue 9: drip -- "can't read config file"

**Error:** `can't read config file` or `invalid config file`

**Cause:** The config file path is wrong or the JSON is malformed.

**Solution:** Validate the config file:
```bash
python3 -c "import json; json.load(open('config.json'))"
```

**Prevention:** Use a JSON linter before starting drip.

---

### Issue 10: uniond -- Node Sync Stuck

**Error:** Node stops syncing or falls behind peers.

**Cause:** Insufficient peers, network issues, or inadequate hardware resources.

**Solution:**
1. Check peer count: `uniond status | jq '.NodeInfo.n_peers'`
2. Add more persistent peers to `config.toml`
3. Check disk space and I/O performance
4. Consider using state sync for faster catch-up

**Prevention:** Maintain a minimum of 10 persistent peers and monitor disk usage.

---

### Issue 11: SDK -- Transfer Times Out

**Error:** Transfer submitted but never completes on the destination chain.

**Cause:** The relayer (Voyager) may be down, the packet may be stuck in the queue, or the light client needs updating.

**Solution:**
1. Check the transfer status in the Explorer at [app.union.build](https://app.union.build)
2. If using a private relayer, check `voyager queue stats`
3. Wait for the SLA period (up to 3 hours for EVM-to-Cosmos)

**Prevention:** Use the public relayer infrastructure for production transfers. Monitor relayer health for private deployments.

---

### Issue 12: uniond -- "invalid chain-id"

**Error:** Transaction rejected with invalid chain ID.

**Cause:** The `--chain-id` flag does not match the connected node's chain ID.

**Solution:** Verify the node's chain ID:
```bash
uniond status | jq '.NodeInfo.network'
```
Use the returned value in your `--chain-id` flag.

**Prevention:** Always query the node status before submitting transactions.

---

### Issue 13: Cosmos SDK -- Gas Estimation Failed

**Error:** `out of gas in location: ...` or `insufficient fees`

**Cause:** Gas price too low or gas limit too small.

**Solution:**
```bash
uniond tx bank send ... --gas auto --gas-adjustment 1.5 --gas-prices 0.025au
```

For the SDK:
```typescript
{ gasPrice: GasPrice.fromString("100000au") }
```

**Prevention:** Use `--gas auto` with a gas adjustment of 1.3-1.5.

---

### Issue 14: Web UI -- Wallet Connection Fails

**Error:** Wallet does not connect or shows "unsupported chain."

**Cause:** The wallet does not have the Union chain configured, or the browser extension is outdated.

**Solution:**
1. Update your wallet extension to the latest version
2. For Keplr/Leap: the app should automatically suggest adding the Union chain
3. Clear browser cache and retry

**Prevention:** Keep wallet extensions updated.

---

### Issue 15: SDK -- Chain Not Found in Registry

**Error:** `ChainRegistry: chain not found for <chain-id>`

**Cause:** The Universal Chain ID is malformed or the chain is not in the default registry.

**Solution:** Verify the chain ID format matches the Universal Chain ID scheme:
```typescript
UniversalChainId.make("ethereum.1")     // correct
UniversalChainId.make("1")              // incorrect
UniversalChainId.make("mainnet")        // incorrect
```

**Prevention:** Always use the `<ecosystem>.<chain-id>` format.

---

### Issue 16: Voyager -- Database Lock Error

**Error:** `database is locked` in Voyager queue operations.

**Cause:** Multiple Voyager instances accessing the same queue database.

**Solution:** Ensure only one Voyager instance uses a given queue database directory. Stop any duplicate processes.

**Prevention:** Use unique working directories for each Voyager instance.

---

### Issue 17: Build -- Nix Build Fails

**Error:** Various build errors when using `nix build`.

**Cause:** Nix store corruption, outdated flake inputs, or missing system dependencies.

**Solution:**
```bash
nix flake update
nix build .#<package> --rebuild
```

**Prevention:** Pin flake inputs and use `nix develop` for a clean build environment.

---

### Issue 18: galoisd -- Health Check Fails

**Error:** `/health` endpoint returns unhealthy or times out.

**Cause:** The proving service is overloaded or crashed.

**Solution:**
```bash
galoisd query-stats-health
```
Restart galoisd if the service is unresponsive.

**Prevention:** Monitor memory usage (ZK proving is resource-intensive) and set up automatic restarts.

---

### Issue 19: SDK -- Incorrect Quote Token Address

**Error:** Transfer fails because the quote token does not exist on the destination chain.

**Cause:** The `quoteToken` address provided does not match the wrapped representation of the base token on the destination.

**Solution:** Use `predictQuoteToken()` or `u zkgm predict-wrapped-token` to determine the correct wrapped token address before constructing the transfer.

**Prevention:** Always predict the wrapped token address programmatically rather than hardcoding it.

---

### Issue 20: Voyager -- Queue Growing Without Processing

**Error:** Queue stats show growing message count but no progress.

**Cause:** The `start` command was not issued, or the appropriate plugins are not loaded.

**Solution:**
1. Verify Voyager is running with `start` (not just `index`)
2. Check plugin list for completeness
3. Review logs for plugin initialization errors

**Prevention:** Ensure all required plugins (event-source, client-update, transaction, proof, state, finality) are configured for each chain pair.

---

> [!WARNING]
> If you encounter stuck transfers or relayer issues, do not submit the same transfer multiple times. Check the packet status via the Explorer first. Duplicate submissions may lock additional funds.

---

### Issue 21: unionvisor -- Upgrade Binary Mismatch

**Error:** Node crashes after upgrade with consensus errors.

**Cause:** The wrong binary version was placed in the bundle.

**Solution:** Verify the version matches the governance proposal:
```bash
uniond version
# Compare with expected upgrade version
```

**Prevention:** Download binaries from official release channels and verify checksums.

---

### Issue 22: drip -- Rate Limit Exceeded

**Error:** Faucet request rejected.

**Cause:** Address has exceeded the `ratelimit_seconds` window.

**Solution:** Wait for the rate limit window to expire, then retry.

**Prevention:** Use a different testnet address if you need tokens immediately.

---

### Issue 23: SDK -- Viem Client Configuration Mismatch

**Error:** `Chain mismatch` or RPC errors during EVM transfers.

**Cause:** The viem client's chain configuration does not match the target network.

**Solution:** Ensure the `chain` parameter matches your RPC endpoint:
```typescript
import { holesky } from "viem/chains"

Evm.PublicClient.Live({
  chain: holesky,  // Must match the RPC network
  transport: http("https://rpc.17000.ethereum.chain.kitchen"),
})
```

**Prevention:** Always pair chain objects with their corresponding RPC URLs.

---

### Issue 24: uniond -- Pruning Causes Query Failures

**Error:** `store has been pruned` when querying historical state.

**Cause:** Aggressive pruning settings removed the requested block data.

**Solution:** If you need historical queries, use an archive node or adjust pruning settings in `app.toml`:
```toml
pruning = "nothing"  # Keep all historical state (high disk usage)
```

**Prevention:** For validator nodes, use `pruning = "default"`. For query nodes, use `pruning = "nothing"`.

---

### Issue 25: Voyager -- TLS/SSL Connection Errors to RPC

**Error:** TLS handshake failures when connecting to chain RPC endpoints.

**Cause:** Expired or invalid SSL certificates on the RPC endpoint, or system CA certificates are outdated.

**Solution:** Verify the RPC endpoint is accessible:
```bash
curl -v https://rpc.union-1.union.chain.kitchen/status
```
Update system CA certificates if needed.

**Prevention:** Use well-maintained public RPC endpoints and monitor their certificate expiry.

---

## 17. FAQ

**What chains does Union support?**
Union currently supports Ethereum (mainnet + testnets), Arbitrum, Base, Berachain, Bob, BSC, Corn, Osmosis, Sei, Babylon, Xion, Union itself, Sui, Starknet, and Movement/Aptos. The full list of mainnet and testnet channels is in the [Supported Chains](#13-supported-chains-and-channels) section.

**What is ZKGM?**
ZKGM (Zero-Knowledge General Message passing, also known as UCS03) is Union's cross-chain messaging protocol. It wraps transfer instructions, contract calls, batch operations, and multi-hop forwards into cryptographically verifiable packets that are relayed across chains.

**What is the difference between "escrow" and "solve" transfer kinds?**
`escrow` is a standard lock-and-mint mechanism where tokens are locked on the source chain and minted on the destination. `solve` enables market-maker settlement where a solver fills the order on the destination chain and claims the locked tokens on the source.

**What wallets work with the web UI?**
MetaMask and WalletConnect for EVM chains, Keplr and Leap for Cosmos chains, and Sui Wallet for Sui.

**How long do cross-chain transfers take?**
Cosmos-to-EVM transfers typically complete in ~20 minutes. EVM-to-Cosmos transfers depend on Ethereum finality and take up to ~3 hours. EVM-to-EVM transfers through L1 intermediaries take ~45 minutes.

**What is the `au` token?**
`au` is the native staking/gas token denomination on the Union chain (similar to `uatom` on Cosmos Hub or `uosmo` on Osmosis). The `union` prefix is used for bech32 addresses.

**Do I need to run my own relayer?**
No. Union operates public Voyager relayer infrastructure. However, you can run your own for increased reliability or to support custom routes.

**What is unionvisor?**
unionvisor is a process supervisor (similar to cosmovisor) that manages `uniond` binary upgrades. It monitors for on-chain upgrade proposals and automatically swaps the binary at the correct block height.

**How do I get testnet tokens?**
Use the faucet at [app.union.build/faucet](https://app.union.build/faucet) or interact with the drip service's GraphQL API directly.

**What is Effect-TS and why does the SDK use it?**
Effect-TS is a TypeScript library for building type-safe, composable programs with explicit dependency injection and error tracking. The Union SDK uses it to provide compile-time guarantees about which services (wallets, clients, registries) are available to each operation.

**Can I bridge NFTs with Union?**
Union's IBC infrastructure supports general message passing, which includes NFTs. The ZKGM protocol can encode arbitrary instruction types beyond fungible asset orders.

**What is the `u` CLI tool for?**
`u` is a developer utility for Union. It provides hex encoding/decoding, ZKGM instruction encoding, deployment info lookup, vanity address generation, and chain-specific debugging tools for Arbitrum, CometBFT, and Parlia.

**How do I check if a transfer succeeded?**
Use the Explorer at [app.union.build/explorer](https://app.union.build/explorer) to search by packet hash, transaction hash, or sender address. Programmatically, use the `response.waitFor()` method in the SDK.

**What are Union channels?**
Channels are bidirectional IBC communication paths between two chains. Each channel has an ID on each side. For example, Union channel 1 connects to Ethereum, and Ethereum channel 2 connects back to Union.

**Can I use the SDK in a browser?**
The core SDK packages (`@unionlabs/sdk`, `@unionlabs/sdk-evm`, etc.) are TypeScript/JavaScript and can be used in browser environments. The web UI at app.union.build is itself built on these same packages.

**What is the difference between TokenOrder v1 and v2?**
TokenOrder v2 (indicated by `version: 2` in SDK calls and the `tokenorder-v2` channel tag) is the current version. It supports enhanced features including solve/escrow modes, metadata fields, and improved token wrapping semantics. All current mainnet channels use v2.

**How do I monitor my validator?**
```bash
uniond status          # Node sync status
uniond query staking validator <operator-addr>   # Validator info
```
The web UI dashboard at app.union.build also provides validator monitoring.

**What RPC endpoints are available?**
Union provides public RPC endpoints in the format `https://rpc.<chain-id>.chain.kitchen`. For example:
- `https://rpc.union-1.union.chain.kitchen`
- `https://rpc.17000.ethereum.chain.kitchen`
- `https://rpc.bbn-1.babylon.chain.kitchen`

**How does Union's ZK bridge work?**
Union uses a BLS12-381 based zero-knowledge proof system (powered by galoisd and Gnark). Validators sign blocks with BLS keys, and galoisd generates succinct proofs of consensus that can be verified on any chain. This enables trust-minimized bridging without trusted intermediaries.

**What is the MPC ceremony for?**
The Multi-Party Computation ceremony generates the trusted setup parameters (structured reference string) for Union's ZK proving system. Each participant contributes randomness -- as long as at least one participant is honest, the parameters are secure.

**Can I run multiple Voyager instances?**
Yes, but each instance must use a separate queue database directory. Multiple instances can relay different chain pairs in parallel.

**What is the `chain.kitchen` domain?**
`chain.kitchen` hosts Union's public RPC infrastructure. It provides endpoints for all supported chains in the Union network.

**How do I debug a failed cross-chain transfer?**
1. Check the transaction hash on the source chain's explorer
2. Look up the packet in Union's Explorer at app.union.build
3. If using a private relayer, check `voyager queue query-failed`
4. Verify the channel is active and the light client is updated

**What is UCS05?**
UCS05 is Union's cross-chain contract call standard. It extends ZKGM (UCS03) with the ability to call smart contracts on destination chains, enabling cross-chain DeFi composability.

**Are there rate limits on the public RPC endpoints?**
Public RPC endpoints may have rate limits. For production applications, consider running your own full nodes or using dedicated RPC providers.

**What is CometBLS?**
CometBLS is Union's custom consensus module that extends CometBFT with BLS12-381 signature aggregation. It enables compact ZK proofs of validator consensus, which is the foundation of Union's trust-minimized bridging.

**What smart contract standards does Union use on EVM?**
Union deploys IBC handler contracts on EVM chains for packet processing. The ZKGM (UCS03) protocol is implemented in Solidity for EVM chains, Cairo for Starknet, Move for Aptos/Movement, and CosmWasm for Cosmos chains.

---

_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._
