@@ -12,28 +12,37 @@ MHDA is a URN-based descriptor for blockchain HD addresses, with
1212[ RFC 8141] ( https://datatracker.ietf.org/doc/rfc8141/ ) compatibility.
1313
1414A single string captures everything needed to identify a derived address:
15- network, derivation scheme, path, signature curve, encoding format and any
16- prefix/suffix conventions.
15+ network, derivation scheme, path, signature curve, encoding format, any
16+ prefix/suffix conventions, and an optional wallet context .
1717
1818```
19- urn:mhda:nt:btc:ct:0 :ci:bitcoin:dt:bip86:dp:m/86'/0'/0'/0/0:af:bech32m:ap:bc1p
19+ urn:mhda:nt:bitcoin :ci:bitcoin:dt:bip86:dp:m/86'/0'/0'/0/0:af:bech32m:ap:bc1p
2020```
2121
22- Supported networks: Bitcoin, EVM, Avalanche, Tron, Cosmos, Solana, XRP,
22+ Supported networks: Bitcoin, EVM, Avalanche, Tron, Cosmos, Solana, XRP Ledger ,
2323Stellar, NEAR, Aptos, Sui, Cardano, Algorand, TON.
2424
25+ The chain identity is the ` (nt, ci) ` pair; the SLIP-44 coin type is an
26+ optional ` ct ` metadata component (for HD addresses the coin already lives in
27+ the derivation path). The optional wallet domain (` wt ` /` wi ` ) binds an address
28+ to a client type and a wallet instance:
29+
30+ ```
31+ urn:mhda:nt:evm:ci:1:dt:bip44:dp:m/44'/60'/0'/0/0:wt:web3:wi:5f2a8c31
32+ ```
33+
2534## Status
2635
27- - Version: ** 1.0 .0**
36+ - Version: ** 1.1 .0**
2837- Standard: ** C++17** , no external runtime dependencies
29- - Tests: ** 71 ** unit + fuzz-equivalent stress cases (≈11 000 randomised
38+ - Tests: ** 139 ** unit + fuzz-equivalent stress cases (≈11 000 randomised
3039 iterations), passing under ` -fsanitize=address,undefined,leak `
3140- Compilers verified: GCC 11.4 (Ubuntu 22.04), Clang 14 (when libstdc++ is
3241 available); the CI matrix runs Linux + macOS, Release + Debug
3342- Warning policy: clean under `-Wall -Wextra -Wpedantic -Wshadow -Wconversion
3443 -Wsign-conversion -Werror`
35- - API surface frozen at ** 0.1.0 ** ; binary stability is not yet guaranteed
36- across pre-1.0 minor versions
44+ - Binary stability is not yet guaranteed across minor versions; 1.1.0
45+ changes the URN grammar (see [ CHANGELOG.md ] ( ./CHANGELOG.md ) )
3746
3847## Building
3948
@@ -69,7 +78,7 @@ target_link_libraries(my_app PRIVATE mhda::mhda)
6978include(FetchContent)
7079FetchContent_Declare(mhda
7180 GIT_REPOSITORY https://github.com/censync/mhda.git
72- GIT_TAG v1.0 .0
81+ GIT_TAG v1.1 .0
7382)
7483FetchContent_MakeAvailable(mhda)
7584target_link_libraries(my_app PRIVATE mhda::mhda)
@@ -85,34 +94,40 @@ int main() {
8594 using namespace mhda;
8695
8796 // Lenient parsing: structural validation only.
88- auto addr = parse_urn("urn:mhda:nt:evm:ct:60: ci:1");
97+ auto addr = parse_urn("urn:mhda:nt:evm:ci:1");
8998 std::cout << addr.get_chain().network().str() << "\n"; // evm
9099 std::cout << addr.resolved_algorithm().str() << "\n"; // secp256k1
91100 std::cout << addr.resolved_format().str() << "\n"; // hex
92101
93102 // Strict parsing also checks the (network, algorithm, format, derivation)
94103 // combination is in the known-good compatibility matrix.
95104 try {
96- parse_urn_strict ("urn:mhda:nt:evm:ct:60: ci:1:aa: ed25519 ");
105+ parse_urn_strict ("urn:mhda:nt:evm:ci:1:aa: ed25519 ");
97106 } catch (const parse_error& e) {
98107 if (e.code() == error_code::incompatible) {
99108 std::cout << "evm + ed25519 rejected, as expected\n";
100109 }
101110 }
102111
103112 // Type-agnostic level-by-level path inspection.
104- auto bip = parse_urn("urn:mhda:nt:evm:ct:60: ci:1:dt:bip44:dp:m/44'/60'/0'/0/0");
113+ auto bip = parse_urn("urn:mhda:nt:evm:ci:1:dt:bip44:dp:m/44'/60'/0'/0/0");
105114 for (const auto& lvl : bip.path()->levels()) {
106115 std::cout << " " << lvl.index << (lvl.is_hardened ? "'" : "") << "\n";
107116 }
108117
109118 // Hashing for content-addressing or deduplication.
110119 std::cout << bip.hash256() << "\n"; // SHA-256 hex
111120
112- // The chain-domain triple (nt, ct, ci) is itself a parseable key.
113- auto key = bip.get_chain().key(); // "nt:evm:ct:60:ci:1"
121+ // The chain identity (nt, ci) is itself a parseable key. Keys never carry
122+ // the optional ct metadata; a pre-1.1 key with ct fails loudly with
123+ // error_code::coin_type_in_chain_key.
124+ auto key = bip.get_chain().key(); // "nt:evm:ci:1"
114125 auto parsed = chain::from_key(key);
115126 (void)parsed;
127+
128+ // Optional wallet context: client type + wallet instance id.
129+ auto wallet = parse_urn("urn:mhda:nt:evm:ci:1:wt:web3:wi:5f2a8c31");
130+ std::cout << wallet.wallet_type() << " " << wallet.wallet_id() << "\n";
116131}
117132```
118133
@@ -122,16 +137,18 @@ A runnable version is in [`examples/basic.cpp`](./examples/basic.cpp).
122137
123138| Network | Example URN |
124139| ----------------| ------------------------------------------------------------------------------------------------------|
125- | Ethereum | ` urn:mhda:nt:evm:ct:60:ci:1:dt:bip44:dp:m/44'/60'/0'/0/0 ` |
126- | Bitcoin (BIP86)| ` urn:mhda:nt:btc:ct:0:ci:bitcoin:dt:bip86:dp:m/86'/0'/0'/0/0:af:bech32m:ap:bc1p ` |
127- | Solana | ` urn:mhda:nt:sol:ct:501:ci:mainnet:dt:slip10:dp:m/44'/501'/0'/0' ` |
128- | Stellar | ` urn:mhda:nt:xlm:ct:148:ci:mainnet:dt:slip10:dp:m/44'/148'/0' ` |
129- | Sui (ed25519) | ` urn:mhda:nt:sui:ct:784:ci:mainnet:dt:slip10:dp:m/44'/784'/0'/0'/0' ` |
130- | Cardano | ` urn:mhda:nt:ada:ct:1815:ci:mainnet:dt:cip1852:dp:m/1852'/1815'/0'/0/0 ` |
131- | Algorand | ` urn:mhda:nt:algo:ct:283:ci:mainnet ` (non-HD) |
132- | TON | ` urn:mhda:nt:ton:ct:607:ci:mainnet ` (non-HD, friendly base64url default) |
133- | Cosmos | ` urn:mhda:nt:cosmos:ct:118:ci:cosmoshub:dt:cip11:dp:m/44'/118'/0'/0/0 ` |
134- | EVM short form | ` urn:mhda:nt:evm:ct:60:ci:1 ` (defaults: bip44, secp256k1, hex) |
140+ | Ethereum | ` urn:mhda:nt:evm:ci:1:dt:bip44:dp:m/44'/60'/0'/0/0 ` |
141+ | Bitcoin (BIP86)| ` urn:mhda:nt:bitcoin:ci:bitcoin:dt:bip86:dp:m/86'/0'/0'/0/0:af:bech32m:ap:bc1p ` |
142+ | Solana | ` urn:mhda:nt:solana:ci:mainnet:dt:slip10:dp:m/44'/501'/0'/0' ` |
143+ | Stellar | ` urn:mhda:nt:stellar:ci:mainnet:dt:slip10:dp:m/44'/148'/0' ` |
144+ | Sui (ed25519) | ` urn:mhda:nt:sui:ci:mainnet:dt:slip10:dp:m/44'/784'/0'/0'/0' ` |
145+ | Cardano | ` urn:mhda:nt:cardano:ci:mainnet:dt:cip1852:dp:m/1852'/1815'/0'/0/0 ` |
146+ | Algorand | ` urn:mhda:nt:algorand:ci:mainnet ` (non-HD) |
147+ | TON | ` urn:mhda:nt:ton:ci:mainnet ` (non-HD, friendly base64url default) |
148+ | Cosmos | ` urn:mhda:nt:cosmos:ci:cosmoshub:dt:cip11:dp:m/44'/118'/0'/0/0 ` |
149+ | EVM short form | ` urn:mhda:nt:evm:ci:1 ` (defaults: secp256k1, hex) |
150+ | With metadata | ` urn:mhda:nt:evm:ci:1:ct:60 ` (optional SLIP-44 annotation) |
151+ | Wallet-bound | ` urn:mhda:nt:ton:ci:mainnet:wt:tonconnect:wi:c0a8f2d4-3b6e-4a51-9c7d-2f8e1a0b5c93 ` |
135152
136153## API mapping (Go → C++)
137154
@@ -141,10 +158,14 @@ A runnable version is in [`examples/basic.cpp`](./examples/basic.cpp).
141158| ` mhda.ParseURNStrict ` | ` mhda::parse_urn_strict ` |
142159| ` mhda.ParseNSS ` | ` mhda::parse_nss ` |
143160| ` mhda.ChainFromKey ` / ` FromNSS ` | ` mhda::chain::from_key ` / ` from_nss ` |
144- | ` mhda.NewChain(...) ` | ` mhda::chain{...} ` |
161+ | ` mhda.NewChain(nt, ci) ` | ` mhda::chain{nt, ci} ` |
162+ | ` Chain.SetCoinType ` / ` ClearCoinType ` | ` chain::set_coin ` / ` chain::clear_coin ` |
163+ | ` Chain.CoinType ` + ` HasCoinType ` | ` chain::coin ` (` std::optional<coin_type> ` ) |
145164| ` mhda.ParseDerivationPath ` | ` mhda::derivation_path::parse ` |
146165| ` mhda.NewDerivationPathFromLevels ` | ` mhda::derivation_path::from_levels ` |
147166| ` Address.String() ` / ` NSS() ` | ` address::str ` / ` address::nss ` |
167+ | ` Address.WalletType ` / ` WalletId ` | ` address::wallet_type ` / ` wallet_id ` |
168+ | ` Address.SetWalletType ` / ` SetWalletId ` | ` address::set_wallet_type ` / ` set_wallet_id ` |
148169| ` Address.MarshalText ` | ` address::marshal_text ` |
149170| ` Address.UnmarshalText ` | ` address::unmarshal_text ` |
150171| ` Address.Hash ` / ` Hash256 ` | ` address::hash ` / ` hash256 ` |
@@ -160,9 +181,11 @@ Sentinel constants:
160181| ` ErrInvalidNSS ` | ` error_code::invalid_nss ` |
161182| ` ErrMissingNetworkType ` | ` error_code::missing_network_type ` |
162183| ` ErrInvalidNetworkType ` | ` error_code::invalid_network_type ` |
163- | ` ErrMissingCoinType ` | ` error_code::missing_coin_type ` |
164184| ` ErrInvalidCoinType ` | ` error_code::invalid_coin_type ` |
165185| ` ErrMissingChainID ` | ` error_code::missing_chain_id ` |
186+ | ` ErrCoinTypeInChainKey ` | ` error_code::coin_type_in_chain_key ` |
187+ | ` ErrInvalidChainKey ` | ` error_code::invalid_chain_key ` |
188+ | ` ErrInvalidValue ` | ` error_code::invalid_value ` |
166189| ` ErrInvalidDerivationType ` | ` error_code::invalid_derivation_type ` |
167190| ` ErrInvalidDerivationPath ` | ` error_code::invalid_derivation_path ` |
168191| ` ErrInvalidAlgorithm ` | ` error_code::invalid_algorithm ` |
@@ -191,7 +214,7 @@ Mirrors the [SPEC §8](./SPEC.md#8-concurrency) contract.
191214
192215## Testing & validation
193216
194- - 71 unit + fuzz-equivalent test cases.
217+ - 139 unit + fuzz-equivalent test cases.
195218- Fuzz harness runs ≈11 000 randomised mutations of the historical Go-fuzz
196219 seed corpus per execution (URN, NSS and derivation-path entry points).
197220 Contracts verified: no exception other than ` parse_error ` /` std::invalid_argument ` ,
0 commit comments