Appchain Development on Cosmos SDK
The question "why not Ethereum L2" comes up first, and it needs an honest answer. Ethereum L2 (Arbitrum, Optimism, zkSync) gives you someone else's virtual machine, someone else's gas token, someone else's sequencer, someone else's rules. Cosmos SDK appchain — is your blockchain: own consensus, own gas token, own governance, own validation rules. The price — full responsibility for security and decentralization of validator set. For most applications, L2 is the right choice. For cases where you need full control over protocol economics, data sovereignty, or specialized consensus mechanisms — Cosmos SDK.
Cosmos SDK architecture
Modular structure
Cosmos SDK is built on modules. Each module is an isolated component with its own state (in IAVL tree), message types (Msgs), queries (Queries), and lifecycle hooks:
Application
├── x/auth — accounts and signatures
├── x/bank — token transfers
├── x/staking — validator set, delegation
├── x/distribution — staking rewards distribution
├── x/gov — on-chain governance
├── x/slashing — validator misbehavior penalties
├── x/ibc — IBC protocol
└── x/yourapp — your business logic
Your custom module lives in x/yourapp. This is where all your logic is.
Consensus: CometBFT (formerly Tendermint)
CometBFT — BFT consensus with instant finality. This is the key difference from PoW: if a block is committed — it's final, no fork risk. Finality latency: ~1–6 seconds depending on configuration and validator set size.
Limitations: BFT consensus requires >2/3 of voting stake online for liveness. At < 33% online — network stops (safety over liveness). This is a conscious trade-off.
Maximum practical validator set: 150–300 validators. More — network slows due to communication overhead in BFT round.
Custom module development
Module structure
x/
└── mymodule/
├── keeper/
│ ├── keeper.go — business logic, state access
│ ├── msg_server.go — transaction handlers
│ └── query_server.go — query handlers
├── types/
│ ├── msgs.go — message types
│ ├── genesis.go — initial state
│ └── params.go — module parameters (governance-managed)
├── module.go — module registration
└── proto/
└── mymodule/ — Protobuf definitions
Keeper and state management
State is stored in IAVL tree via KVStore. Keeper is the only access point to state:
type Keeper struct {
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
bankKeeper types.BankKeeper // dependency on bank module
authKeeper types.AccountKeeper
}
func (k Keeper) SetOrder(ctx sdk.Context, order types.Order) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&order)
store.Set(types.OrderKey(order.Id), bz)
}
func (k Keeper) GetOrder(ctx sdk.Context, id uint64) (types.Order, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.OrderKey(id))
if bz == nil {
return types.Order{}, false
}
var order types.Order
k.cdc.MustUnmarshal(bz, &order)
return order, true
}
Important: in Cosmos SDK there is no "transaction failed, state rolled back" in the EVM sense. If msg_server returns error, gas is burned but state unchanged. If returns nil — state changed and it's final. So all checks must come before state changes.
Msg Server: transaction handling
func (k msgServer) CreateOrder(
goCtx context.Context,
msg *types.MsgCreateOrder,
) (*types.MsgCreateOrderResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
// 1. Validation
if msg.Amount.IsZero() {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "amount cannot be zero")
}
// 2. Check balance and lock funds
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, msg.Creator, types.ModuleName, sdk.NewCoins(msg.Amount))
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, err.Error())
}
// 3. State change
order := types.Order{
Id: k.GetNextOrderID(ctx),
Creator: msg.Creator,
Amount: msg.Amount,
CreatedAt: ctx.BlockTime().Unix(),
}
k.SetOrder(ctx, order)
k.IncrementOrderID(ctx)
// 4. Events for clients
ctx.EventManager().EmitEvent(sdk.NewEvent(
types.EventTypeCreateOrder,
sdk.NewAttribute(types.AttributeOrderID, fmt.Sprintf("%d", order.Id)),
sdk.NewAttribute(types.AttributeCreator, msg.Creator),
))
return &types.MsgCreateOrderResponse{OrderId: order.Id}, nil
}
BeginBlock / EndBlock hooks
Powerful mechanism: execute logic every block without external trigger. Examples:
- Order matching — match orders at end of each block (DEX without matching gas)
- Liquidations — check position health and liquidate if needed
- Epoch transitions — staking epoch transitions, reward distribution
- Oracle price updates — aggregating data from validators
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
keeper.ProcessExpiredOrders(ctx)
keeper.MatchPendingOrders(ctx)
return []abci.ValidatorUpdate{}
}
This is fundamentally impossible in EVM without external keeper bots — no native cron there.
IBC: inter-blockchain communication
IBC (Inter-Blockchain Communication Protocol) — killer feature of Cosmos. Allows sending tokens and arbitrary data between chains without bridge (trustless, via light client verification).
ICS-20: token transfer
Simplest IBC use case — transfer tokens between chains:
// Send tokens from your chain to Osmosis
transfer := channeltypes.NewMsgTransfer(
"transfer", // port
"channel-0", // channel to Osmosis
sdk.NewCoin("uatom", amount),
sender,
receiver,
timeoutHeight,
timeoutTimestamp,
"memo",
)
Recipient in Osmosis sees token as ibc/HASH — "wrapped" version of your token. For "native" representation you need ICS-20 + custom denom alias.
Custom IBC packets
For sending arbitrary logic between chains — custom IBC packets:
// Send arbitrary data
packet := channeltypes.NewPacket(
modulePacketData, // your data, serialized to protobuf
sequence,
sourcePort, sourceChannel,
destPort, destChannel,
timeoutHeight, timeoutTimestamp,
)
Receiving chain verifies Merkle proof that packet was committed on sending chain — this is trustless, no oracle needed.
IBC latency: depends on block times of both chains and relayer speed. Usually 10–30 seconds for Cosmos-to-Cosmos transfer.
Relayer infrastructure
IBC packets don't deliver themselves — relayers needed. These are off-chain processes that read packets from one chain and submit them to another.
Two main implementations: Hermes (Rust, high performance) and Go relayer. For production — Hermes.
Hermes configuration for your chain → Osmosis:
[[chains]]
id = "yourchain-1"
rpc_addr = "http://localhost:26657"
grpc_addr = "http://localhost:9090"
key_name = "relayer"
[[chains]]
id = "osmosis-1"
rpc_addr = "https://osmosis-rpc.polkachu.com"
grpc_addr = "https://osmosis-grpc.polkachu.com:12590"
key_name = "relayer-osmosis"
Relayer should have tokens on both chains for gas. For mainnet — balance monitoring and auto-refill critical.
Governance and parameters
On-chain governance — one reason to choose Cosmos. Change protocol parameters through token holder voting:
// Module parameters, changeable via governance
type Params struct {
MinOrderSize sdk.Coin `json:"min_order_size"`
TradingFeeRate sdk.Dec `json:"trading_fee_rate"`
MaxOrdersPerBlock uint64 `json:"max_orders_per_block"`
}
Proposal → Deposit period → Voting period (usually 14 days) → Execution. If >50% of stake votes yes — parameters change automatically.
Testnet and launch
Localnet for development:
ignite chain serve # Ignite CLI — quick start
# or
./scripts/run_node.sh # custom initialization script
Public testnet:
- Genesis file with initial validators
- Faucet for test tokens
- Explorer (Mintscan, Ping.pub) for visibility
Mainnet launch:
- Genesis parameters — discussed publicly
- Gentx from initial validators (each creates create-validator transaction)
- Collect gentx, form genesis.json
- Synchronous node launch by validators at genesis time
Validator onboarding: need to attract sufficiently decentralized and reliable validators. For serious mainnet — minimum 20–30 independent validators with geographic and jurisdictional diversification.
Stack and tools
| Component | Technology |
|---|---|
| Framework | Cosmos SDK v0.50.x |
| Consensus | CometBFT v0.38.x |
| Language | Go 1.21+ |
| Scaffolding | Ignite CLI |
| Protobuf | buf + cosmos-proto |
| Testing | Go testing + simapp |
| Explorer | Mintscan (Cosmostation) or custom |
| Relayer | Hermes (Informal Systems) |
| Monitoring | Prometheus + Grafana + Cosmos-specific dashboards |
Development timeline for custom appchain with one-two business modules, IBC, governance: 4–7 months to testnet, +2–3 months to mainnet.







