MHDA MultiChain Hierarchical Deterministic Address
A URN-based descriptor for blockchain HD addresses, compatible with RFC 8141. A single string captures everything needed to identify a derived address: network, derivation scheme, path, signature curve, encoding format, prefix and suffix conventions, and an optional wallet context.
A Taproot address on Bitcoin, BIP-86 path, bech32m:
An EVM address bound to a wallet context:
One string for a derived address
MHDA unifies the encoding of derivation paths, signature curves and address formats across EVM,
Bitcoin, Cosmos, Solana, XRP Ledger, Stellar, NEAR, Aptos, Sui, Cardano, Algorand and TON. The chain
identity is the (nt, ci) pair; the SLIP-44 coin type is an optional ct
annotation, because for HD addresses the coin already lives in the derivation path.
| Key | Component | Required | Default |
|---|---|---|---|
nt | Network type | yes | – |
ci | Chain ID | yes | – |
ct | Coin type (SLIP-44) | no | none (metadata) |
dt | Derivation type | no | root |
dp | Derivation path | no | empty when dt=root |
aa | Address algorithm | no | per-network default |
af | Address format | no | per-network default |
ap | Address prefix | no | none |
as | Address suffix | no | none |
wt | Wallet type | no | none |
wi | Wallet ID | no | none |
Canonical order, any input order
The chain identity comes first, so a chain key is always a strict prefix of the URN. Parsers accept any order on input; optional components are emitted only when set.
Exact round-trip
For canonical input Parse(s).String() == s. A short form round-trips to the same short form, a long form to the same long form. Hardening markers ', H and h are all accepted.
Wallet domain
wt names the client or protocol (web3, tonconnect), wi the wallet instance, e.g. a UUID or an HD root key fingerprint. Setters reject :, ?, # and whitespace.
Supported networks
Every network registers its algorithms, formats and derivation types. Strict validation requires the
resolved combination to be in this matrix; root, the non-HD form, is accepted on every network.
nt |
SLIP-44 | Algorithms | Formats | Derivations | Defaults |
|---|---|---|---|---|---|
| bitcoin | 0 | secp256k1 | p2pkh, p2sh, p2wpkh, p2wsh, p2tr, bech32, bech32m | bip32, bip44, bip49, bip84, bip86 | secp256k1 |
| evm | 60 | secp256k1 | hex | bip32, bip44 | secp256k1, hex |
| avalanche | 9000 | secp256k1 | hex, bech32 | bip44 | secp256k1 |
| tron | 195 | secp256k1 | base58 | bip44 | secp256k1, base58 |
| cosmos | 118 | secp256k1, ed25519 | bech32 | bip44, cip11 | secp256k1, bech32 |
| solana | 501 | ed25519 | base58 | slip10 | ed25519, base58 |
| xrpl | 144 | secp256k1, ed25519 | base58 | bip44 | secp256k1, base58 |
| stellar | 148 | ed25519 | strkey | slip10 | ed25519, strkey |
| near | 397 | ed25519, secp256k1 | hex | slip10, bip44 | ed25519, hex |
| aptos | 637 | ed25519, secp256k1 | hex | slip10, bip44 | ed25519, hex |
| sui | 784 | ed25519, secp256k1, secp256r1 | hex | slip10, bip54, bip74 | ed25519, hex |
| cardano | 1815 | ed25519 | bech32, base58 | cip1852 | ed25519, bech32 |
| algorand | 283 | ed25519 | base32 | slip10 | ed25519, base32 |
| ton | 607 | ed25519 | base64url, hex | slip10 | ed25519, base64url |
Derivation types: root, bip32, bip44, bip49,
bip54, bip74, bip84, bip86, slip10
(variable length), cip11, cip1852, zip32. Each path is checked
against the template of its type. Per-network notes are in
SPEC §2.
MHDA URNs by network
| Network | Example URN |
|---|---|
| Ethereum | urn:mhda:nt:evm:ci:1:dt:bip44:dp:m/44'/60'/0'/0/0 |
| Bitcoin (BIP-86) | urn:mhda:nt:bitcoin:ci:bitcoin:dt:bip86:dp:m/86'/0'/0'/0/0:af:bech32m:ap:bc1p |
| Solana | urn:mhda:nt:solana:ci:mainnet:dt:slip10:dp:m/44'/501'/0'/0' |
| Stellar | urn:mhda:nt:stellar:ci:mainnet:dt:slip10:dp:m/44'/148'/0' |
| Sui (ed25519) | urn:mhda:nt:sui:ci:mainnet:dt:slip10:dp:m/44'/784'/0'/0'/0' |
| Cardano | urn:mhda:nt:cardano:ci:mainnet:dt:cip1852:dp:m/1852'/1815'/0'/0/0 |
| Cosmos | urn:mhda:nt:cosmos:ci:cosmoshub:dt:cip11:dp:m/44'/118'/0'/0/0 |
| Algorand | urn:mhda:nt:algorand:ci:mainnet non-HD |
| TON | urn:mhda:nt:ton:ci:mainnet non-HD, friendly base64url by default |
| EVM short form | urn:mhda:nt:evm:ci:1 defaults: secp256k1, hex |
| With metadata | urn:mhda:nt:evm:ci:1:ct:60 optional SLIP-44 annotation |
| Wallet-bound | urn:mhda:nt:ton:ci:mainnet:wt:tonconnect:wi:c0a8f2d4-3b6e-4a51-9c7d-2f8e1a0b5c93 |
What the libraries give you
Lenient and strict parsing
Lenient parsing checks structure: prefix, known keys and values, no duplicates, paths that match their type. Strict parsing also checks the (algorithm, format, derivation) combination against the network matrix.
Chain keys
nt:evm:ci:1 is itself a parseable key for maps, caches and hashes. Chain keys are canonical-only: a pre-1.1 key that carries ct fails loudly instead of being reinterpreted.
Content addressing
Hash256 and NSSHash256 give a SHA-256 of the canonical form for deduplication and content addressing. SHA-1 variants are kept only for stable identifiers.
Codecs out of the box
In Go, Address implements TextMarshaler, so JSON, XML and YAML encoding work unchanged; MarshalText writes the canonical URN.
Typed errors
Every failure wraps an exported sentinel: errors.Is(err, mhda.ErrIncompatible) in Go, parse_error::code() in C++. Messages are for people, codes are the contract.
Fuzzed and sanitised
Go fuzz targets for URNs, NSS and paths. The C++ port runs 139 test cases and about 11,000 randomised iterations under AddressSanitizer, UBSan and LeakSanitizer.
Install and quick start
Go
go get github.com/censync/go-mhdaimport (
"errors"
"fmt"
mhda "github.com/censync/go-mhda"
)
func main() {
// Lenient parsing: structural validation only.
addr, err := mhda.ParseURN("urn:mhda:nt:evm:ci:1")
if err != nil {
panic(err)
}
fmt.Println(addr.Chain().NetworkType()) // evm
fmt.Println(addr.Algorithm()) // secp256k1 (default for evm)
fmt.Println(addr.Format()) // hex (default for evm)
// Strict parsing: also checks the (network, algorithm, format,
// derivation) combination is in the known-good compatibility matrix.
if _, err := mhda.ParseURNStrict("urn:mhda:nt:evm:ci:1:aa:ed25519"); err != nil {
if errors.Is(err, mhda.ErrIncompatible) {
fmt.Println("evm + ed25519 rejected, as expected")
}
}
// The chain identity (nt, ci) is itself a parseable key.
key := addr.Chain().Key() // "nt:evm:ci:1"
chain, _ := mhda.ChainFromKey(key)
_ = chain
// Optional wallet context: client type + wallet instance id.
wallet, _ := mhda.ParseURN("urn:mhda:nt:evm:ci:1:wt:web3:wi:5f2a8c31")
fmt.Println(wallet.WalletType(), wallet.WalletId()) // web3 5f2a8c31
}Go 1.18+. API reference on pkg.go.dev.
C++17
include(FetchContent)
FetchContent_Declare(mhda
GIT_REPOSITORY https://github.com/censync/mhda.git
GIT_TAG v1.1.0
)
FetchContent_MakeAvailable(mhda)
target_link_libraries(my_app PRIVATE mhda::mhda)#include <iostream>
#include "mhda/mhda.hpp"
int main() {
using namespace mhda;
// Lenient parsing: structural validation only.
auto addr = parse_urn("urn:mhda:nt:evm:ci:1");
std::cout << addr.get_chain().network().str() << "\n"; // evm
std::cout << addr.resolved_algorithm().str() << "\n"; // secp256k1
std::cout << addr.resolved_format().str() << "\n"; // hex
// Strict parsing also checks the (network, algorithm, format, derivation)
// combination is in the known-good compatibility matrix.
try {
parse_urn_strict("urn:mhda:nt:evm:ci:1:aa:ed25519");
} catch (const parse_error& e) {
if (e.code() == error_code::incompatible) {
std::cout << "evm + ed25519 rejected, as expected\n";
}
}
// Hashing for content-addressing or deduplication.
auto bip = parse_urn("urn:mhda:nt:evm:ci:1:dt:bip44:dp:m/44'/60'/0'/0/0");
std::cout << bip.hash256() << "\n"; // SHA-256 hex
}C++17, CMake 3.14+, no external runtime dependencies. Also installable with cmake --install and find_package(mhda).
GitHub repositories and packages
go-mhda is the normative reference: the specification lives there and changes land there first. mhda is a faithful C++17 port that keeps the URN format and validation rules bit-identical, so a URN produced by either implementation parses and round-trips through the other. Both are MIT-licensed; the current release is 1.1.0.
| Language | GitHub repository | Package | Install | Release |
|---|---|---|---|---|
| Goreference implementation and specification | censync/go-mhda | pkg.go.dev github.com/censync/go-mhda |
go get github.com/censync/go-mhda |
v1.1.0 |
| C++17port of go-mhda | censync/mhda | CMake mhda::mhdaGitHub Releases |
CMake FetchContent or find_package(mhda) |
v1.1.0 |
Documentation
- Specification: components, networks, derivation types, formats, validation rules, errors, concurrency and known limitations.
- Release notes 1.1.0: URN grammar 1.1 and migration from 1.0.
- C++ changelog and the Go → C++ API mapping.
Version 1.1
URN grammar 1.1 (4 July 2026) makes (nt, ci) the chain identity, turns the SLIP-44 coin
type into optional metadata, adds the wallet domain and renames network types to the commonly
accepted names (bitcoin, tron, solana…). It is a
format-changing release: strings produced by 1.0.0 do not round-trip unchanged, and the release
notes describe the migration.
