from starkware.starknet.public.abi import starknet_keccak
def checksum_encode(addr: int): # Takes a 32-byte binary address as input
addr_bytes = int.to_bytes(addr, 32, "big")
hex_addr = addr_bytes.hex()
checksummed_buffer = ""
# Treat the hex address as ascii/utf-8 for hashing
hashed_address = f"{starknet_keccak(addr_bytes):064x}"
# Iterate over each character in the hex address
for nibble_index, character in enumerate(hex_addr):
if character in "0123456789":
# We can't upper-case the decimal digits
checksummed_buffer += character
elif character in "abcdef":
# Check if the corresponding hex digit (nibble) in the hash is 8 or higher
hashed_address_nibble = int(hashed_address[nibble_index], 16)
if hashed_address_nibble > 7:
checksummed_buffer += character.upper()
else:
checksummed_buffer += character
else:
raise eth_utils.ValidationError(
f"Unrecognized hex character {character!r} at position {nibble_index}"
)
return "0x" + checksummed_buffer
checksum_encode(0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7)
# 0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7
checksum_encode(0xbd3fcc84644ddd6b96f7c741b1562b82f9e004dc7)
# 0x00000000000000000000000Bd3fcc84644dDd6B96F7C741b1562B82f9e004dc7
It’s a huge PITA because every tool treats it differently (SDKs, Apibara, block explorers, etc.) and while 0x0f00 and 0xf00 are numerically the same, since they pretty much have to be stored as strings for interop, this introduces hard to spot bugs.
from starkware.starknet.public.abi import starknet_keccak
from typing import Union
class AddressValidationError(Exception):
“”“Custom exception for address validation errors.”“”
pass
def checksum_encode(addr: Union[int, str]) → str:
“”"
Generate a checksummed address following ERC-55 style convention for Starknet addresses.
Args:
addr (Union[int, str]): Address as integer or hex string
Returns:
str: Checksummed address string
Raises:
AddressValidationError: If address is invalid
"""
try:
# Convert string input to int if needed
if isinstance(addr, str):
addr = int(addr.replace("0x", ""), 16)
# Validate address size
if addr < 0:
raise AddressValidationError("Address cannot be negative")
if addr.bit_length() > 256:
raise AddressValidationError("Address exceeds 32 bytes")
# Convert to 32-byte representation
addr_bytes = int.to_bytes(addr, 32, "big")
hex_addr = addr_bytes.hex()
# Calculate hash using starknet_keccak
hashed_address = f"{starknet_keccak(addr_bytes):064x}"
checksummed_buffer = []
# Process each character
for nibble_index, character in enumerate(hex_addr):
if character in "0123456789":
checksummed_buffer.append(character)
elif character in "abcdef":
# Check corresponding hash nibble
hash_nibble = int(hashed_address[nibble_index], 16)
checksummed_buffer.append(
character.upper() if hash_nibble > 7 else character
)
else:
raise AddressValidationError(
f"Invalid hex character '{character}' at position {nibble_index}"
)
return "0x" + "".join(checksummed_buffer)
except ValueError as e:
raise AddressValidationError(f"Invalid address format: {str(e)}")
def validate_checksum(address: str) → bool:
“”"
Validate if an address string has the correct checksum.
Args:
address (str): Address string to validate
Returns:
bool: True if checksum is valid, False otherwise
"""
try:
if not address.startswith("0x"):
return False
addr_int = int(address.replace("0x", ""), 16)
return checksum_encode(addr_int) == address
except (AddressValidationError, ValueError):
return False
Could we get buy-in from SDKs, as well as Wallets?
We’d like an answer and plan to include (and possible alerts and warnings on non-backward compatibility) from:
Okay, if it’s just about formalizing, can we make sure the SNIP matches the implementation?
Migrating to a new implementation would be painful for everyone