Skip to content
Merged
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.

### Added

- Refactored `account_create_transaction_create_with_alias.py` example by splitting monolithic function into modular functions: `generate_main_and_alias_keys()`, `create_account_with_ecdsa_alias()`, `fetch_account_info()`, `print_account_summary()` (#1016)
- Modularized `transfer_transaction_fungible` example by introducing `account_balance_query()` & `transfer_transaction()`.Renamed `transfer_tokens()` → `main()`
- Phase 2 of the inactivity-unassign bot:Automatically detects stale open pull requests (no commit activity for 21+ days), comments with a helpful InactivityBot message, closes the stale PR, and unassigns the contributor from the linked issue.
- Phase 2 of the inactivity-unassign bot: Automatically detects stale open pull requests (no commit activity for 21+ days), comments with a helpful InactivityBot message, closes the stale PR, and unassigns the contributor from the linked issue.
- Added `__str__()` to CustomFixedFee and updated examples and tests accordingly.
- Added unit tests for `crypto_utils` (#993)
- Added a github template for good first issues
Expand Down
180 changes: 115 additions & 65 deletions examples/account/account_create_transaction_create_with_alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Client,
PrivateKey,
AccountCreateTransaction,
AccountInfo,
AccountInfoQuery,
Network,
AccountId,
Expand All @@ -49,83 +50,132 @@ def setup_client():
print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
sys.exit(1)

def create_account_with_separate_ecdsa_alias(client: Client) -> None:
"""Create an account whose alias comes from a separate ECDSA key."""
try:
print("\nSTEP 1: Generating main account key and separate ECDSA alias key...")

# Main account key (can be any key type, here ed25519)
main_private_key = PrivateKey.generate()
main_public_key = main_private_key.public_key()

# Separate ECDSA key used only for the EVM alias
alias_private_key = PrivateKey.generate("ecdsa")
alias_public_key = alias_private_key.public_key()
alias_evm_address = alias_public_key.to_evm_address()

if alias_evm_address is None:
print("❌ Error: Failed to generate EVM address from alias ECDSA key.")
sys.exit(1)

print(f"✅ Main account public key: {main_public_key}")
print(f"✅ Alias ECDSA public key: {alias_public_key}")
print(f"✅ Alias EVM address: {alias_evm_address}")

print("\nSTEP 2: Creating the account with the EVM alias from the ECDSA key...")

# Use the helper that accepts both the main key and the ECDSA alias key
transaction = (
AccountCreateTransaction(
initial_balance=Hbar(5),
memo="Account with separate ECDSA alias",
)
.set_key_with_alias(main_private_key, alias_public_key)
)

# Freeze and sign:
# - operator key signs as payer (via client)
# - alias private key MUST sign to authorize the alias
transaction = (
transaction.freeze_with(client)
.sign(alias_private_key)
)
def generate_main_and_alias_keys() -> tuple[PrivateKey, PrivateKey]:
"""Generate the main account key and a separate ECDSA alias key.

Returns:
tuple: (main_private_key, alias_private_key)
"""
print("\nSTEP 1: Generating main account key and separate ECDSA alias key...")

# Main account key (can be any key type, here ed25519)
main_private_key = PrivateKey.generate()
main_public_key = main_private_key.public_key()

# Separate ECDSA key used only for the EVM alias
alias_private_key = PrivateKey.generate("ecdsa")
alias_public_key = alias_private_key.public_key()
alias_evm_address = alias_public_key.to_evm_address()

if alias_evm_address is None:
print("❌ Error: Failed to generate EVM address from alias ECDSA key.")
sys.exit(1)

response = transaction.execute(client)
new_account_id = response.account_id
print(f"✅ Main account public key: {main_public_key}")
print(f"✅ Alias ECDSA public key: {alias_public_key}")
print(f"✅ Alias EVM address: {alias_evm_address}")

if new_account_id is None:
raise RuntimeError(
"AccountID not found in receipt. Account may not have been created."
)
return main_private_key, alias_private_key

print(f"✅ Account created with ID: {new_account_id}\n")

account_info = (
AccountInfoQuery()
.set_account_id(new_account_id)
.execute(client)
def create_account_with_ecdsa_alias(
client: Client, main_private_key: PrivateKey, alias_private_key: PrivateKey
) -> AccountId:
"""Create an account with a separate ECDSA key as the EVM alias.

Args:
client: The Hedera client.
main_private_key: The main account private key.
alias_private_key: The ECDSA private key for the EVM alias.

Returns:
AccountId: The newly created account ID.
"""
print("\nSTEP 2: Creating the account with the EVM alias from the ECDSA key...")

alias_public_key = alias_private_key.public_key()

# Use the helper that accepts both the main key and the ECDSA alias key
transaction = (
AccountCreateTransaction(
initial_balance=Hbar(5),
memo="Account with separate ECDSA alias",
)
.set_key_with_alias(main_private_key, alias_public_key)
)

# Freeze and sign:
# - operator key signs as payer (via client)
# - alias private key MUST sign to authorize the alias
transaction = (
transaction.freeze_with(client)
.sign(alias_private_key)
)

response = transaction.execute(client)
new_account_id = response.account_id

if new_account_id is None:
raise RuntimeError(
"AccountID not found in receipt. Account may not have been created."
)

out = info_to_dict(account_info)
print("Account Info:")
print(json.dumps(out, indent=2) + "\n")
print(f"✅ Account created with ID: {new_account_id}\n")
return new_account_id

if account_info.contract_account_id is not None:
print(
f"✅ Contract Account ID (EVM alias on-chain): "
f"{account_info.contract_account_id}"
)
else:
print("❌ Error: Contract Account ID (alias) does not exist.")

except Exception as error:
print(f"❌ Error: {error}")
sys.exit(1)
def fetch_account_info(client: Client, account_id: AccountId) -> AccountInfo:
"""Fetch account information from the network.

Args:
client: The Hedera client.
account_id: The account ID to query.

Returns:
AccountInfo: The account info object.
"""
print("\nSTEP 3: Fetching account information...")
account_info = (
AccountInfoQuery()
.set_account_id(account_id)
.execute(client)
)
return account_info


def print_account_summary(account_info: AccountInfo) -> None:
"""Print a summary of the account information.

Args:
account_info: The account info object to display.
"""
out = info_to_dict(account_info)
print("Account Info:")
print(json.dumps(out, indent=2) + "\n")

if account_info.contract_account_id is not None:
print(
f"✅ Contract Account ID (EVM alias on-chain): "
f"{account_info.contract_account_id}"
)
else:
print("❌ Error: Contract Account ID (alias) does not exist.")


def main():
"""Main entry point."""
client = setup_client()
create_account_with_separate_ecdsa_alias(client)
try:
client = setup_client()
main_private_key, alias_private_key = generate_main_and_alias_keys()
account_id = create_account_with_ecdsa_alias(
client, main_private_key, alias_private_key
)
account_info = fetch_account_info(client, account_id)
print_account_summary(account_info)
except Exception as error:
print(f"❌ Error: {error}")
sys.exit(1)


if __name__ == "__main__":
Expand Down
Loading