Skip to content

Commit e08d303

Browse files
committed
Merge branch 'main' into Pylint
Signed-off-by: exploreriii <[email protected]>
2 parents b715f66 + 5ac763a commit e08d303

13 files changed

+958
-25
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from hiero_sdk_python.account.account_id import AccountId
2+
from hiero_sdk_python.hapi.services import basic_types_pb2
3+
from hiero_sdk_python.tokens.token_id import TokenId
4+
from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer
5+
from hiero_sdk_python.tokens.token_transfer import TokenTransfer
6+
from hiero_sdk_python.tokens.token_transfer_list import TokenTransferList
7+
from hiero_sdk_python.transaction.transaction import Transaction
8+
9+
class AbstractTokenTransferTransaction(Transaction):
10+
def __init__(self):
11+
"""
12+
Initializes a new AbstractTokenTransferTransaction instance.
13+
"""
14+
super().__init__()
15+
self.token_transfers: list[TokenTransfer] = []
16+
self.nft_transfers: list[TokenNftTransfer] = []
17+
self._default_transaction_fee = 100_000_000
18+
19+
def _init_token_transfers(self, token_transfers: list[TokenTransfer]):
20+
for transfer in token_transfers:
21+
self._add_token_transfer(transfer.token_id, transfer.account_id, transfer.amount, transfer.expected_decimals, transfer.is_approved)
22+
23+
def _init_nft_transfers(self, nft_transfers: list[TokenNftTransfer]):
24+
for transfer in nft_transfers:
25+
self._add_nft_transfer(transfer.token_id, transfer.sender_id, transfer.receiver_id, transfer.serial_number, transfer.is_approved)
26+
27+
def _add_token_transfer(self, token_id: TokenId, account_id: AccountId, amount: int, expected_decimals: int=None, is_approved: bool=False):
28+
"""
29+
Adds a token transfer to the transaction.
30+
"""
31+
if amount == 0:
32+
raise ValueError("Amount must be a non-zero integer.")
33+
34+
self.token_transfers.append(
35+
TokenTransfer(token_id, account_id, amount, expected_decimals, is_approved)
36+
)
37+
38+
def _add_nft_transfer(self, token_id: TokenId, sender: AccountId, receiver: AccountId, serial_number: int, is_approved: bool=False):
39+
"""
40+
Adds a nft transfer to the transaction.
41+
"""
42+
self.nft_transfers.append(
43+
TokenNftTransfer(token_id,sender, receiver, serial_number, is_approved)
44+
)
45+
46+
def build_token_transfers(self) -> 'list[basic_types_pb2.TokenTransferList]':
47+
"""
48+
Aggregates all individual fungible token transfers and NFT transfers into
49+
a list of TokenTransferList objects, where each TokenTransferList groups
50+
transfers for a specific token ID.
51+
52+
Returns:
53+
list[basic_types_pb2.TokenTransferList]: A list of TokenTransferList objects,
54+
each grouping transfers for a specific token ID.
55+
"""
56+
transfer_list: dict[TokenId,TokenTransferList] = {}
57+
58+
for token_transfer in self.token_transfers:
59+
if token_transfer.token_id not in transfer_list:
60+
transfer_list[token_transfer.token_id] = TokenTransferList(
61+
token_transfer.token_id,
62+
expected_decimals=token_transfer.expected_decimals
63+
)
64+
65+
transfer_list[token_transfer.token_id].add_token_transfer(token_transfer)
66+
67+
for nft_transfer in self.nft_transfers:
68+
if nft_transfer.token_id not in transfer_list:
69+
transfer_list[nft_transfer.token_id] = TokenTransferList(
70+
nft_transfer.token_id
71+
)
72+
73+
transfer_list[nft_transfer.token_id].add_nft_transfer(nft_transfer)
74+
75+
token_transfers: list[basic_types_pb2.TokenTransferList] = []
76+
77+
for transfer in list(transfer_list.values()):
78+
net_amount = 0
79+
for token_transfer in transfer.transfers:
80+
net_amount += token_transfer.amount
81+
82+
if net_amount != 0:
83+
raise ValueError("All fungible token transfers must be balanced, debits must equal credits.")
84+
85+
token_transfers.append(transfer._to_proto())
86+
87+
return token_transfers
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
from hiero_sdk_python.channels import _Channel
2+
from hiero_sdk_python.executable import _Method
3+
from hiero_sdk_python.account.account_id import AccountId
4+
from hiero_sdk_python.tokens.nft_id import NftId
5+
from hiero_sdk_python.tokens.token_id import TokenId
6+
from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer
7+
from hiero_sdk_python.tokens.token_transfer import TokenTransfer
8+
from hiero_sdk_python.tokens.abstract_token_transfer_transaction import AbstractTokenTransferTransaction
9+
from hiero_sdk_python.hapi.services import token_airdrop_pb2
10+
11+
class TokenAirdropTransaction(AbstractTokenTransferTransaction):
12+
"""
13+
Represents a token airdrop transaction on the Hedera network.
14+
15+
The TokenAirdropTransaction allows users to transfer tokens to multiple accounts,
16+
handling both fungible tokens and NFTs.
17+
"""
18+
def __init__(self, token_transfers: list[TokenTransfer]|None=None, nft_transfers: list[TokenNftTransfer]|None=None):
19+
"""
20+
Initializes a new TokenAirdropTransaction instance.
21+
22+
Args:
23+
token_transfers (list[TokenTransfer], optional): Initial list of fungible token transfers.
24+
nft_transfers (list[TokenNftTransfer], optional): Initial list of NFT transfers.
25+
"""
26+
super().__init__()
27+
if token_transfers:
28+
self._init_token_transfers(token_transfers)
29+
if nft_transfers:
30+
self._init_nft_transfers(nft_transfers)
31+
32+
def add_token_transfer(self, token_id: TokenId, account_id: AccountId, amount: int) -> 'TokenAirdropTransaction':
33+
"""
34+
Adds a tranfer to token_transfers list
35+
Args:
36+
token_id (TokenId): The ID of the token being transferred.
37+
account_id (AccountId): The accountId of sender/receiver.
38+
amount (int): The amount of the fungible token to transfer.
39+
40+
Returns:
41+
TokenAirdropTransaction: The current instance of the transaction for chaining.
42+
"""
43+
self._require_not_frozen()
44+
self._add_token_transfer(token_id, account_id, amount)
45+
return self
46+
47+
def add_token_transfer_with_decimals(self, token_id: TokenId, account_id: AccountId, amount: int, decimals: int) -> 'TokenAirdropTransaction':
48+
"""
49+
Adds a tranfer with expected_decimals to token_transfers list
50+
Args:
51+
token_id (TokenId): The ID of the token being transferred.
52+
account_id (AccountId): The accountId of sender/receiver.
53+
amount (int): The amount of the fungible token to transfer.
54+
decimals (int): The number specifying the amount in the smallest denomination.
55+
56+
Returns:
57+
TokenAirdropTransaction: The current instance of the transaction for chaining.
58+
"""
59+
self._require_not_frozen()
60+
self._add_token_transfer(token_id, account_id, amount, expected_decimals=decimals)
61+
return self
62+
63+
def add_approved_token_transfer(self, token_id: TokenId, account_id: AccountId, amount: int) -> 'TokenAirdropTransaction':
64+
"""
65+
Adds a tranfer with approve allowance to token_transfers list
66+
Args:
67+
token_id (TokenId): The ID of the token being transferred.
68+
account_id (AccountId): The accountId of sender/receiver.
69+
amount (int): The amount of the fungible token to transfer.
70+
71+
Returns:
72+
TokenAirdropTransaction: The current instance of the transaction for chaining.
73+
"""
74+
self._require_not_frozen()
75+
self._add_token_transfer(token_id, account_id, amount, is_approved=True)
76+
return self
77+
78+
def add_approved_token_transfer_with_decimals(self, token_id: TokenId, account_id: AccountId, amount: int, decimals: int) -> 'TokenAirdropTransaction':
79+
"""
80+
Adds a tranfer with expected_decimals and approve allowance to token_transfers list
81+
Args:
82+
token_id (TokenId): The ID of the token being transferred.
83+
account_id (AccountId): The accountId of sender/receiver.
84+
amount (int): The amount of the fungible token to transfer.
85+
decimals (int): The number specifying the amount in the smallest denomination.
86+
87+
Returns:
88+
TokenAirdropTransaction: The current instance of the transaction for chaining.
89+
"""
90+
self._require_not_frozen()
91+
self._add_token_transfer(token_id, account_id, amount, decimals, True)
92+
return self
93+
94+
def add_nft_transfer(self, nft_id: NftId, sender: AccountId, receiver: AccountId) -> 'TokenAirdropTransaction':
95+
"""
96+
Adds a transfer to the nft_transfers
97+
98+
Args:
99+
nft_id (NftId): The ID of the NFT being transferred.
100+
sender (AccountId): The sender's account ID.
101+
receiver (AccountId): The receiver's account ID.
102+
103+
Returns:
104+
TokenAirdropTransaction: The current instance of the transaction for chaining.
105+
"""
106+
self._require_not_frozen()
107+
self._add_nft_transfer(nft_id.tokenId, sender, receiver, nft_id.serialNumber)
108+
return self
109+
110+
def add_approved_nft_transfer(self, nft_id: NftId, sender: AccountId, receiver: AccountId) -> 'TokenAirdropTransaction':
111+
"""
112+
Adds a transfer to the nft_transfers with approved allowance
113+
114+
Args:
115+
nft_id (NftId): The ID of the NFT being transferred.
116+
sender (AccountId): The sender's account ID.
117+
receiver (AccountId): The receiver's account ID.
118+
119+
Returns:
120+
TokenAirdropTransaction: The current instance of the transaction for chaining.
121+
"""
122+
self._require_not_frozen()
123+
self._add_nft_transfer(nft_id.tokenId, sender, receiver, nft_id.serialNumber,True)
124+
return self
125+
126+
def build_transaction_body(self):
127+
"""
128+
Builds and returns the protobuf transaction body for token airdrop.
129+
"""
130+
token_transfers = self.build_token_transfers()
131+
132+
if (len(token_transfers) < 1 or len(token_transfers) > 10):
133+
raise ValueError("Airdrop transfer list must contain mininum 1 and maximum 10 transfers.")
134+
135+
token_airdrop_body = token_airdrop_pb2.TokenAirdropTransactionBody(
136+
token_transfers=token_transfers
137+
)
138+
transaction_body = self.build_base_transaction_body()
139+
transaction_body.tokenAirdrop.CopyFrom(token_airdrop_body)
140+
141+
return transaction_body
142+
143+
def _get_method(self, channel: _Channel) -> _Method:
144+
return _Method(
145+
transaction_func=channel.token.airdropTokens,
146+
query_func=None
147+
)

