Address Conversion
Last updated
Last updated
MANTRA Chain uses the Bech32 address format, which contains a human-readable format for encoding addresses (addresses start with mantra1
).
This format is used to represent addresses in a way that is more user-friendly and less error-prone than traditional hexadecimal formats (addresses start with 0x
).
You can find Bech32 details in the .
MANTRA Chain also supports EVM module, which is compatible with the Ethereum Virtual Machine (EVM) and uses the standard hexadecimal address format.
With the same private key, you can derive both EVM address and Bech32 address with coin type 60.
In order to convert between a Bech32 format address and an EVM format address, we provide the following sample codes below:
Download the latest binary from page
Basic command:
$ mantrachaind debug addr mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv
Belows are outputs of the above command:
Address: [20 72 178 68 144 118 103 42 205 22 123 145 64 108 9 85 33 1 197 201]
Address (hex): 1448B2449076672ACD167B91406C09552101C5C9
Bech32 Acc: mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv
Bech32 Val: mantravaloper1z3yty3yswenj4ngk0wg5qmqf25ssr3wfyx9jmf
Bech32 Con: mantravalcons1z3yty3yswenj4ngk0wg5qmqf25ssr3wfs4kwhg
Command with better output:
mantrachaind-v5 debug addr mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv | grep "Address (hex):" | awk '{print "0x"$3}
output:
0x1448B2449076672ACD167B91406C09552101C5C9
Check test cases for examples of how to use the functions.
/// Codes are referenced from https://github.com/bitcoinjs/bech32/blob/master/src/index.ts
use std::collections::HashMap;
use std::sync::LazyLock;
const MANTRA_ADDRESS_PREFIX: &str = "mantra";
/// These characters are carefully designed as part of the BIP173 specification to avoid common transcription errors.
/// Reference: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
const ALPHABET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
static ALPHABET_MAP: LazyLock<HashMap<char, u8>> = LazyLock::new(|| {
let mut map: HashMap<char, u8> = HashMap::new();
for (z, x) in ALPHABET.chars().enumerate() {
map.insert(x, z as u8);
}
map
});
/// Convert EVM address to MANTRA address.
/// References:
/// - https://docs.cronos.org/for-dapp-developers/chain-integration/adress-conversion
/// - https://en.bitcoin.it/wiki/BIP_0173
///
/// # Arguments
/// * `evm_address` - The EVM address as a hex string (with or without 0x prefix)
///
/// # Returns
/// * `Result<String, Box<dyn std::error::Error>>` - The MANTRA address or an error
pub fn convert_evm_address_to_mantra_address(
evm_address: &str,
) -> Result<String, Box<dyn std::error::Error>> {
// Remove "0x" prefix if present
let hex_str = evm_address.trim_start_matches("0x").to_ascii_lowercase();
// Convert hex string to bytes
let evm_address_bytes = hex::decode(hex_str)?;
// Convert bits from 8 to 5
let converted_bits = convert_bits(&evm_address_bytes, 8, 5, true)?;
// Encode using bech32
let mantra_address = bech32_encoder(MANTRA_ADDRESS_PREFIX, &converted_bits, None)?;
Ok(mantra_address)
}
#[allow(dead_code)]
pub fn convert_mantra_address_to_eth_address(
mantra_address: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let decoded: Decoded = bech32_decoder(mantra_address, None)?;
let hex_bytes = convert_bits(&decoded.words, 5, 8, false)?;
Ok(format!("0x{}", hex::encode(hex_bytes)))
}
/// General power-of-2 base conversion.
/// References:
/// - https://en.bitcoin.it/wiki/Bech32
/// - https://github.com/fiatjaf/bech32/blob/master/bech32/__init__.py
///
/// # Arguments
/// * `data` - Input data as a slice of bytes
/// * `from_bits` - Number of bits in source representation
/// * `to_bits` - Number of bits in target representation
/// * `pad` - Whether to pad a result if needed
///
/// # Returns
/// * `Result<Vec<u8>, Box<dyn std::error::Error>>` - Converted data or error if conversion fails
pub fn convert_bits(
data: &[u8],
from_bits: u32,
to_bits: u32,
pad: bool,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut acc = 0u32;
let mut bits = 0u32;
let mut ret = Vec::new();
let maxv = (1 << to_bits) - 1;
let max_acc = (1 << (from_bits + to_bits - 1)) - 1;
for &value in data {
let value = value as u32;
if value >= (1 << from_bits) {
return Err("Invalid data for base conversion: value out of range".into());
}
acc = (acc << from_bits | value) & max_acc;
bits += from_bits;
while bits >= to_bits {
bits -= to_bits;
ret.push(((acc >> bits) & maxv) as u8);
}
}
if pad && bits > 0 {
ret.push(((acc << (to_bits - bits)) & maxv) as u8);
} else if !pad && (bits >= from_bits || (acc << (to_bits - bits)) & maxv != 0) {
return Err("Invalid padding in base conversion".into());
}
Ok(ret)
}
fn bech32_encoder(prefix: &str, words: &[u8], limit: Option<usize>) -> Result<String, String> {
let limit = limit.unwrap_or(90);
if prefix.len() + 7 + words.len() > limit {
return Err("Exceeds length limit".to_string());
}
let prefix = prefix.to_lowercase();
// determine chk mod
let mut chk = prefix_chk(&prefix).map_err(|e| e)?;
let mut result = format!("{}1", prefix);
for &x in words {
if x >> 5 != 0 {
return Err("Non-5-bit word".to_string());
}
chk = polymod_step(chk) ^ (x as u32);
result.push(ALPHABET.chars().nth(x as usize).unwrap());
}
chk = (0..6).fold(chk, |chk, _| polymod_step(chk)) ^ 1;
for i in 0..6 {
let v = (chk >> ((5 - i) * 5)) & 0x1f;
result.push(ALPHABET.chars().nth(v as usize).unwrap());
}
Ok(result)
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct Decoded {
pub prefix: String,
pub words: Vec<u8>,
}
#[allow(dead_code)]
pub fn bech32_decoder(str_input: &str, limit: Option<usize>) -> Result<Decoded, String> {
let limit = limit.unwrap_or(90);
if str_input.len() < 8 {
return Err(format!("{} too short", str_input));
}
if str_input.len() > limit {
return Err("Exceeds length limit".to_string());
}
// Don't allow mixed case
let lowered = str_input.to_lowercase();
let uppered = str_input.to_uppercase();
if str_input != lowered && str_input != uppered {
return Err(format!("Mixed-case string {}", str_input));
}
let str_normalized = &lowered;
let split = match str_normalized.rfind('1') {
Some(pos) => pos,
None => return Err(format!("No separator character for {}", str_normalized)),
};
if split == 0 {
return Err(format!("Missing prefix for {}", str_normalized));
}
let prefix = str_normalized[..split].to_string();
let word_chars = &str_normalized[split + 1..];
if word_chars.len() < 6 {
return Err("Data too short".to_string());
}
let mut chk = match prefix_chk(&prefix) {
Ok(checksum) => checksum,
Err(error) => return Err(error),
};
let mut words = Vec::new();
for (i, c) in word_chars.chars().enumerate() {
let v = match ALPHABET_MAP.get(&c) {
Some(value) => *value,
None => return Err(format!("Unknown character {}", c)),
};
chk = polymod_step(chk) ^ (v as u32);
// Not in the checksum?
if i + 6 >= word_chars.len() {
continue;
}
words.push(v);
}
if chk != 1 {
return Err(format!("Invalid checksum for {}", str_normalized));
}
Ok(Decoded { prefix, words })
}
fn polymod_step(pre: u32) -> u32 {
let b = pre >> 25;
((pre & 0x1ffffff) << 5)
^ (if ((b >> 0) & 1) != 0 { 0x3b6a57b2 } else { 0 })
^ (if ((b >> 1) & 1) != 0 { 0x26508e6d } else { 0 })
^ (if ((b >> 2) & 1) != 0 { 0x1ea119fa } else { 0 })
^ (if ((b >> 3) & 1) != 0 { 0x3d4233dd } else { 0 })
^ (if ((b >> 4) & 1) != 0 { 0x2a1462b3 } else { 0 })
}
fn prefix_chk(prefix: &str) -> Result<u32, String> {
let mut chk: u32 = 1;
for &code in prefix.as_bytes() {
if code < 33 || code > 126 {
return Err(format!("Invalid prefix ({})", prefix));
}
chk = polymod_step(chk) ^ (code as u32 >> 5);
}
chk = polymod_step(chk);
for &code in prefix.as_bytes() {
chk = polymod_step(chk) ^ (code as u32 & 0x1f);
}
Ok(chk)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convert_evm_address_to_mantra_address() {
let evm_address = "0x1448b2449076672aCD167b91406c09552101C5C9";
let mantra_address = "mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv";
let result = convert_evm_address_to_mantra_address(evm_address);
assert!(result.is_ok());
let converted_mantra_address = result.unwrap();
assert!(mantra_address.starts_with("mantra"));
assert!(mantra_address.len() > 6); // More than just the prefix
assert_eq!(converted_mantra_address.as_str(), mantra_address);
}
#[test]
fn test_convert_evm_address_without_0x_prefix() {
let evm_address = "FaAEcfd80e8c8572bA96657c84eCc9003Ed1b27B";
let mantra_address = "mantra1l2hvlkqw3jzh9w5kv47gfmxfqqldrvnmkun5lg";
let result = convert_evm_address_to_mantra_address(evm_address);
assert!(result.is_ok());
let converted_mantra_address = result.unwrap();
assert!(mantra_address.starts_with("mantra"));
assert!(mantra_address.len() > 6); // More than just the prefix
assert_eq!(converted_mantra_address.as_str(), mantra_address);
}
#[test]
fn test_convert_bits() {
let data = vec![0xFF, 0xFF];
let result = convert_bits(&data, 8, 5, true);
assert!(result.is_ok());
}
#[test]
fn test_convert_bits_invalid_data() {
let data = vec![0xFF]; // 8 bits but trying to convert from 4 bits
let result = convert_bits(&data, 4, 8, true);
assert!(result.is_err());
}
#[test]
fn test_convert_mantra_address_to_eth_address() {
let mantra_address = "mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv";
let expected_eth_address = "0x1448b2449076672acd167b91406c09552101c5c9";
let result = convert_mantra_address_to_eth_address(mantra_address);
assert!(result.is_ok());
let converted_eth_address = result.unwrap();
assert_eq!(converted_eth_address.to_lowercase(), expected_eth_address);
}
}
"use strict";
import {bech32} from "bech32";
const mantraAddressPrefix = 'mantra';
/**
* Convert EVM address to MANTRA address.
* References:
* - https://docs.cronos.org/for-dapp-developers/chain-integration/adress-conversion
* - https://en.bitcoin.it/wiki/BIP_0173
* @param {string} evmAddress
* @returns {string}
*/
export const convertEVMAddressToMantraAddress = (evmAddress) => {
const evmAddressBuffer = Buffer.from(evmAddress.slice(2), 'hex');
const evmAddressBytes = Array.from(evmAddressBuffer);
const bz = convertBits(evmAddressBytes, 8, 5);
return bech32.encode(mantraAddressPrefix, bz);
}
export const convertMantraAddressToEVMAddress = (mantraAddress) => {
const decoded = bech32.decode(mantraAddress);
const hexBytes = convertBits(decoded.words, 5, 8, false);
return `0x${Buffer.from(hexBytes).toString('hex')}`;
}
/**
* General power-of-2 base conversion.
* References:
* - https://en.bitcoin.it/wiki/Bech32
* - https://github.com/fiatjaf/bech32/blob/master/bech32/__init__.py
* @param {Array<number>} data - Input data as an array of integers
* @param {number} fromBits - Number of bits in source representation
* @param {number} toBits - Number of bits in target representation
* @param {boolean} pad - Whether to pad a result if needed
* @returns {Array<number>|null} - Converted data or null if conversion fails
*/
export const convertBits = (data, fromBits, toBits, pad = true) => {
let acc = 0;
let bits = 0;
const ret = [];
const maxv = (1 << toBits) - 1;
const max_acc = (1 << (fromBits + toBits - 1)) - 1;
for (const value of data) {
if (value < 0 || (value >> fromBits)) {
return null;
}
acc = ((acc << fromBits) | value) & max_acc;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
ret.push((acc >> bits) & maxv);
}
}
if (pad) {
if (bits > 0) {
ret.push((acc << (toBits - bits)) & maxv);
}
} else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv)) {
return null;
}
return ret;
}
Test cases
import { expect } from 'chai';
import { convertEVMAddressToMantraAddress, convertMantraAddressToEVMAddress } from "<YOUR_PATH>/<TO_THE_FUNCTIONS>.js";
describe('Address Conversion', () => {
it('should be able to convert EVM address to MANTRA address', () => {
const evmAddress = '0x1448b2449076672aCD167b91406c09552101C5C9';
const expectedMantraAddress = 'mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv';
const convertedAddress = convertEVMAddressToMantraAddress(evmAddress);
expect(convertedAddress.toLowerCase()).to.equal(expectedMantraAddress.toLowerCase());
});
it('should be able to convert MANTRA address to EVM address', () => {
const mantraAddress = 'mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv';
const expectedEVMAddress = '0x1448b2449076672aCD167b91406c09552101C5C9';
const convertedAddress = convertMantraAddressToEVMAddress(mantraAddress);
expect(convertedAddress.toLowerCase()).to.equal(expectedEVMAddress.toLowerCase());
})
});
From mantra1
to 0x
import bech32
bech32_address = "mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv"
_, bz = bech32.bech32_decode(bech32_address)
hexbytes=bytes(bech32.convertbits(bz, 5, 8))
eth_address = '0x' + hexbytes.hex()
print(eth_address)
Vice versa, from 0x
to mantra1
import bech32
eth_address = "0x1448b2449076672acd167b91406c09552101c5c9"
eth_address_bytes = bytes.fromhex(eth_address[2:])
bz = bech32.convertbits(eth_address_bytes, 8, 5)
bech32_address = bech32.bech32_encode("crc",bz)
print(bech32_address)