Bech32 Addresses in Cosmos, Kaia, and Beyond: A Cross-Chain Primer
The first time you look at a block explorer for a Cosmos chain, the prefixes hit you in a confusing order. Someone sends you an address that starts with cosmos1abc..., you paste it into Mintscan and it works. Then a friend on Osmosis sends osmo1abc... with what looks like the exact same tail, and you wonder if they typoed it. They did not. It really is the same wallet, viewed through a different HRP. Welcome to Bech32.
I lost an afternoon once chasing an IBC transfer that "disappeared" between Osmosis and Cosmos Hub. The funds were never lost — they had just landed under the Hub's prefix and I was searching the Osmosis explorer for an osmo1... address. That day I learned that Cosmos addresses, Kaia addresses, and IBC denoms all live in the same encoding family, and that once you know the rules, cross-chain debugging stops feeling like witchcraft.
What Bech32 actually is
Bech32 is an address encoding format defined in BIP-0173, originally designed for Bitcoin SegWit addresses in 2017. The format was picked up by the Cosmos ecosystem as its canonical address representation, and then by a growing list of chains outside the Cosmos SDK that wanted human-readable address prefixes without sacrificing error detection.
A Bech32 string has three parts, always in the same order:
- HRP (Human-Readable Part). One to 83 lowercase ASCII characters. On Cosmos Hub this is
cosmos; on Osmosisosmo; on Junojuno; on Kaiakaia; on XPLAxpla. - Separator. A single character, always
1. It is the only character that cannot appear in the HRP, which makes parsing unambiguous even when the prefix itself contains digits. - Data + checksum. A string of characters from the 32-letter Bech32 alphabet (
qpzry9x8gf2tvdw0s3jn54khce6mua7l). The last six characters are the BCH checksum.
The alphabet is the interesting part. The designers deliberately excluded 1, b, i, and o because those are the characters most often misread when a human transcribes an address from paper to screen. Everything that survives is visually distinct. That design choice is why Bech32 addresses tend to look slightly weird compared to hex — the letter set is smaller on purpose.
The six-character checksum is the reason Bech32 earns its reputation. It uses a BCH code over GF(32) that is guaranteed to detect any single error (substitution, insertion, or deletion of one character), and detects up to four errors with probability greater than one minus 2-30. Practically, if you mistype a character, the wallet will refuse the address. The checksum does not require calling a server; it is pure math over the data portion combined with the HRP, so any wallet can validate it offline in microseconds.
Worth noting: the HRP is folded into the checksum calculation, not just prepended to the encoded bytes. If you take a valid cosmos1... string and surgically swap the prefix to osmo without recomputing, the result fails checksum and every decoder rejects it. That is why prefix conversion has to be a real decode-and-re-encode round-trip rather than a string substitution — a detail that looks pedantic until you debug a tool that gets it wrong.
Why Cosmos chains share the same key across prefixes
Here is the core insight that trips up newcomers. A Cosmos SDK address is not a blockchain identity — it is an encoding of a public key hash. The pipeline is:
secp256k1 private key ↓ derive secp256k1 public key (33 bytes, compressed) ↓ sha256 then ripemd160 20-byte address hash ↓ bech32 encode with HRP cosmos1... OR osmo1... OR juno1...
The 20-byte address hash is the real identity. The HRP is just a label for which chain you want to display it under. If you own the private key, you own every Bech32 representation of that key, across every chain that uses the same secp256k1 derivation scheme. That is why a Keplr wallet shows you the same balance-ready address under different prefixes on every Cosmos SDK chain it supports — the key never changes, only the label does.
This also means you can convert between prefixes mechanically. Decode the source Bech32 string, ignore the source HRP, take the 20 bytes of data, re-encode with the target HRP and a fresh checksum. No signing, no RPC call, no permission required. The Bech32 converter in BeautiCode does exactly this: paste cosmos1..., pick target prefix osmo, get back the matching Osmosis address. It is the single most useful sanity check when someone sends you an address on "the wrong chain".
A few real prefixes worth memorizing if you work in Cosmos: cosmos (Cosmos Hub), osmo (Osmosis), juno (Juno), stars (Stargaze),akash (Akash), secret (Secret Network), kujira (Kujira), celestia (Celestia), dydx (dYdX v4), neutron (Neutron). Same key shape, different labels.
Kaia and XPLA: the dual-representation trick
Some networks live between two worlds. Kaia (the chain formed by the Klaytn + Finschia merger) and XPLA (Com2uS' gaming chain built on Cosmos SDK with Ethermint) both expose each account under two addresses at once: an EVM-style hex address that starts with 0x, and a Bech32 address that starts with kaia1 or xpla1. The two forms point to the same 20-byte hash.
The reason is historical. EVM chains hash a public key to 20 bytes and display the result in hex. Cosmos SDK chains hash a public key to 20 bytes and display the result in Bech32. Dual-chain networks adopted Ethermint, which uses the Ethereum derivation (secp256k1 → keccak256 → take last 20 bytes) but then offers a Bech32 view of that same 20 bytes for Cosmos-native tooling like IBC, governance, and delegator workflows.
# Kaia: the same account, two ways 0x742d35Cc6634C0532925a3b844Bc454e4438f44e kaia1ap...e4e (bech32, same 20 bytes, different encoding)
Which form you use depends on which side of the network you are talking to. Ethers.js and MetaMask want hex. Keplr, IBC relayers, and Cosmos governance UIs want Bech32. If you get a receipt from a Kaia dApp and want to check the same transaction in a Cosmos-style explorer, you need to convert. The wallet address converter handles both directions: paste the hex and it gives you the Bech32 form, or paste kaia1... and it extracts the hex.
Important caveat: this trick only works when the chain uses the Ethermint (or functionally identical) derivation. A plain Cosmos SDK chain like Osmosis uses secp256k1 + sha256 + ripemd160 and has no meaningful hex twin. A plain EVM chain like Ethereum uses keccak256 and has no meaningful Bech32 twin. Dual-representation is a property of chains that deliberately bridge the two encodings, not an inherent feature of Bech32.
Bech32 vs Bech32m: where Taproot fits in
Bech32 had a subtle flaw in its original checksum constant that made insertions of the character q at the end of the data portion undetectable in some cases. The Bitcoin community fixed it in BIP-0350 by defining a variant called Bech32m, which changes a single constant in the checksum polynomial.
The practical rules after BIP-0350:
- Bitcoin SegWit v0 addresses (the
bc1q...P2WPKH / P2WSH form) still use plain Bech32. - Bitcoin SegWit v1 addresses (Taproot,
bc1p...) use Bech32m. - Cosmos and friends stayed on plain Bech32. There was no upgrade moment equivalent to Taproot, and migrating the whole ecosystem to Bech32m would have broken every existing address.
When you are writing validation code, pick based on the target network. A generic "Bech32 validator" that only checks for plain Bech32 will reject valid Taproot addresses. A validator that only checks for Bech32m will reject everything in Cosmos. Good libraries (@scure/base, bech32, cosmjs) expose both variants and make you name the one you want.
IBC denom hashes and how they trace back
Once your wallet starts holding IBC-bridged tokens, the denom strings get strange fast. Your Osmosis balance might show ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 and nothing else. What chain is that token from? What was its original denom?
The hash is not random. IBC defines the derived denom for a bridged token as:
trace = "transfer/channel-0/uatom"
denom = "ibc/" + uppercase(hex(sha256(trace)))
= "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"
# That particular hash is ATOM on Osmosis, bridged from Cosmos Hub
# over channel-0.The trace is a slash-separated path of the hops the token took, ending in its original denom on the source chain. Single-hop is most common (transfer/channel-141/uosmo on Cosmos Hub, for example), but multi-hop paths do exist: transfer/channel-X/transfer/channel-Y/uosmo means the token was bridged A → B → C and kept its trace.
Going the other direction — turning an ibc/... hash back into its human-readable trace — requires a lookup because sha256 is one-way. Explorers and relayer operators maintain a table of known denoms. When you paste a hash into the Cosmos denom parser, it checks the hash against the known registry and returns the trace, the source chain, the channel, and the native denom. If the hash is not in the registry, you can also feed it a candidate trace and ask it to verify by recomputing the sha256 — which is how you confirm a new bridged asset.
One quirk that bites operators: the trace string is case-sensitive and channel names must match exactly. transfer/channel-0/uatom and transfer/channel-00/uatom hash to different values. If your relayer produces a denom that nobody recognizes, the first thing to check is whether you got the channel number right.
Validating cross-chain input
Airdrops and bridge UIs are the places where address validation actually matters. A user pastes an address, your form has to decide: is this valid, and is it for the chain I am claiming on? Bech32 makes this easier than hex because the HRP tells you the intended network directly.
A pragmatic validation flow for a Cosmos airdrop form:
- Parse the string with a Bech32 decoder. If decoding fails, reject with a clear error.
- Extract the HRP. If your airdrop is for Cosmos Hub holders, the HRP must be
cosmos. If the user pastedosmo1..., offer to convert it automatically rather than rejecting outright — they probably copied from Keplr on the wrong tab. - Confirm the data length. Cosmos SDK standard accounts encode 20 bytes. Modules and multi-sig accounts encode 32 bytes. If the length is neither, something is wrong.
- Recompute and check the checksum. Done by the decoder in step 1, but worth naming.
The multi-chain address validator runs this loop for you. You paste an arbitrary address string and it tells you which chain it belongs to (by matching HRP against a registry), whether the checksum is valid, and whether the data length matches the expected account type. For forms that have to accept multiple chains — a unified bridge UI, a cross-chain claim page — it replaces a pile of per-chain regex rules with one call.
A realistic scenario: retrying a stuck IBC transfer
Here is the kind of problem Bech32 tooling actually solves. Suppose you sent 100 ATOM from Osmosis back to Cosmos Hub. The transfer looked fine in Keplr, but after 30 minutes the funds have not arrived on the Hub and the IBC packet is marked as timed out in Mintscan. How do you debug it?
Step 1: read the IBC packet on Osmosis. It contains a source channel (say channel-0), a sender (osmo1abc...), a receiver on the Hub (cosmos1xyz...), and a denom. If the denom is something like ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2, paste it into the denom parser. It should resolve to transfer/channel-0/uatom, meaning the token is ATOM coming from the Hub via channel 0.
Step 2: confirm the receiver. If you pasted the Osmosis address as the receiver by mistake, the Hub cannot resolve it and the packet times out. Run the receiver through the Bech32 converter targeting cosmos. If it already is cosmos1xyz..., you have the right destination. If it is osmo1xyz..., that was the bug.
Step 3: once the timeout is acknowledged on the source chain, the funds come back to your Osmosis address. Build a fresh IBC transfer with the corrected Bech32 receiver and send again. The whole investigation takes a few minutes once you know the flow — denom parser to identify the asset, Bech32 converter to sanity-check the destination, explorer to watch the retry clear.
I have run this exact loop on three different IBC incidents. The first time it took me most of a day. Now it takes under ten minutes because I know which tools to reach for first.
Common mistakes
Checksum typos
A single mistyped character at the end of a Bech32 address invalidates the checksum and every well-behaved wallet will reject it. Good news: the funds are not at risk, because the transaction cannot be built in the first place. Bad news: users sometimes hand-copy an address from a printout and get one character wrong, and they assume the wallet is broken. If your UI sees a checksum failure, say so plainly ("checksum mismatch — address may have a typo") instead of a generic "invalid address".
Similar prefix, different curve
Injective uses injas its HRP and looks like a normal Cosmos SDK chain from the outside, but its accounts are derived using Ethereum's secp256k1 + keccak256 scheme, not the classic Cosmos sha256 + ripemd160. That means an inj1... address has an EVM-style 20-byte hash underneath, and a plain cosmos1... address does not. You cannot derive one from the other by swapping HRPs. The Bech32 envelope is the same; the thing inside is different.
Other chains in this ETH-under-Cosmos category: Evmos, Kava (in EVM mode), Canto, Kaia, XPLA. When you see an address from one of these, do not assume a Bech32-to-Bech32 conversion with another Cosmos prefix will land in the same account. It will land in a completely different account that happens to share the encoded bytes.
LUNA and LUNC post-upgrade confusion
After the Terra collapse in 2022, the ecosystem split into Terra Classic (LUNC, keeping the original terra1... prefix) and Terra 2.0 (new chain, also using terra1...). The HRP is the same but the chains are independent — your old Terra Classic address on the new Terra 2.0 chain is a fresh empty account that happens to share the prefix, and vice versa. Tooling from that era often conflates them. When working with any pre-2022 Terra material, always check which chain the explorer is pointing at before trusting a balance.
Case sensitivity
Bech32 is defined as all-lowercase or all-uppercase, never mixed. Most wallets display lowercase. If you paste a mixed-case string, a strict validator will reject it. A lax validator might normalize and accept, but do not rely on that. Keep everything lowercase in storage and display.
Truncating addresses in logs
A habit carried over from Ethereum tooling is to log addresses as cosmos1abc...wxyz with a middle ellipsis. On EVM chains this is fine because the address is a simple hex hash. On Bech32 it strips away the checksum, which is the single most useful piece of information the string contains. When something is broken and you want to paste an address from a log into a converter, a truncated form is useless. Log the full address in diagnostic output and only truncate in user-facing UI.
Assuming 20 bytes everywhere
Cosmos SDK standard user accounts encode a 20-byte hash, but the same chain uses 32-byte encodings for module accounts (cosmos1... with a longer tail) and for interchain accounts. If your validator hard-codes a 20-byte length check, it will reject perfectly valid addresses like the community pool or an ICA controller. The safer rule is to accept either 20 or 32 bytes and flag anything else as suspect.
Wrap up
Bech32 addresses started as a Bitcoin convenience and ended up as the connective tissue of the Cosmos-adjacent multichain world. Once you see the three parts — HRP, separator, data+checksum — and internalize that the HRP is only a label, the rest of the ecosystem gets much less intimidating. Cross-prefix conversions are free. IBC denoms are just sha256 hashes of a path. Dual-chain networks like Kaia and XPLA give you two views of the same twenty bytes.
The BeautiCode toolkit for this kind of work:
- Bech32 converter — swap HRPs across Cosmos SDK chains.
- Wallet address converter — flip between EVM hex and Bech32 on Kaia, XPLA, and other dual-representation chains.
- Cosmos denom parser — turn
ibc/...hashes back into traces and source chains. - Multi-chain address validator — one-call validation across dozens of prefixes.
The single best habit I picked up after a year of IBC debugging: when in doubt, decode. Every Bech32 string is self-describing once you strip the HRP and look at the bytes underneath. Most of what looks like a cross-chain mystery is just two different labels on the same twenty bytes.
Related Tools
Bech32 Address Converter
Convert between Cosmos-style Bech32 prefixes (cosmos1…, osmo1…, juno1…) while preserving the underlying key.
Wallet Address Format Converter
Convert between EVM hex and Bech32 (Kaia, XPLA, Cosmos prefix) formats for dual-chain wallets.
Cosmos IBC Denom Parser
Parse IBC denom hashes (ibc/…) back to the channel, port, and base denom of the originating chain.
Multi-chain Address Validator
Auto-detect and validate BTC, ETH, SOL, Cosmos and dozens of other blockchain addresses in one go.
Related Articles
How to Generate Secure Passwords in 2026: A Complete Guide
Learn why strong passwords matter and how to generate secure passwords using entropy, length, and complexity. Includes practical tips and free tools.
2025-12-15 · 8 min readData FormatsJSON vs YAML: When to Use What — A Developer's Guide
Compare JSON and YAML formats with syntax examples, pros and cons, and use case recommendations for APIs, configs, and CI/CD pipelines.
2025-12-28 · 10 min read