src/hiero_sdk_python/tokens/token_nft_transfer.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88
from hiero_sdk_python.account.account_id import AccountId
99
from hiero_sdk_python.hapi.services import basic_types_pb2
10+
from hiero_sdk_python.tokens.token_id import TokenId
1011

1112
class TokenNftTransfer:
1213
"""
@@ -16,8 +17,10 @@ class TokenNftTransfer:
1617
receiver, serial number of the NFT, and whether the transfer is approved.
1718
"""
1819

20+
1921
def __init__(
2022
self,
23+
token_id: TokenId,
2124
sender_id: AccountId,
2225
receiver_id: AccountId,
2326
serial_number: int,
@@ -27,16 +30,18 @@ def __init__(
2730
Initializes a new TokenNftTransfer instance.
2831
2932
Args:
33+
token_id (TokenId): The ID of the token being transferred.
3034
sender_id (AccountId): The account ID of the sender.
3135
receiver_id (AccountId): The account ID of the receiver.
3236
serial_number (int): The serial number of the NFT being transferred.
3337
is_approved (bool, optional): Whether the transfer is approved. Defaults to False.
3438
"""
35-
self.sender_id: AccountId = sender_id
36-
self.receiver_id: AccountId = receiver_id
37-
self.serial_number: int = serial_number
38-
self.is_approved: bool = is_approved
39-
39+
self.token_id: TokenId = token_id
40+
self.sender_id : AccountId = sender_id
41+
self.receiver_id : AccountId = receiver_id
42+
self.serial_number : int = serial_number
43+
self.is_approved : bool = is_approved
44+
4045
def _to_proto(self) -> basic_types_pb2.NftTransfer:
4146
"""
4247
Converts this TokenNftTransfer instance to its protobuf representation.
@@ -52,16 +57,25 @@ def _to_proto(self) -> basic_types_pb2.NftTransfer:
5257
)
5358

5459
@classmethod
55-
def _from_proto(cls, proto: basic_types_pb2.NftTransfer):
60+
def _from_proto(cls, proto: basic_types_pb2.TokenTransferList):
5661
"""
5762
Creates a TokenNftTransfer from a protobuf representation.
5863
"""
59-
return cls(
60-
sender_id=AccountId._from_proto(proto.senderAccountID),
61-
receiver_id=AccountId._from_proto(proto.receiverAccountID),
62-
serial_number=proto.serialNumber,
63-
is_approved=proto.is_approval
64-
)
64+
nftTransfers: list[TokenNftTransfer] = []
65+
66+
token_id = TokenId._from_proto(proto.token)
67+
for nftTransfer in proto.nftTransfers:
68+
nftTransfers.append(
69+
cls(
70+
token_id = token_id,
71+
sender_id=AccountId._from_proto(nftTransfer.senderAccountID),
72+
receiver_id=AccountId._from_proto(nftTransfer.receiverAccountID),
73+
serial_number=nftTransfer.serialNumber,
74+
is_approved=nftTransfer.is_approval
75+
)
76+
)
77+
78+
return nftTransfers
6579

6680
def __str__(self):
6781
"""
@@ -72,6 +86,7 @@ def __str__(self):
7286
"""
7387
return (
7488
"TokenNftTransfer("
89+
f"token={self.token_id}, "
7590
f"sender_id={self.sender_id}, "
7691
f"receiver_id={self.receiver_id}, "
7792
f"serial_number={self.serial_number}, "
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from hiero_sdk_python.account.account_id import AccountId
2+
from hiero_sdk_python.hapi.services import basic_types_pb2
3+
from hiero_sdk_python.tokens.token_id import TokenId
4+
5+
class TokenTransfer:
6+
"""
7+
Represents a single fungible token transfer, detailing the token, the account involved,
8+
the amount, and optional approval status and decimal expectations.
9+
"""
10+
def __init__(self, token_id: TokenId, account_id: AccountId, amount: int, expected_decimals: int=None, is_approved: bool=False):
11+
"""
12+
Initializes a new TokenTransfer instance.
13+
14+
Args:
15+
token_id (TokenId): The ID of the token being transferred.
16+
account_id (AccountId): The account ID of the sender or receiver.
17+
amount (int): The amount of the token to send or receive.
18+
expected_decimals (optional, int): The number specifying the amount in the smallest denomination.
19+
is_approved (optional, bool): Indicates whether this transfer is an approved allowance.
20+
"""
21+
self.token_id: TokenId = token_id
22+
self.account_id: AccountId = account_id
23+
self.amount: int = amount
24+
self.expected_decimals: int = expected_decimals
25+
self.is_approved: bool = is_approved
26+
27+
def _to_proto(self) -> basic_types_pb2.AccountAmount:
28+
"""
29+
Converts this TokenTransfer instance to its protobuf representation, AccountAmount.
30+
31+
Returns:
32+
AccountAmount: The protobuf representation of this TokenTransfer.
33+
"""
34+
return basic_types_pb2.AccountAmount(
35+
accountID=self.account_id._to_proto(),
36+
amount=self.amount,
37+
is_approval=self.is_approved
38+
)
39+
40+
def __str__(self) -> str:
41+
"""
42+
Returns a string representation of this TokenTransfer instance.
43+
44+
Returns:
45+
str: A string representation of this TokenTransfer.
46+
"""
47+
return f"TokenTransfer(token_id={self.token_id}, account_id={self.account_id}, amount={self.amount}, expected_decimals={self.expected_decimals}, is_approved={self.is_approved})"

0 commit comments

Comments
 (0)