Skip to content

Commit ba774c1

Browse files
committed
refactor(examples): modularize transaction_to_bytes example (issue #1012)
Signed-off-by: dantesb777 <daniel199227@hotmail.com>
1 parent b7f8b99 commit ba774c1

10 files changed

+715
-101
lines changed

CHANGELOG.md

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

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
Example: Create an account using a separate ECDSA key for the EVM alias.
3+
4+
This demonstrates:
5+
- Using a "main" key for the account
6+
- Using a separate ECDSA public key as the EVM alias
7+
- The need to sign the transaction with the alias private key
8+
9+
Usage:
10+
- uv run -m examples.account.account_create_transaction_create_with_alias
11+
- python -m examples.account.account_create_transaction_create_with_alias
12+
(we use -m because we use the util `info_to_dict`)
13+
"""
14+
15+
import os
16+
import sys
17+
import json
18+
from dotenv import load_dotenv
19+
20+
from examples.utils import info_to_dict
21+
22+
from hiero_sdk_python import (
23+
Client,
24+
PrivateKey,
25+
AccountCreateTransaction,
26+
AccountInfoQuery,
27+
Network,
28+
AccountId,
29+
Hbar,
30+
)
31+
32+
load_dotenv()
33+
network_name = os.getenv("NETWORK", "testnet").lower()
34+
35+
36+
def setup_client():
37+
"""Setup Client."""
38+
network = Network(network_name)
39+
print(f"Connecting to Hedera {network_name} network!")
40+
client = Client(network)
41+
42+
try:
43+
operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", ""))
44+
operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
45+
client.set_operator(operator_id, operator_key)
46+
print(f"Client set up with operator id {client.operator_account_id}")
47+
return client
48+
except Exception:
49+
print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
50+
sys.exit(1)
51+
52+
def create_account_with_separate_ecdsa_alias(client: Client) -> None:
53+
"""Create an account whose alias comes from a separate ECDSA key."""
54+
try:
55+
print("\nSTEP 1: Generating main account key and separate ECDSA alias key...")
56+
57+
# Main account key (can be any key type, here ed25519)
58+
main_private_key = PrivateKey.generate()
59+
main_public_key = main_private_key.public_key()
60+
61+
# Separate ECDSA key used only for the EVM alias
62+
alias_private_key = PrivateKey.generate("ecdsa")
63+
alias_public_key = alias_private_key.public_key()
64+
alias_evm_address = alias_public_key.to_evm_address()
65+
66+
if alias_evm_address is None:
67+
print("❌ Error: Failed to generate EVM address from alias ECDSA key.")
68+
sys.exit(1)
69+
70+
print(f"✅ Main account public key: {main_public_key}")
71+
print(f"✅ Alias ECDSA public key: {alias_public_key}")
72+
print(f"✅ Alias EVM address: {alias_evm_address}")
73+
74+
print("\nSTEP 2: Creating the account with the EVM alias from the ECDSA key...")
75+
76+
# Use the helper that accepts both the main key and the ECDSA alias key
77+
transaction = (
78+
AccountCreateTransaction(
79+
initial_balance=Hbar(5),
80+
memo="Account with separate ECDSA alias",
81+
)
82+
.set_key_with_alias(main_private_key, alias_public_key)
83+
)
84+
85+
# Freeze and sign:
86+
# - operator key signs as payer (via client)
87+
# - alias private key MUST sign to authorize the alias
88+
transaction = (
89+
transaction.freeze_with(client)
90+
.sign(alias_private_key)
91+
)
92+
93+
response = transaction.execute(client)
94+
new_account_id = response.account_id
95+
96+
if new_account_id is None:
97+
raise RuntimeError(
98+
"AccountID not found in receipt. Account may not have been created."
99+
)
100+
101+
print(f"✅ Account created with ID: {new_account_id}\n")
102+
103+
account_info = (
104+
AccountInfoQuery()
105+
.set_account_id(new_account_id)
106+
.execute(client)
107+
)
108+
109+
out = info_to_dict(account_info)
110+
print("Account Info:")
111+
print(json.dumps(out, indent=2) + "\n")
112+
113+
if account_info.contract_account_id is not None:
114+
print(
115+
f"✅ Contract Account ID (EVM alias on-chain): "
116+
f"{account_info.contract_account_id}"
117+
)
118+
else:
119+
print("❌ Error: Contract Account ID (alias) does not exist.")
120+
121+
except Exception as error:
122+
print(f"❌ Error: {error}")
123+
sys.exit(1)
124+
125+
def main():
126+
"""Main entry point."""
127+
client = setup_client()
128+
create_account_with_separate_ecdsa_alias(client)
129+
130+
131+
if __name__ == "__main__":
132+
main()

examples/account/account_create_transaction_evm_alias.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# uv run examples/account/account_create_transaction_evm_alias.py
2-
# python examples/account/account_create_transaction_evm_alias.py
1+
# uv run -m examples.account.account_create_transaction_evm_alias
2+
# python -m examples.account.account_create_transaction_evm_alias
33
"""
44
Example: Create an account using an EVM-style alias (evm_address).
55
"""
@@ -9,6 +9,8 @@
99
import json
1010
from dotenv import load_dotenv
1111

12+
from examples.utils import info_to_dict
13+
1214
from hiero_sdk_python import (
1315
Client,
1416
PrivateKey,
@@ -40,22 +42,6 @@ def setup_client():
4042
print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
4143
sys.exit(1)
4244

43-
def info_to_dict(info):
44-
"""Convert AccountInfo to dictionary for easy printing."""
45-
out = {}
46-
for name in dir(info):
47-
if name.startswith("_"):
48-
continue
49-
try:
50-
val = getattr(info, name)
51-
except Exception as error:
52-
out[name] = f"Error retrieving value: {error}"
53-
continue
54-
if callable(val):
55-
continue
56-
out[name] = str(val)
57-
return out
58-
5945
def create_account_with_alias(client):
6046
"""Create an account with an alias transaction."""
6147
try:
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Example: Create an account where the EVM alias is derived from the main ECDSA key.
3+
4+
This demonstrates:
5+
- Passing only an ECDSA PrivateKey to `set_key_with_alias`
6+
- The alias being derived from the main key's EVM address (fallback behaviour)
7+
8+
Usage:
9+
- uv run -m examples.account.account_create_transaction_with_fallback_alias
10+
- python -m examples.account.account_create_transaction_with_fallback_alias
11+
(we use -m because we use the util `info_to_dict`)
12+
"""
13+
14+
import os
15+
import sys
16+
import json
17+
from dotenv import load_dotenv
18+
19+
from examples.utils import info_to_dict
20+
21+
from hiero_sdk_python import (
22+
Client,
23+
PrivateKey,
24+
AccountCreateTransaction,
25+
AccountInfoQuery,
26+
Network,
27+
AccountId,
28+
Hbar,
29+
)
30+
31+
load_dotenv()
32+
network_name = os.getenv("NETWORK", "testnet").lower()
33+
34+
35+
def setup_client():
36+
"""Setup Client."""
37+
network = Network(network_name)
38+
print(f"Connecting to Hedera {network_name} network!")
39+
client = Client(network)
40+
41+
try:
42+
operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", ""))
43+
operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
44+
client.set_operator(operator_id, operator_key)
45+
print(f"Client set up with operator id {client.operator_account_id}")
46+
return client
47+
except Exception:
48+
print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
49+
sys.exit(1)
50+
51+
def create_account_with_fallback_alias(client: Client) -> None:
52+
"""Create an account whose alias is derived from the main ECDSA key."""
53+
try:
54+
print("\nSTEP 1: Generating a single ECDSA key pair for the account...")
55+
account_private_key = PrivateKey.generate("ecdsa")
56+
account_public_key = account_private_key.public_key()
57+
evm_address = account_public_key.to_evm_address()
58+
59+
if evm_address is None:
60+
print("❌ Error: Failed to generate EVM address from ECDSA public key.")
61+
sys.exit(1)
62+
63+
print(f"✅ Account ECDSA public key: {account_public_key}")
64+
print(f"✅ Derived EVM address: {evm_address}")
65+
66+
print("\nSTEP 2: Creating the account using the fallback alias behaviour...")
67+
transaction = (
68+
AccountCreateTransaction(
69+
initial_balance=Hbar(5),
70+
memo="Account with alias derived from main ECDSA key",
71+
)
72+
# Fallback path: only one ECDSA key is provided
73+
.set_key_with_alias(account_private_key)
74+
)
75+
76+
# Freeze & sign with the account key as well
77+
transaction = (
78+
transaction.freeze_with(client)
79+
.sign(account_private_key)
80+
)
81+
82+
response = transaction.execute(client)
83+
new_account_id = response.account_id
84+
85+
if new_account_id is None:
86+
raise RuntimeError(
87+
"AccountID not found in receipt. Account may not have been created."
88+
)
89+
90+
print(f"✅ Account created with ID: {new_account_id}\n")
91+
92+
account_info = (
93+
AccountInfoQuery()
94+
.set_account_id(new_account_id)
95+
.execute(client)
96+
)
97+
98+
out = info_to_dict(account_info)
99+
print("Account Info:")
100+
print(json.dumps(out, indent=2) + "\n")
101+
102+
print(
103+
"✅ contract_account_id (EVM alias on-chain): "
104+
f"{account_info.contract_account_id}"
105+
)
106+
107+
except Exception as error:
108+
print(f"❌ Error: {error}")
109+
sys.exit(1)
110+
111+
def main():
112+
"""Main entry point."""
113+
client = setup_client()
114+
create_account_with_fallback_alias(client)
115+
116+
if __name__ == "__main__":
117+
main()

0 commit comments

Comments
 (0)