Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
db20296
feat: penidng airdrop id
exploreriii Jul 22, 2025
f8488b0
chore: improved docstring for clarity
exploreriii Jul 22, 2025
c153913
chore: test for token airdrop, expanding edge errors
exploreriii Jul 22, 2025
f7a478c
chore: remove duplicate test
exploreriii Jul 22, 2025
63ef43c
feat: ensure sender and receivers are provided
exploreriii Jul 22, 2025
d8a298b
feat: rough and notes token airdrop pending transaction
exploreriii Jul 22, 2025
ec3a239
feat: rough validations for pending airdrop ids
exploreriii Jul 22, 2025
5b8e93a
feat: completing validations in setters
exploreriii Jul 22, 2025
a2afead
chore: now using hashable
exploreriii Jul 22, 2025
d5aa36a
chore: removing initial notes token airdrop pending transaction
exploreriii Jul 22, 2025
3dfa0f2
feat: to and from proto methods for token airdrop pending
exploreriii Jul 22, 2025
6e0ff80
style: pylinting token airdrop pending transaction
exploreriii Jul 22, 2025
9e958f2
fix: _from proto not from proto
exploreriii Jul 22, 2025
460a1fe
feat: string method token airdrop pending
exploreriii Jul 22, 2025
6044661
fix: missing get method token airdrop pending
exploreriii Jul 22, 2025
fd6896f
style: pylint and mypy upgrades
exploreriii Jul 22, 2025
c7002db
cleaner string
exploreriii Jul 26, 2025
eb6b840
refactor: some robustness checks
exploreriii Jul 26, 2025
92725a8
refactor: using dataclass in pending id
exploreriii Jul 26, 2025
fa5d886
chore: remove old notes from testing
exploreriii Jul 26, 2025
264ded2
chore: rename errors so tests are consistent
exploreriii Jul 26, 2025
38f161b
style: correct naming scheme
piyush588 Jul 27, 2025
9d92b3d
chore: rename the file to align with the class name
piyush588 Jul 27, 2025
4e098bc
feat: freezing account id
exploreriii Aug 17, 2025
b7772e1
feat: hash and eq token airdrop
exploreriii Aug 17, 2025
5a97ca5
feat: working e2e example for token claim but needs a lot of tidy
exploreriii Aug 17, 2025
d0a8dbd
feat: add repr to token airdrop
exploreriii Aug 17, 2025
b0f76b0
chore: some tidy, need to tidy into functions and work on auto associ…
exploreriii Aug 17, 2025
3c96e2f
feat: handling signature required airdrop claim in example
exploreriii Aug 19, 2025
ce64ba0
chore: notes on not required signature example
exploreriii Aug 19, 2025
32c1826
fix: remaining naming issues
exploreriii Aug 19, 2025
c0e1338
chore: changelog update
exploreriii Aug 19, 2025
a4ee291
feat: unit tests for token airdrop claim
exploreriii Aug 19, 2025
8183394
feat: unit tests
exploreriii Aug 19, 2025
4f41c94
chore: integration tests
exploreriii Aug 20, 2025
01b04eb
chore: changelog update
exploreriii Aug 20, 2025
87b5bee
fix: duplicate variable name
exploreriii Aug 20, 2025
13c8b1a
chore: token airdrop cancel rename
exploreriii Sep 6, 2025
f1f9b62
chore: bump proto, need to refactor proto method
exploreriii Sep 6, 2025
11969fe
chore: update init py
exploreriii Sep 6, 2025
814a287
chore: example for token airdrop tidied
exploreriii Sep 6, 2025
10526cf
chore: rename token airdrop cancel
exploreriii Sep 6, 2025
d3ea82d
fix: bump proto
exploreriii Sep 6, 2025
a3db54f
chore: rename unit tests airdrops
exploreriii Sep 6, 2025
7103f15
chore: rename integration tests airdrops
exploreriii Sep 6, 2025
d8069c6
chore: add examples for auto airdrop and signed airdrop
exploreriii Sep 6, 2025
5e632de
chore: expand unit tests
exploreriii Sep 6, 2025
1e2acd0
chore: integration tests
exploreriii Sep 6, 2025
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
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- ContractDeleteTransaction class
- ContractExecuteTransaction class
- setMessageAndPay() function in StatefulContract
- TokenClaimAirdropTransaction class and examples
- AccountDeleteTransaction Class
- generate_proto.py
- Bumped Hedera proto version from v0.57.3 to v0.64.3
Expand All @@ -40,6 +41,12 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Update protobuf dependency from 5.28.1 to 5.29.1
- Update grpcio dependency from 1.68.1 to 1.71.2
- Updated `rebasing.md` with clarification on using `git reset --soft HEAD~<n>` where `<n>` specifies the number of commits to rewind.
- Renamed cancel_token_airdrop to token_airdrop_cancel in examples
- Renamed token_airdrop_cancel_transaction or token_airdrop_cancel in src
- Renamed test_token_cancel_airdrop_transaction in unit tests to test_token_airdrop_transaction_cancel
- Renamed test_pending_airdrop_record in unit tests to test_token_airdrop_pending_record
- Renamed test_pending_airdrop_id in unit tests to test_token_airdrop_pending_id
- Renamed token_cancel_airdrop_transaction_e2e in integration tests to token_airdrop_transaction_cancel_e2e_test

