Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions lib/bip/address/eth_addr.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:blockchain_utils/bech32/bech32.dart';
import 'package:blockchain_utils/hex/hex.dart';
import 'package:blockchain_utils/bip/address/addr_dec_utils.dart';
import 'package:blockchain_utils/bip/address/decoder.dart';
import 'package:blockchain_utils/bip/coin_conf/constant/coins_conf.dart';
Expand Down Expand Up @@ -126,3 +128,54 @@ class EthAddrEncoder implements BlockchainAddressEncoder {
EthAddrUtils._checksumEncode(addr);
}
}

/// Some cosmos-sdk chains integrate EVM modules, which means in the same chain,
/// both bech32 and 0x addresses are supported.
/// Here provides a utility methods to convert between each other.
/// Note: Addresses are convertible if and only if both addresses are derived using the same coin type.
class EthBech32Converter {
/// Encodes an Ethereum address in Bech32 format.
/// This method takes a hexadecimal Ethereum address and a prefix,
/// converts the address to bytes, and then encodes to Bech32-format address.
///
/// Parameters:
/// - hexAddress: The hexadecimal representation of the Ethereum address.
/// - prefix: The Bech32 prefix to be used for encoding.
///
/// Returns:
/// A Bech32-encoded address.
///
/// Throws:
/// - AssertionError: If the length of the cleaned hexadecimal address is not equal to the expected Ethereum address length.
static String ethAddressToBech32(String ethAddress, prefix) {

final cleanHex = AddrDecUtils.validateAndRemovePrefix(
ethAddress, CoinsConf.ethereum.params.addrPrefix!
);
assert(
cleanHex.length == EthAddrConst.addrLen,
"Invalid Ethereum address length: ${cleanHex.length}, expected: ${EthAddrConst.addrLen}"
);
final hexAddressBytes = hex.decode(cleanHex);
return Bech32Encoder.encode(prefix, hexAddressBytes);
}

/// Decodes a Bech32-encoded address.
/// This method takes a Bech32-encoded address
/// and decodes it to its hexadecimal representation.
///
/// Parameters:
/// - bech32Address: The Bech32-encoded Ethereum address.
///
/// Returns:
/// A string representing the Ethereum address.
static String bech32ToEthAddress(String bech32Address, prefix) {
final decoded = Bech32Decoder.decode(prefix, bech32Address);
final hexEncoded = hex.encode(decoded);
assert(
hexEncoded.length == EthAddrConst.addrLen,
"Invalid Ethereum address length: ${hexEncoded.length}, expected: ${EthAddrConst.addrLen}"
);
return '${CoinsConf.ethereum.params.addrPrefix!}$hexEncoded';
}
}
50 changes: 49 additions & 1 deletion test/address/eth/eth_test.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'package:blockchain_utils/bech32/bech32.dart';
import 'package:blockchain_utils/bip/address/addr_dec_utils.dart';
import 'package:blockchain_utils/bip/address/eth_addr.dart';
import 'package:blockchain_utils/bip/coin_conf/constant/coins_conf.dart';
import '../../quick_hex.dart';
import 'package:blockchain_utils/utils/utils.dart';
import 'package:test/test.dart';

import 'test_vector.dart' show testVector;
import 'test_vector.dart' show testVector, convertorTestVector;

void main() {
test("eth address test", () {
Expand All @@ -16,4 +19,49 @@ void main() {
expect(decode.toHex(), i["decode"]);
}
});

test("eth address convertor test", () {
for (final i in convertorTestVector) {
final bech32Address = i["bech32"]!;
final ethAddress = i["hex"]!;
expect(
AddrDecUtils.validateAndRemovePrefix(
ethAddress, CoinsConf.ethereum.params.addrPrefix!
).length,
EthAddrConst.addrLen
);

final prefix = bech32Address.split(Bech32Const.separator)[0];

// Convert Bech32 to Hex
final convertedHex =
EthBech32Converter.bech32ToEthAddress(bech32Address, prefix);
expect(convertedHex, ethAddress.toLowerCase(),
reason:
"Converting $bech32Address, Expected: $ethAddress, but got: $convertedHex");

// Convert Hex to Bech32
final convertedBech32 =
EthBech32Converter.ethAddressToBech32(ethAddress, prefix);
expect(convertedBech32, bech32Address,
reason:
"Converting $ethAddress, Expected: $bech32Address, but got: $convertedBech32");
}

final invalidAddressWithWrongLength =
"0x1448b2449076672aCD167b91406c09552101C5";
expect(
AddrDecUtils.validateAndRemovePrefix(
invalidAddressWithWrongLength,
CoinsConf.ethereum.params.addrPrefix!
).length,
isNot(EthAddrConst.addrLen)
);

(
() => EthBech32Converter.ethAddressToBech32(
invalidAddressWithWrongLength, "eth"),
throwsA(isA<AssertionError>())
);
});
}
16 changes: 16 additions & 0 deletions test/address/eth/test_vector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,19 @@ final List<Map<String, dynamic>> testVector = [
"params": {}
}
];

final List<Map<String, String>> convertorTestVector = [
{
"bech32": "mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv",
"hex": "0x1448b2449076672aCD167b91406c09552101C5C9"
},
{
"bech32": "crc1gwqac243g2z3vryqsev6acq965f9ttwhw9r7vk",
"hex": "0x4381dc2ab14285160c808659aee005d51255add7"
},
{
"bech32": "plq1l2hvlkqw3jzh9w5kv47gfmxfqqldrvnm50fv6p",
"hex": "0xFaAEcfd80e8c8572bA96657c84eCc9003Ed1b27B"
}
];