Skip to content

Commit 965e66d

Browse files
authored
feat: Refactor TokenDissociateTransaction to use set_token_ids method (#830)
Signed-off-by: MonaaEid <monaa_eid@hotmail.com> Signed-off-by: MontyPokemon <59332150+MonaaEid@users.noreply.github.com>
1 parent ad0b026 commit 965e66d

File tree

5 files changed

+107
-12
lines changed

5 files changed

+107
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
5656
- Allow `PrivateKey` to be used for keys in `TopicCreateTransaction` for consistency.
5757
- Update github actions checkout from 5.0.0 to 5.0.1 (#814)
5858
- changed to add concurrency to workflow bot
59+
- feat: Refactor `TokenDissociateTransaction` to use set_token_ids method and update transaction fee to Hbar, also update `transaction.py` and expand `examples/token_dissociate.py`, `tests/unit/token_dissociate.py`.
5960

6061
### Fixed
6162

examples/tokens/token_dissociate_transaction.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,7 @@ def token_dissociate(client, nft_token_id, fungible_token_id, recipient_id, reci
155155
receipt = (
156156
TokenDissociateTransaction()
157157
.set_account_id(recipient_id)
158-
.add_token_id(nft_token_id)
159-
.add_token_id(fungible_token_id)
158+
.set_token_ids([nft_token_id, fungible_token_id])
160159
.freeze_with(client)
161160
.sign(recipient_key) # Recipient must sign to approve
162161
.execute(client)

src/hiero_sdk_python/tokens/token_dissociate_transaction.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from hiero_sdk_python.channels import _Channel
2323
from hiero_sdk_python.executable import _Method
2424
from hiero_sdk_python.tokens.token_id import TokenId
25+
from hiero_sdk_python.hbar import Hbar
2526

2627
class TokenDissociateTransaction(Transaction):
2728
"""
@@ -49,7 +50,7 @@ def __init__(
4950
self.account_id: Optional[AccountId] = account_id
5051
self.token_ids: List[TokenId] = token_ids or []
5152

52-
self._default_transaction_fee: int = 500_000_000
53+
self._default_transaction_fee = Hbar(2)
5354

5455
def set_account_id(self, account_id: AccountId) -> "TokenDissociateTransaction":
5556
""" Sets the account ID for the token dissociation transaction. """
@@ -63,6 +64,42 @@ def add_token_id(self, token_id: TokenId) -> "TokenDissociateTransaction":
6364
self.token_ids.append(token_id)
6465
return self
6566

67+
def set_token_ids(self, token_ids: List[TokenId]) -> "TokenDissociateTransaction":
68+
"""Sets the list of token IDs to dissociate from the account.
69+
"""
70+
self._require_not_frozen()
71+
self.token_ids = token_ids
72+
return self
73+
74+
def _validate_check_sum(self, client) -> None:
75+
"""Validates the checksums of the account ID and token IDs against the provided client."""
76+
if self.account_id is not None:
77+
self.account_id.validate_checksum(client)
78+
for token_id in (self.token_ids or []):
79+
if token_id is not None:
80+
token_id.validate_checksum(client)
81+
82+
83+
@classmethod
84+
def _from_proto(cls, proto: token_dissociate_pb2.TokenDissociateTransactionBody) -> "TokenDissociateTransaction":
85+
"""
86+
Creates a TokenDissociateTransaction instance from a protobuf
87+
TokenDissociateTransactionBody object.
88+
89+
Args:
90+
proto (TokenDissociateTransactionBody): The protobuf
91+
representation of the token dissociate transaction.
92+
"""
93+
account_id = AccountId._from_proto(proto.account)
94+
token_ids = [TokenId._from_proto(token_proto) for token_proto in proto.tokens]
95+
96+
transaction = cls(
97+
account_id=account_id,
98+
token_ids=token_ids
99+
)
100+
101+
return transaction
102+
66103
def _build_proto_body(self) -> token_dissociate_pb2.TokenDissociateTransactionBody:
67104
"""
68105
Returns the protobuf body for the token dissociate transaction.

src/hiero_sdk_python/transaction/transaction.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from hiero_sdk_python.hapi.services import (basic_types_pb2, transaction_pb2, transaction_contents_pb2, transaction_pb2)
1313
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody
1414
from hiero_sdk_python.hapi.services.transaction_response_pb2 import (TransactionResponse as TransactionResponseProto)
15+
from hiero_sdk_python.hbar import Hbar
1516
from hiero_sdk_python.response_code import ResponseCode
1617
from hiero_sdk_python.transaction.transaction_id import TransactionId
1718
from hiero_sdk_python.transaction.transaction_response import TransactionResponse
@@ -61,8 +62,9 @@ def __init__(self) -> None:
6162
# This allows us to maintain the signatures for each unique transaction
6263
# and ensures that the correct signatures are used when submitting transactions
6364
self._signature_map: dict[bytes, basic_types_pb2.SignatureMap] = {}
64-
self._default_transaction_fee = 2_000_000
65-
self.operator_account_id = None
65+
# changed from int: 2_000_000 to Hbar: 0.02
66+
self._default_transaction_fee = Hbar(0.02)
67+
self.operator_account_id = None
6668
self.batch_key: Optional[PrivateKey] = None
6769

6870
def _make_request(self):
@@ -419,7 +421,11 @@ def build_base_transaction_body(self) -> transaction_pb2.TransactionBody:
419421
transaction_body.transactionID.CopyFrom(transaction_id_proto)
420422
transaction_body.nodeAccountID.CopyFrom(self.node_account_id._to_proto())
421423

422-
transaction_body.transactionFee = self.transaction_fee or self._default_transaction_fee
424+
fee = self.transaction_fee or self._default_transaction_fee
425+
if hasattr(fee, "to_tinybars"):
426+
transaction_body.transactionFee = int(fee.to_tinybars())
427+
else:
428+
transaction_body.transactionFee = int(fee)
423429

424430
transaction_body.transactionValidDuration.seconds = self.transaction_valid_duration
425431
transaction_body.generateRecord = self.generate_record
@@ -441,9 +447,13 @@ def build_base_scheduled_body(self) -> SchedulableTransactionBody:
441447
The protobuf SchedulableTransactionBody message with common fields set.
442448
"""
443449
schedulable_body = SchedulableTransactionBody()
444-
schedulable_body.transactionFee = (
445-
self.transaction_fee or self._default_transaction_fee
446-
)
450+
451+
fee = self.transaction_fee or self._default_transaction_fee
452+
if hasattr(fee, "to_tinybars"):
453+
schedulable_body.transactionFee = int(fee.to_tinybars())
454+
else:
455+
schedulable_body.transactionFee = int(fee)
456+
447457
schedulable_body.memo = self.memo
448458
custom_fee_limits = [custom_fee._to_proto() for custom_fee in self.custom_fee_limits]
449459
schedulable_body.max_custom_fees.extend(custom_fee_limits)

tests/unit/test_token_dissociate_transaction.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
from unittest.mock import call, MagicMock, Mock
12
import pytest
2-
from unittest.mock import MagicMock
33
from hiero_sdk_python.tokens.token_dissociate_transaction import TokenDissociateTransaction
44
from hiero_sdk_python.hapi.services import timestamp_pb2
55
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import (
66
SchedulableTransactionBody,
77
)
88
from hiero_sdk_python.transaction.transaction_id import TransactionId
9+
from hiero_sdk_python.tokens.token_id import TokenId
910

1011
pytestmark = pytest.mark.unit
1112

@@ -54,7 +55,7 @@ def test_transaction_body_with_multiple_tokens(mock_account_ids):
5455
dissociate_tx.set_account_id(account_id)
5556
for token_id in token_ids:
5657
dissociate_tx.add_token_id(token_id)
57-
dissociate_tx.operator_account_id = operator_id
58+
dissociate_tx.operator_account_id = operator_id
5859
dissociate_tx.transaction_id = generate_transaction_id(account_id)
5960
dissociate_tx.node_account_id = node_account_id
6061

@@ -68,6 +69,40 @@ def test_transaction_body_with_multiple_tokens(mock_account_ids):
6869
for i, token_id in enumerate(token_ids):
6970
assert transaction_body.tokenDissociate.tokens[i].tokenNum == token_id.num
7071

72+
# This test uses fixture mock_account_ids as parameter
73+
def test_set_token_ids(mock_account_ids):
74+
"""Test setting multiple token IDs at once for dissociation."""
75+
account_id, _, _, token_id_1, token_id_2 = mock_account_ids
76+
token_ids = [token_id_1, token_id_2]
77+
78+
dissociate_tx = TokenDissociateTransaction()
79+
dissociate_tx.set_account_id(account_id)
80+
dissociate_tx.set_token_ids(token_ids)
81+
82+
assert dissociate_tx.token_ids == token_ids
83+
84+
85+
def test_validate_check_sum(mock_account_ids, mock_client, monkeypatch):
86+
"""Test that validate_check_sum method correctly validates account and token IDs."""
87+
account_id, _, _, token_id_1, token_id_2 = mock_account_ids
88+
89+
dissociate_tx = TokenDissociateTransaction()
90+
dissociate_tx.set_account_id(account_id)
91+
dissociate_tx.set_token_ids([token_id_1, token_id_2])
92+
93+
# Mock the validate_checksum methods on the classes to avoid assigning
94+
# attributes on frozen dataclass instances.
95+
monkeypatch.setattr(type(account_id), "validate_checksum", MagicMock())
96+
token_cls = type(token_id_1)
97+
monkeypatch.setattr(token_cls, "validate_checksum", MagicMock())
98+
99+
dissociate_tx._validate_check_sum(mock_client)
100+
101+
type(account_id).validate_checksum.assert_called_once_with(mock_client)
102+
token_validate = type(token_id_1).validate_checksum
103+
assert token_validate.call_count == 2
104+
token_validate.assert_has_calls([call(mock_client), call(mock_client)])
105+
71106
def test_missing_fields():
72107
"""Test that building the transaction without account ID or token IDs raises a ValueError."""
73108
dissociate_tx = TokenDissociateTransaction()
@@ -121,7 +156,20 @@ def test_to_proto(mock_account_ids, mock_client):
121156

122157
assert proto.signedTransactionBytes
123158
assert len(proto.signedTransactionBytes) > 0
124-
159+
160+
def test_from_proto(mock_account_ids):
161+
"""Test creating a TokenDissociateTransaction from a protobuf object."""
162+
account_id, _, _, token_id_1, token_id_2 = mock_account_ids
163+
dissociate_tx = TokenDissociateTransaction()
164+
dissociate_tx.set_account_id(account_id)
165+
dissociate_tx.set_token_ids([token_id_1, token_id_2])
166+
proto_body = dissociate_tx._build_proto_body()
167+
reconstructed_tx = TokenDissociateTransaction._from_proto(proto_body)
168+
assert reconstructed_tx.account_id == account_id
169+
assert len(reconstructed_tx.token_ids) == 2
170+
assert reconstructed_tx.token_ids[0] == token_id_1
171+
assert reconstructed_tx.token_ids[1] == token_id_2
172+
125173
def test_build_scheduled_body(mock_account_ids):
126174
"""Test building a scheduled transaction body for token dissociate transaction."""
127175
account_id, _, _, token_id_1, token_id_2 = mock_account_ids

0 commit comments

Comments
 (0)