### Fixed
- Unit test compatibility issues when running with UV package manager
Expand Down Expand Up @@ -226,4 +233,4 @@ contract_call_local_pb2.ContractLoginfo -> contract_types_pb2.ContractLoginfo
- N/A

### Removed
- N/A
- N/A
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import os
import sys
from dotenv import load_dotenv
from hiero_sdk_python import (
Client,
Network,
AccountId,
PrivateKey,
AccountCreateTransaction,
TokenCreateTransaction,
TokenMintTransaction,
TokenAirdropTransaction,
TokenType,
SupplyType,
NftId,
CryptoGetAccountBalanceQuery,
ResponseCode,
Hbar,
)
from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery

load_dotenv()

# ----------------------- Setup Client -----------------------
def setup_client():
client = Client(Network(os.getenv("NETWORK", "testnet")))
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)
except Exception:
print("❌ Invalid OPERATOR_ID or OPERATOR_KEY in .env")
sys.exit(1)
print("✅ Connected to Hedera network")
return client, operator_id, operator_key

# ----------------------- Create Receiver -----------------------
def create_receiver(client, signature_required=False, max_auto_assoc=10):
receiver_key = PrivateKey.generate()
tx = (
AccountCreateTransaction()
.set_key(receiver_key.public_key())
.set_initial_balance(Hbar(1))
.set_receiver_signature_required(signature_required)
.set_max_automatic_token_associations(max_auto_assoc)
.freeze_with(client)
.sign(receiver_key)
)
receipt = tx.execute(client)
if receipt.status != ResponseCode.SUCCESS:
sys.exit(f"❌ Receiver account creation failed: {receipt.status}")
receiver_id = receipt.account_id
print(f"✅ Receiver account created: {receiver_id} (auto-assoc={max_auto_assoc}, sig_required={signature_required})")
return receiver_id, receiver_key

# ----------------------- Create Fungible Token -----------------------
def create_fungible_token(client, operator_id, operator_key, name="FTK", symbol="FT", initial_supply=1000):
tx = TokenCreateTransaction() \
.set_token_name(name) \
.set_token_symbol(symbol) \
.set_initial_supply(initial_supply) \
.set_token_type(TokenType.FUNGIBLE_COMMON) \
.set_supply_type(SupplyType.FINITE) \
.set_max_supply(initial_supply) \
.set_treasury_account_id(operator_id) \
.freeze_with(client) \
.sign(operator_key)
receipt = tx.execute(client)
token_id = receipt.token_id
print(f"✅ Fungible token created: {token_id}")
return token_id

# ----------------------- Create and Mint NFT -----------------------
def create_and_mint_nft(client, operator_id, operator_key, name="NFTK", symbol="NFT"):
tx = TokenCreateTransaction() \
.set_token_name(name) \
.set_token_symbol(symbol) \
.set_token_type(TokenType.NON_FUNGIBLE_UNIQUE) \
.set_initial_supply(0) \
.set_supply_type(SupplyType.FINITE) \
.set_max_supply(100) \
.set_treasury_account_id(operator_id) \
.set_supply_key(operator_key) \
.freeze_with(client) \
.sign(operator_key)
receipt = tx.execute(client)
nft_token_id = receipt.token_id
print(f"✅ NFT created: {nft_token_id}")

tx_mint = TokenMintTransaction(token_id=nft_token_id, metadata=[b"NFT Metadata Auto"]) \
.freeze_with(client).sign(operator_key)
receipt_mint = tx_mint.execute(client)
serial = receipt_mint.serial_numbers[0]
nft_id = NftId(nft_token_id, serial)
print(f"✅ NFT minted: serial {serial}")

