Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Added `docs/sdk_developers/training/transaction_lifecycle.md` to explain the typical lifecycle of executing a transaction using the Hedera Python SDK.
- Add inactivity bot workflow to unassign stale issue assignees (#952)
- Made custom fraction fee end to end
- feat: AccountCreateTransaction now supports both PrivateKey and PublicKey [#939](https://github.com/hiero-ledger/hiero-sdk-python/issues/939)
- Added Acceptance Criteria section to Good First Issue template for better contributor guidance (#997)
- Added __str__() to CustomRoyaltyFee and updated examples and tests accordingly (#986)

Expand Down
132 changes: 132 additions & 0 deletions examples/account/account_create_transaction_create_with_alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""
Example: Create an account using a separate ECDSA key for the EVM alias.

This demonstrates:
- Using a "main" key for the account
- Using a separate ECDSA public key as the EVM alias
- The need to sign the transaction with the alias private key

Usage:
- uv run -m examples.account.account_create_transaction_create_with_alias
- python -m examples.account.account_create_transaction_create_with_alias
(we use -m because we use the util `info_to_dict`)
"""

import os
import sys
import json
from dotenv import load_dotenv

from examples.utils import info_to_dict

from hiero_sdk_python import (
Client,
PrivateKey,
AccountCreateTransaction,
AccountInfoQuery,
Network,
AccountId,
Hbar,
)

load_dotenv()
network_name = os.getenv("NETWORK", "testnet").lower()


def setup_client():
"""Setup Client."""
network = Network(network_name)
print(f"Connecting to Hedera {network_name} network!")
client = Client(network)

try:
operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", ""))
operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
client.set_operator(operator_id, operator_key)
print(f"Client set up with operator id {client.operator_account_id}")
return client
except Exception:
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)
)

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."
)

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

account_info = (
AccountInfoQuery()
.set_account_id(new_account_id)
.execute(client)
)

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.")

except Exception as error:
print(f"❌ Error: {error}")
sys.exit(1)

def main():
"""Main entry point."""
client = setup_client()
create_account_with_separate_ecdsa_alias(client)


if __name__ == "__main__":
main()
22 changes: 4 additions & 18 deletions examples/account/account_create_transaction_evm_alias.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# uv run examples/account/account_create_transaction_evm_alias.py
# python examples/account/account_create_transaction_evm_alias.py
# uv run -m examples.account.account_create_transaction_evm_alias
# python -m examples.account.account_create_transaction_evm_alias
"""
Example: Create an account using an EVM-style alias (evm_address).
"""
Expand All @@ -9,6 +9,8 @@
import json
from dotenv import load_dotenv

from examples.utils import info_to_dict

from hiero_sdk_python import (
Client,
PrivateKey,
Expand Down Expand Up @@ -40,22 +42,6 @@ def setup_client():
print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
sys.exit(1)

def info_to_dict(info):
"""Convert AccountInfo to dictionary for easy printing."""
out = {}
for name in dir(info):
if name.startswith("_"):
continue
try:
val = getattr(info, name)
except Exception as error:
out[name] = f"Error retrieving value: {error}"
continue
if callable(val):
continue
out[name] = str(val)
return out

def create_account_with_alias(client):
"""Create an account with an alias transaction."""
try:
Expand Down
117 changes: 117 additions & 0 deletions examples/account/account_create_transaction_with_fallback_alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
Example: Create an account where the EVM alias is derived from the main ECDSA key.

This demonstrates:
- Passing only an ECDSA PrivateKey to `set_key_with_alias`
- The alias being derived from the main key's EVM address (fallback behaviour)

Usage:
- uv run -m examples.account.account_create_transaction_with_fallback_alias
- python -m examples.account.account_create_transaction_with_fallback_alias
(we use -m because we use the util `info_to_dict`)
"""

import os
import sys
import json
from dotenv import load_dotenv

from examples.utils import info_to_dict

from hiero_sdk_python import (
Client,
PrivateKey,
AccountCreateTransaction,
AccountInfoQuery,
Network,
AccountId,
Hbar,
)

load_dotenv()
network_name = os.getenv("NETWORK", "testnet").lower()


def setup_client():
"""Setup Client."""
network = Network(network_name)
print(f"Connecting to Hedera {network_name} network!")
client = Client(network)

try:
operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", ""))
operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
client.set_operator(operator_id, operator_key)
print(f"Client set up with operator id {client.operator_account_id}")
return client
except Exception:
print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
sys.exit(1)

def create_account_with_fallback_alias(client: Client) -> None:
"""Create an account whose alias is derived from the main ECDSA key."""
try:
print("\nSTEP 1: Generating a single ECDSA key pair for the account...")
account_private_key = PrivateKey.generate("ecdsa")
account_public_key = account_private_key.public_key()
evm_address = account_public_key.to_evm_address()

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

print(f"✅ Account ECDSA public key: {account_public_key}")
print(f"✅ Derived EVM address: {evm_address}")

print("\nSTEP 2: Creating the account using the fallback alias behaviour...")
transaction = (
AccountCreateTransaction(
initial_balance=Hbar(5),
memo="Account with alias derived from main ECDSA key",
)
# Fallback path: only one ECDSA key is provided
.set_key_with_alias(account_private_key)
)

# Freeze & sign with the account key as well
transaction = (
transaction.freeze_with(client)
.sign(account_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."
)

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

account_info = (
AccountInfoQuery()
.set_account_id(new_account_id)
.execute(client)
)

out = info_to_dict(account_info)
print("Account Info:")
print(json.dumps(out, indent=2) + "\n")

print(
"✅ contract_account_id (EVM alias on-chain): "
f"{account_info.contract_account_id}"
)

except Exception as error:
print(f"❌ Error: {error}")
sys.exit(1)

def main():
"""Main entry point."""
client = setup_client()
create_account_with_fallback_alias(client)

if __name__ == "__main__":
main()
Loading
Loading