Skip to content

Commit

Permalink
account balance query in readme and with flexible syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
nadineloepfe committed Jan 3, 2025
1 parent d585078 commit 119a0cb
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 39 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This is a Python SDK for interacting with the Hedera Hashgraph platform. It allo
- [Running Tests](#running-tests)
- [Usage](#usage)
- [Creating an Account](#creating-an-account)
- [Querying Account Balance](#querying-account-balance)
- [Creating a Token](#creating-a-token)
- [Associating a Token](#associating-a-token)
- [Transferring Tokens](#transferring-tokens)
Expand Down Expand Up @@ -155,6 +156,17 @@ transaction = (
transaction.execute(client)
```

### Querying Account Balance

#### Pythonic Syntax:
```
balance = CryptoGetAccountBalanceQuery(account_id=some_account_id).execute(client) print(f"Account balance: {balance.hbars} hbars")
```

#### Method Chaining:
```
balance = ( CryptoGetAccountBalanceQuery() .set_account_id(some_account_id) .execute(client) ) print(f"Account balance: {balance.hbars} hbars")
```

### Creating a Token

Expand Down
20 changes: 8 additions & 12 deletions examples/query_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,21 @@
def create_account_and_transfer():
network = Network(network='testnet')
client = Client(network)

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)

new_account_private_key = PrivateKey.generate()
new_account_public_key = new_account_private_key.public_key()

initial_balance = Hbar(10)
transaction = (
AccountCreateTransaction()
.set_key(new_account_public_key)
.set_initial_balance(initial_balance)
.set_account_memo("New Account")
.freeze_with(client)
)
transaction = AccountCreateTransaction(
key=new_account_public_key,
initial_balance=Hbar(10),
memo="New Account"
).freeze_with(client)
transaction.sign(operator_key)
receipt = transaction.execute(client)
new_account_id = receipt.accountId
print(f"New account created with ID: {new_account_id}")

balance_query = CryptoGetAccountBalanceQuery().set_account_id(new_account_id)
balance = balance_query.execute(client)
Expand All @@ -48,8 +44,8 @@ def create_account_and_transfer():
.add_hbar_transfer(operator_id, -transfer_amount.to_tinybars())
.add_hbar_transfer(new_account_id, transfer_amount.to_tinybars())
.freeze_with(client)
.sign(operator_key)
)
transfer_transaction.sign(operator_key)
transfer_receipt = transfer_transaction.execute(client)
print(f"Transfer transaction status: {ResponseCode.get_name(transfer_receipt.status)}")

Expand All @@ -60,4 +56,4 @@ def create_account_and_transfer():
print(f"Updated balance of new account: {updated_balance.hbars} hbars")

if __name__ == "__main__":
create_account_and_transfer()
create_account_and_transfer()
100 changes: 77 additions & 23 deletions src/hedera_sdk_python/account/account_create_transaction.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Union

from hedera_sdk_python.transaction.transaction import Transaction
from hedera_sdk_python.hapi.services import crypto_create_pb2, duration_pb2
from hedera_sdk_python.response_code import ResponseCode
Expand All @@ -10,15 +12,22 @@ class AccountCreateTransaction(Transaction):
Represents an account creation transaction on the Hedera network.
"""

def __init__(self, key=None, initial_balance=0, receiver_signature_required=False, auto_renew_period=7890000, memo=""):
def __init__(
self,
key: PublicKey = None,
initial_balance: Union[Hbar, int] = 0,
receiver_signature_required: bool = False,
auto_renew_period: int = 7890000, # 90 days in seconds
memo: str = ""
):
"""
Initializes a new AccountCreateTransaction instance with default values or keyword arguments.
Initializes a new AccountCreateTransaction instance with default values or specified keyword arguments.
Args:
key (PublicKey, optional): The public key for the new account.
initial_balance (int or Hbar, optional): Initial balance in tinybars or as an Hbar instance.
initial_balance (Hbar or int, optional): Initial balance in Hbar or tinybars.
receiver_signature_required (bool, optional): Whether receiver signature is required.
auto_renew_period (int, optional): Auto-renew period in seconds (default is 90 days).
auto_renew_period (int, optional): Auto-renew period in seconds (default is ~90 days).
memo (str, optional): Memo for the account.
"""
super().__init__()
Expand All @@ -27,32 +36,78 @@ def __init__(self, key=None, initial_balance=0, receiver_signature_required=Fals
self.receiver_signature_required = receiver_signature_required
self.auto_renew_period = auto_renew_period
self.account_memo = memo

self._default_transaction_fee = 300_000_000

def set_initial_balance(self, balance):
def set_key(self, key: PublicKey):
"""
Sets the public key for the new account.
Args:
key (PublicKey): The public key to assign to the account.
Returns:
AccountCreateTransaction: The current transaction instance for method chaining.
"""
self._require_not_frozen()
if not isinstance(balance, (Hbar, int)):
raise TypeError("initial_balance must be an instance of Hbar or int representing tinybars.")
self.initial_balance = balance
self.key = key
return self

def set_key(self, key: PublicKey):
def set_initial_balance(self, balance: Union[Hbar, int]):
"""
Sets the initial balance for the new account.
Args:
balance (Hbar or int): The initial balance in Hbar or tinybars.
Returns:
AccountCreateTransaction: The current transaction instance for method chaining.
"""
self._require_not_frozen()
self.key = key
if not isinstance(balance, (Hbar, int)):
raise TypeError(
"initial_balance must be either an instance of Hbar or an integer (tinybars)."
)
self.initial_balance = balance
return self

def set_receiver_signature_required(self, required: bool):
"""
Sets whether a receiver signature is required.
Args:
required (bool): True if required, False otherwise.
Returns:
AccountCreateTransaction: The current transaction instance for method chaining.
"""
self._require_not_frozen()
self.receiver_signature_required = required
return self

def set_auto_renew_period(self, seconds: int):
"""
Sets the auto-renew period in seconds.
Args:
seconds (int): The auto-renew period.
Returns:
AccountCreateTransaction: The current transaction instance for method chaining.
"""
self._require_not_frozen()
self.auto_renew_period = seconds
return self

def set_account_memo(self, memo: str):
"""
Sets the memo for the new account.
Args:
memo (str): The memo to associate with the account.
Returns:
AccountCreateTransaction: The current transaction instance for method chaining.
"""
self._require_not_frozen()
self.account_memo = memo
return self
Expand All @@ -66,18 +121,17 @@ def build_transaction_body(self):
Raises:
ValueError: If required fields are missing.
TypeError: If initial_balance is an invalid type.
"""
if not self.key:
raise ValueError("Key must be set.")

if self.initial_balance is None:
initial_balance_tinybars = 0
elif isinstance(self.initial_balance, Hbar):
raise ValueError("Key must be set before building the transaction.")

if isinstance(self.initial_balance, Hbar):
initial_balance_tinybars = self.initial_balance.to_tinybars()
elif isinstance(self.initial_balance, int):
initial_balance_tinybars = self.initial_balance
else:
raise TypeError("initial_balance must be an instance of Hbar or int representing tinybars.")
raise TypeError("initial_balance must be Hbar or int (tinybars).")

crypto_create_body = crypto_create_pb2.CryptoCreateTransactionBody(
key=self.key.to_proto(),
Expand Down Expand Up @@ -111,7 +165,9 @@ def _execute_transaction(self, client, transaction_proto):
if response.nodeTransactionPrecheckCode != ResponseCode.OK:
error_code = response.nodeTransactionPrecheckCode
error_message = ResponseCode.get_name(error_code)
raise Exception(f"Error during transaction submission: {error_code} ({error_message})")
raise Exception(
f"Error during transaction submission: {error_code} ({error_message})"
)

receipt = self.get_receipt(client)
return receipt
Expand All @@ -121,7 +177,7 @@ def get_receipt(self, client, timeout=60):
Retrieves the receipt for the transaction.
Args:
client (Client): The client instance.
client (Client): The client instance to query.
timeout (int): Maximum time in seconds to wait for the receipt.
Returns:
Expand All @@ -131,7 +187,5 @@ def get_receipt(self, client, timeout=60):
Exception: If the transaction ID is not set or if receipt retrieval fails.
"""
if self.transaction_id is None:
raise Exception("Transaction ID is not set.")

receipt = client.get_transaction_receipt(self.transaction_id, timeout)
return receipt
raise Exception("Transaction ID is not set. Did you forget to sign or execute the transaction?")
return client.get_transaction_receipt(self.transaction_id, timeout)
11 changes: 8 additions & 3 deletions src/hedera_sdk_python/query/account_balance_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ class CryptoGetAccountBalanceQuery(Query):
including hbars and tokens.
"""

def __init__(self):
"""Initializes a new instance of the CryptoGetAccountBalanceQuery class."""
def __init__(self, account_id: AccountId = None):
"""
Initializes a new instance of the CryptoGetAccountBalanceQuery class.
Args:
account_id (AccountId, optional): The ID of the account to retrieve the balance for.
"""
super().__init__()
self.account_id = None
self.account_id = account_id

def set_account_id(self, account_id: AccountId):
"""
Expand Down
9 changes: 8 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from hedera_sdk_python.consensus.topic_delete_transaction import TopicDeleteTransaction
from hedera_sdk_python.consensus.topic_id import TopicId
from hedera_sdk_python.query.topic_info_query import TopicInfoQuery
from hedera_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery

load_dotenv()

Expand Down Expand Up @@ -57,6 +58,11 @@ def create_new_account(client, initial_balance=100000000):

return new_account_id, new_account_private_key

def query_balance(client, account_id):
balance = CryptoGetAccountBalanceQuery(account_id=account_id).execute(client)
print(f"Account {account_id} balance: {balance.hbars}")
return balance

def create_token(client, operator_id, admin_key):
transaction = TokenCreateTransaction(
token_name="ExampleToken",
Expand Down Expand Up @@ -240,6 +246,7 @@ def main():
client.set_operator(operator_id, operator_key)

recipient_id, recipient_private_key = create_new_account(client)
query_balance(client, recipient_id)

token_id = create_token(client, operator_id, admin_key)
associate_token(client, recipient_id, recipient_private_key, token_id)
Expand All @@ -249,7 +256,7 @@ def main():
topic_id = create_topic(client)
submit_message(client, topic_id)
update_topic(client, topic_id)
# query_topic_info(client, topic_id)
query_topic_info(client, topic_id)
delete_topic(client, topic_id)

if __name__ == "__main__":
Expand Down
40 changes: 40 additions & 0 deletions tests/test_account_balance_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pytest
from unittest.mock import MagicMock
from hedera_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery
from hedera_sdk_python.account.account_id import AccountId
from hedera_sdk_python.account.account_balance import AccountBalance
from hedera_sdk_python.client.client import Client
from hedera_sdk_python.hbar import Hbar
from hedera_sdk_python.response_code import ResponseCode

@pytest.mark.usefixtures("mock_account_ids")
def test_build_account_balance_query(mock_account_ids):
"""
Test building a CryptoGetAccountBalanceQuery with a valid account ID.
"""
account_id_sender, account_id_recipient, node_account_id, token_id_1, token_id_2 = mock_account_ids
query = CryptoGetAccountBalanceQuery(account_id=account_id_sender)
assert query.account_id == account_id_sender

@pytest.mark.usefixtures("mock_account_ids")
def test_execute_account_balance_query(mock_account_ids):
"""
Test executing the CryptoGetAccountBalanceQuery with a mocked client.
"""
account_id_sender, account_id_recipient, node_account_id, token_id_1, token_id_2 = mock_account_ids
query = CryptoGetAccountBalanceQuery().set_account_id(account_id_sender)
mock_client = MagicMock(spec=Client)
mock_response = MagicMock()
mock_response.cryptogetAccountBalance.balance = 100000000
mock_response.cryptogetAccountBalance.tokenBalances = []
mock_response.cryptogetAccountBalance.header.nodeTransactionPrecheckCode = ResponseCode.OK
mock_client.send_query = MagicMock(return_value=mock_response)
query.node_account_ids = [node_account_id]
query._make_request = MagicMock(return_value="fake_request")
query._get_status_from_response = MagicMock(return_value=ResponseCode.OK)
query._map_response = MagicMock(
return_value=AccountBalance(Hbar.from_tinybars(100000000))
)
balance = query.execute(mock_client)
assert balance.hbars.to_tinybars() == 100000000
print("Test passed: CryptoGetAccountBalanceQuery executed successfully.")

0 comments on commit 119a0cb

Please sign in to comment.