return nft_id

# ----------------------- Log Balances -----------------------
def log_balances(client, operator_id, receiver_id, ft_ids, nft_ids, prefix=""):
print(f"\n{prefix} balances and NFT ownership:")
for ft_id in ft_ids:
sender_balance = CryptoGetAccountBalanceQuery(operator_id).execute(client).token_balances.get(ft_id, 0)
receiver_balance = CryptoGetAccountBalanceQuery(receiver_id).execute(client).token_balances.get(ft_id, 0)
print(f"Fungible token {ft_id} -> Sender: {sender_balance}, Receiver: {receiver_balance}")
for nft_id in nft_ids:
info = TokenNftInfoQuery(nft_id=nft_id).execute(client)
print(f"NFT {nft_id} now owned by {info.account_id}")

# ----------------------- Perform Airdrop -----------------------
def perform_airdrop(client, operator_id, operator_key, receiver_id, ft_ids, nft_ids, ft_amount=100):
tx = TokenAirdropTransaction()
for ft_id in ft_ids:
tx.add_token_transfer(ft_id, operator_id, -ft_amount)
tx.add_token_transfer(ft_id, receiver_id, ft_amount)
for nft_id in nft_ids:
tx.add_nft_transfer(nft_id, operator_id, receiver_id)
tx.freeze_with(client).sign(operator_key)
receipt = tx.execute(client)
print(f"\n✅ Airdrop executed: {receipt.transaction_id}")

# ----------------------- Verify Airdrop Contents -----------------------
def verify_airdrop_contents(client, operator_id, receiver_id, ft_ids, nft_ids, ft_amount=100):
print("\n🔍 Verifying airdrop transaction contents:")
tx_preview = TokenAirdropTransaction()
for ft_id in ft_ids:
tx_preview.add_token_transfer(ft_id, operator_id, -ft_amount)
tx_preview.add_token_transfer(ft_id, receiver_id, ft_amount)
for nft_id in nft_ids:
tx_preview.add_nft_transfer(nft_id, operator_id, receiver_id)
print("Fungible token transfers scheduled:")
for token_transfer in tx_preview.token_transfers:
print(f" Token {token_transfer.token_id} -> Account {token_transfer.account_id}: {token_transfer.amount}")
print("NFT transfers scheduled:")
for nft_transfer in tx_preview.nft_transfers:
print(f" NFT {nft_transfer.token_id} -> From {nft_transfer.sender_id} to {nft_transfer.receiver_id}, Serial {nft_transfer.serial_number}")

# ----------------------- Main Function -----------------------
def main():
client, operator_id, operator_key = setup_client()
receiver_id, receiver_key = create_receiver(client, signature_required=False)
ft_id = create_fungible_token(client, operator_id, operator_key, name="FTK2", symbol="FT2")
nft_id = create_and_mint_nft(client, operator_id, operator_key, name="NFTK2", symbol="NFT2")
log_balances(client, operator_id, receiver_id, [ft_id], [nft_id], prefix="Before airdrop")

print("\n🔍 Verifying initial state:")
ft_balance_before = CryptoGetAccountBalanceQuery(receiver_id).execute(client).token_balances.get(ft_id, 0)
print("✅ Receiver had no FT balance before → no prior association." if ft_balance_before == 0 else "⚠️ Receiver already had FT balance (check associations).")

nft_info_before = TokenNftInfoQuery(nft_id=nft_id).execute(client)
print("✅ Operator owned NFT before → ready to transfer." if nft_info_before.account_id == operator_id else "⚠️ NFT was already transferred.")

verify_airdrop_contents(client, operator_id, receiver_id, [ft_id], [nft_id], ft_amount=100)
perform_airdrop(client, operator_id, operator_key, receiver_id, [ft_id], [nft_id], ft_amount=100)
log_balances(client, operator_id, receiver_id, [ft_id], [nft_id], prefix="After airdrop")

print("\n🔍 Verifying auto-association & no-signature behavior:")
ft_balance_after = CryptoGetAccountBalanceQuery(receiver_id).execute(client).token_balances.get(ft_id, 0)
print("✅ Auto-association successful: Receiver accepted new fungible tokens without pre-association." if ft_balance_after > ft_balance_before else "❌ Auto-association failed.")

nft_info_after = TokenNftInfoQuery(nft_id=nft_id).execute(client)
print("✅ Signature not required: Receiver owns NFT without signing." if nft_info_after.account_id == receiver_id else "❌ NFT transfer failed.")

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