Skip to content

Asset Clawback

← Back to Transactions

This example demonstrates how to clawback assets from an account using the clawback address and the transact package: 1. Create an asset with clawback address set 2. Transfer assets to a target account 3. Clawback assets from target account using asset_sender field 4. Verify target account balance decreased 5. Verify clawback receiver received the assets Uses AssetTransferTransactionFields with the asset_sender field for clawback operations. Prerequisites:

  • LocalNet running (via algokit localnet start)

From the repository’s examples directory:

Terminal window
cd examples
uv run python transact/06_asset_clawback.py

View source on GitHub

06_asset_clawback.py
# ruff: noqa: N999, C901, PLR0912, PLR0915, PLR2004
"""
Example: Asset Clawback
This example demonstrates how to clawback assets from an account using
the clawback address and the transact package:
1. Create an asset with clawback address set
2. Transfer assets to a target account
3. Clawback assets from target account using asset_sender field
4. Verify target account balance decreased
5. Verify clawback receiver received the assets
Uses AssetTransferTransactionFields with the asset_sender field for clawback operations.
Prerequisites:
- LocalNet running (via `algokit localnet start`)
"""
from shared import (
create_algod_client,
print_error,
print_header,
print_info,
print_step,
print_success,
shorten_address,
wait_for_confirmation,
)
from algokit_transact import (
AssetConfigTransactionFields,
AssetTransferTransactionFields,
PaymentTransactionFields,
Transaction,
TransactionType,
assign_fee,
)
from algokit_utils import AlgorandClient
def main() -> None:
print_header("Asset Clawback Example")
# Step 1: Initialize clients
print_step(1, "Initialize Algod Client")
algod = create_algod_client()
algorand = AlgorandClient.default_localnet()
try:
algod.status()
print_info("Connected to LocalNet Algod")
except Exception as e:
print_error(f"Failed to connect to LocalNet: {e}")
print_info("Make sure LocalNet is running (e.g., algokit localnet start)")
return
# Step 2: Get clawback manager account from KMD
print_step(2, "Get Clawback Manager Account from KMD")
clawback_manager = algorand.account.localnet_dispenser()
print_info(f"Clawback manager address: {shorten_address(clawback_manager.addr)}")
# Step 3: Get suggested transaction parameters
print_step(3, "Get Suggested Transaction Parameters")
suggested_params = algod.suggested_params()
print_info(f"First valid round: {suggested_params.first_valid}")
print_info(f"Last valid round: {suggested_params.last_valid}")
print_info(f"Min fee: {suggested_params.min_fee} microALGO")
# Step 4: Generate and fund target account (will have assets clawed back)
print_step(4, "Generate and Fund Target Account")
target = algorand.account.random()
print_info(f"Target address: {shorten_address(target.addr)}")
# Fund the target with enough ALGO to cover opt-in transaction fee
funding_amount = 1_000_000 # 1 ALGO in microALGO
fund_target_tx_without_fee = Transaction(
transaction_type=TransactionType.Payment,
sender=clawback_manager.addr,
first_valid=suggested_params.first_valid,
last_valid=suggested_params.last_valid,
genesis_hash=suggested_params.genesis_hash,
genesis_id=suggested_params.genesis_id,
payment=PaymentTransactionFields(
receiver=target.addr,
amount=funding_amount,
),
)
fund_target_tx = assign_fee(
fund_target_tx_without_fee,
fee_per_byte=suggested_params.fee,
min_fee=suggested_params.min_fee,
)
signed_fund_target_tx = clawback_manager.signer([fund_target_tx], [0])
algod.send_raw_transaction(signed_fund_target_tx[0])
wait_for_confirmation(algod, fund_target_tx.tx_id())
print_info("Funded target with 1 ALGO for transaction fees")
# Step 5: Generate and fund clawback receiver account (will receive clawed back assets)
print_step(5, "Generate and Fund Clawback Receiver Account")
clawback_receiver = algorand.account.random()
print_info(f"Clawback receiver address: {shorten_address(clawback_receiver.addr)}")
fund_receiver_suggested_params = algod.suggested_params()
fund_receiver_tx_without_fee = Transaction(
transaction_type=TransactionType.Payment,
sender=clawback_manager.addr,
first_valid=fund_receiver_suggested_params.first_valid,
last_valid=fund_receiver_suggested_params.last_valid,
genesis_hash=fund_receiver_suggested_params.genesis_hash,
genesis_id=fund_receiver_suggested_params.genesis_id,
payment=PaymentTransactionFields(
receiver=clawback_receiver.addr,
amount=funding_amount,
),
)
fund_receiver_tx = assign_fee(
fund_receiver_tx_without_fee,
fee_per_byte=fund_receiver_suggested_params.fee,
min_fee=fund_receiver_suggested_params.min_fee,
)
signed_fund_receiver_tx = clawback_manager.signer([fund_receiver_tx], [0])
algod.send_raw_transaction(signed_fund_receiver_tx[0])
wait_for_confirmation(algod, fund_receiver_tx.tx_id())
print_info("Funded clawback receiver with 1 ALGO for transaction fees")
# Step 6: Create an asset with clawback address set
print_step(6, "Create Asset with Clawback Address Set")
asset_total = 10_000_000_000 # 10,000 units with 6 decimals
asset_decimals = 6
asset_name = "Clawbackable Token"
asset_unit_name = "CLW"
create_suggested_params = algod.suggested_params()
print_info(f"Creating asset: {asset_name} ({asset_unit_name})")
print_info(f"Clawback address set to: {shorten_address(clawback_manager.addr)}")
create_asset_tx_without_fee = Transaction(
transaction_type=TransactionType.AssetConfig,
sender=clawback_manager.addr,
first_valid=create_suggested_params.first_valid,
last_valid=create_suggested_params.last_valid,
genesis_hash=create_suggested_params.genesis_hash,
genesis_id=create_suggested_params.genesis_id,
asset_config=AssetConfigTransactionFields(
asset_id=0, # 0 indicates asset creation
total=asset_total,
decimals=asset_decimals,
default_frozen=False,
asset_name=asset_name,
unit_name=asset_unit_name,
url="https://example.com/clawbackable-token",
manager=clawback_manager.addr,
reserve=clawback_manager.addr,
freeze=clawback_manager.addr,
clawback=clawback_manager.addr, # IMPORTANT: Set clawback address to enable clawback
),
)
create_asset_tx = assign_fee(
create_asset_tx_without_fee,
fee_per_byte=create_suggested_params.fee,
min_fee=create_suggested_params.min_fee,
)
signed_create_tx = clawback_manager.signer([create_asset_tx], [0])
algod.send_raw_transaction(signed_create_tx[0])
create_pending_info = wait_for_confirmation(algod, create_asset_tx.tx_id())
asset_id = create_pending_info.asset_id
if not asset_id:
raise ValueError("Asset ID not found in pending transaction response")
print_info(f"Asset created with ID: {asset_id}")
print_success(f"Asset {asset_name} (ID: {asset_id}) created with clawback capability!")
# Step 7: Target opts into the asset
print_step(7, "Target Opts Into the Asset")
opt_in_target_suggested_params = algod.suggested_params()
opt_in_target_tx_without_fee = Transaction(
transaction_type=TransactionType.AssetTransfer,
sender=target.addr,
first_valid=opt_in_target_suggested_params.first_valid,
last_valid=opt_in_target_suggested_params.last_valid,
genesis_hash=opt_in_target_suggested_params.genesis_hash,
genesis_id=opt_in_target_suggested_params.genesis_id,
asset_transfer=AssetTransferTransactionFields(
asset_id=asset_id,
receiver=target.addr,
amount=0, # 0 amount for opt-in
),
)
opt_in_target_tx = assign_fee(
opt_in_target_tx_without_fee,
fee_per_byte=opt_in_target_suggested_params.fee,
min_fee=opt_in_target_suggested_params.min_fee,
)
signed_opt_in_target_tx = target.signer([opt_in_target_tx], [0])
algod.send_raw_transaction(signed_opt_in_target_tx[0])
wait_for_confirmation(algod, opt_in_target_tx.tx_id())
print_info("Target opted into the asset")
print_success("Target successfully opted into the asset!")
# Step 8: Clawback receiver opts into the asset
print_step(8, "Clawback Receiver Opts Into the Asset")
opt_in_receiver_suggested_params = algod.suggested_params()
opt_in_receiver_tx_without_fee = Transaction(
transaction_type=TransactionType.AssetTransfer,
sender=clawback_receiver.addr,
first_valid=opt_in_receiver_suggested_params.first_valid,
last_valid=opt_in_receiver_suggested_params.last_valid,
genesis_hash=opt_in_receiver_suggested_params.genesis_hash,
genesis_id=opt_in_receiver_suggested_params.genesis_id,
asset_transfer=AssetTransferTransactionFields(
asset_id=asset_id,
receiver=clawback_receiver.addr,
amount=0, # 0 amount for opt-in
),
)
opt_in_receiver_tx = assign_fee(
opt_in_receiver_tx_without_fee,
fee_per_byte=opt_in_receiver_suggested_params.fee,
min_fee=opt_in_receiver_suggested_params.min_fee,
)
signed_opt_in_receiver_tx = clawback_receiver.signer([opt_in_receiver_tx], [0])
algod.send_raw_transaction(signed_opt_in_receiver_tx[0])
wait_for_confirmation(algod, opt_in_receiver_tx.tx_id())
print_info("Clawback receiver opted into the asset")
print_success("Clawback receiver successfully opted into the asset!")
# Step 9: Transfer assets from creator to target
print_step(9, "Transfer Assets to Target")
transfer_amount = 1_000_000_000 # 1,000 units
display_transfer = transfer_amount / (10**asset_decimals)
print_info(f"Transferring {display_transfer:,.0f} {asset_unit_name} to target")
transfer_suggested_params = algod.suggested_params()
transfer_tx_without_fee = Transaction(
transaction_type=TransactionType.AssetTransfer,
sender=clawback_manager.addr,
first_valid=transfer_suggested_params.first_valid,
last_valid=transfer_suggested_params.last_valid,
genesis_hash=transfer_suggested_params.genesis_hash,
genesis_id=transfer_suggested_params.genesis_id,
asset_transfer=AssetTransferTransactionFields(
asset_id=asset_id,
receiver=target.addr,
amount=transfer_amount,
),
)
transfer_tx = assign_fee(
transfer_tx_without_fee,
fee_per_byte=transfer_suggested_params.fee,
min_fee=transfer_suggested_params.min_fee,
)
signed_transfer_tx = clawback_manager.signer([transfer_tx], [0])
algod.send_raw_transaction(signed_transfer_tx[0])
wait_for_confirmation(algod, transfer_tx.tx_id())
# Verify target's balance before clawback
target_asset_info_before = algod.account_asset_information(target.addr, asset_id)
target_balance_before = target_asset_info_before.asset_holding.amount
display_before = target_balance_before / (10**asset_decimals)
print_info(f"Target balance before clawback: {target_balance_before} ({display_before:,.0f} {asset_unit_name})")
print_success(f"Transferred {display_transfer:,.0f} {asset_unit_name} to target!")
# Step 10: Clawback assets from target to clawback receiver
print_step(10, "Clawback Assets Using asset_sender Field")
print_info("Demonstrating clawback: clawback address takes assets from target and sends to receiver")
print_info("Key: Use asset_sender field to specify the account to clawback FROM")
clawback_amount = 500_000_000 # 500 units (half of what target holds)
display_clawback = clawback_amount / (10**asset_decimals)
print_info(f"Clawback amount: {display_clawback:,.0f} {asset_unit_name}")
clawback_suggested_params = algod.suggested_params()
# IMPORTANT: For clawback, use asset_sender to specify WHO we're taking assets FROM
clawback_tx_without_fee = Transaction(
transaction_type=TransactionType.AssetTransfer,
sender=clawback_manager.addr, # Transaction sender is the clawback address
first_valid=clawback_suggested_params.first_valid,
last_valid=clawback_suggested_params.last_valid,
genesis_hash=clawback_suggested_params.genesis_hash,
genesis_id=clawback_suggested_params.genesis_id,
asset_transfer=AssetTransferTransactionFields(
asset_id=asset_id,
receiver=clawback_receiver.addr, # Assets go TO clawback receiver
amount=clawback_amount,
asset_sender=target.addr, # CLAWBACK: Taking assets FROM target account
),
)
clawback_tx = assign_fee(
clawback_tx_without_fee,
fee_per_byte=clawback_suggested_params.fee,
min_fee=clawback_suggested_params.min_fee,
)
# Only the clawback address needs to sign (not the target)
signed_clawback_tx = clawback_manager.signer([clawback_tx], [0])
algod.send_raw_transaction(signed_clawback_tx[0])
wait_for_confirmation(algod, clawback_tx.tx_id())
print_success("Clawback transaction confirmed!")
# Step 11: Verify target account balance decreased
print_step(11, "Verify Target Account Balance Decreased")
target_asset_info_after = algod.account_asset_information(target.addr, asset_id)
target_balance_after = target_asset_info_after.asset_holding.amount
expected_target_balance = target_balance_before - clawback_amount
display_before = target_balance_before / (10**asset_decimals)
display_after = target_balance_after / (10**asset_decimals)
display_expected = expected_target_balance / (10**asset_decimals)
print_info(f"Target balance before: {display_before:,.0f} {asset_unit_name}")
print_info(f"Target balance after: {display_after:,.0f} {asset_unit_name}")
print_info(f"Expected balance: {display_expected:,.0f} {asset_unit_name}")
if target_balance_after != expected_target_balance:
raise ValueError(f"Target balance mismatch: expected {expected_target_balance}, got {target_balance_after}")
print_success(f"Target balance correctly decreased by {display_clawback:,.0f} {asset_unit_name}!")
# Step 12: Verify clawback receiver received the assets
print_step(12, "Verify Clawback Receiver Received the Assets")
receiver_asset_info = algod.account_asset_information(clawback_receiver.addr, asset_id)
receiver_balance = receiver_asset_info.asset_holding.amount
display_receiver = receiver_balance / (10**asset_decimals)
print_info(f"Clawback receiver balance: {display_receiver:,.0f} {asset_unit_name}")
if receiver_balance != clawback_amount:
raise ValueError(f"Receiver balance mismatch: expected {clawback_amount}, got {receiver_balance}")
print_success(f"Clawback receiver correctly received {display_clawback:,.0f} {asset_unit_name}!")
# Summary
print_success("Asset clawback example completed successfully!")
print_info("Summary:")
print_info(f" - Created asset {asset_name} (ID: {asset_id}) with clawback address")
print_info(f" - Transferred {display_transfer:,.0f} {asset_unit_name} to target")
print_info(f" - Clawed back {display_clawback:,.0f} {asset_unit_name} from target to receiver")
print_info(f" - Target final balance: {display_after:,.0f} {asset_unit_name}")
print_info(f" - Receiver final balance: {display_receiver:,.0f} {asset_unit_name}")
if __name__ == "__main__":
main()