Freeze and Clawback

In this chapter, we will explore how to manage assets on the Algorand blockchain using Python. This includes generating accounts, creating assets, opting in accounts to receive assets, transferring assets between accounts, freezing assets, and performing clawback transactions.

1. Generating Accounts

First, we'll generate three Algorand accounts and store their private keys and addresses securely using a .env file.

from algosdk.account import generate_account
from dotenv import load_dotenv, set_key

# Load our .env file where we store discrete information
load_dotenv()

# Generate three accounts
private_key_1, address_1 = generate_account()
private_key_2, address_2 = generate_account()
private_key_3, address_3 = generate_account()

# Print the private key and address generated
print(private_key_1, address_1)
print(private_key_2, address_2)
print(private_key_3, address_3)

# Save our private keys and addresses to our .env
set_key('.env', key_to_set='private_key_1', value_to_set=private_key_1)
set_key('.env', key_to_set='private_key_2', value_to_set=private_key_2)
set_key('.env', key_to_set='private_key_3', value_to_set=private_key_3)

set_key('.env', key_to_set='address_1', value_to_set=address_1)
set_key('.env', key_to_set='address_2', value_to_set=address_2)
set_key('.env', key_to_set='address_3', value_to_set=address_3)

# Fund these three addresses with testnet Algorand @ https://bank.testnet.algorand.network/

Ensure that all three generated accounts are funded with testnet Algorand using the [Algorand Testnet Bank](https://bank.testnet.algorand.network/).

2. Creating an Asset

Next, we'll create a new asset on the Algorand blockchain. This example demonstrates how to create an asset without freeze functionality.

from algosdk.transaction import AssetCreateTxn, wait_for_confirmation
from algosdk.v2client.algod import AlgodClient
from dotenv import load_dotenv, set_key
from algosdk.account import address_from_private_key
import os

# Load the .env file
load_dotenv()

# Instantiate Algorand Client
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

# Get our first private key from .env
private_key_1 = os.getenv('private_key_1')

# Get address from private key
address_1 = address_from_private_key(private_key_1)

# Get parameters for transaction
params = algod_client.suggested_params()

# Define the asset creation transaction parameters
asset_creation_transaction = AssetCreateTxn(
    sender=address_1,
    sp=params,
    total=1000,
    decimals=0,
    default_frozen=False,
    manager=address_1,
    reserve=address_1,
    freeze=address_1,
    asset_name='Test Token',
    unit_name='TEST',   
)

# Sign the transaction
signed_asset_creation_transaction = asset_creation_transaction.sign(private_key_1)

# Submit the transaction, which returns a transaction ID
transaction_id = algod_client.send_transaction(signed_asset_creation_transaction)

# Wait for the transaction to be confirmed 
wait_for_confirmation(algod_client, transaction_id)

# Get the confirmed transaction information
transaction_info = algod_client.pending_transaction_info(transaction_id)

# Get the asset ID from the transaction information returned
new_asset_id = transaction_info['asset-index']

# Print the asset ID
print(new_asset_id)

# Create/Set a key in our .env called asset_id, with its value set to the asset ID as a string
set_key('.env', key_to_set='asset_id', value_to_set=str(new_asset_id))

Note: You may use the same code as we did previously to opt in the second and third accounts into the asset and transfer an amount to them.

3. Opting In Accounts to the Asset

Before accounts can receive the newly created asset, they must opt in. Opting in is essentially an asset transfer to self with the amount field set to 0.

from algosdk.transaction import AssetTransferTxn, wait_for_confirmation, assign_group_id
from algosdk.v2client.algod import AlgodClient
from dotenv import load_dotenv, set_key
from algosdk.account import address_from_private_key
import os

# Load the .env file
load_dotenv()

# Instantiate Algorand Client
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

# Get the private keys of our other two accounts from .env
private_key_2 = os.getenv('private_key_2')
private_key_3 = os.getenv('private_key_3')

# Get the public keys of our other two accounts
address_2 = address_from_private_key(private_key_2)
address_3 = address_from_private_key(private_key_3)

# Get the new asset ID from our .env, make sure to convert to an integer
asset_id = int(os.getenv('asset_id'))

# Get suggested transaction parameters
params = algod_client.suggested_params()

# Create an opt-in transaction for Account 2
optin_account_2 = AssetTransferTxn(
    sender=address_2,
    sp=params,
    receiver=address_2,
    index=asset_id,
    amt=0,
)

# Create an opt-in transaction for Account 3
optin_account_3 = AssetTransferTxn(
    sender=address_3,
    sp=params,
    receiver=address_3,
    index=asset_id,
    amt=0,
)

# Sign the asset opt-in transactions with the respective accounts
signed_optin_tx_account_2 = optin_account_2.sign(private_key_2)
signed_optin_tx_account_3 = optin_account_3.sign(private_key_3)

# Create a list of our unsigned transactions
txs = [optin_account_2, optin_account_3]

# Assign a group ID to the unsigned transaction group
assign_group_id(txs)

# Create a new list and sign the transactions by the correct accounts from our txs list
signed_txs = [txs[0].sign(private_key_2), txs[1].sign(private_key_3)]

# Send the group transactions by using "send_transactions" [plural]
# This returns the transaction ID of the first transaction in the group
submit_txs = algod_client.send_transactions(signed_txs)

print(submit_txs)

4. Transferring Assets to Accounts

Now that the accounts have opted in, we can transfer assets from the creator account (Account 1) to Account 2 and Account 3.

from algosdk.transaction import AssetTransferTxn, wait_for_confirmation, assign_group_id
from algosdk.v2client.algod import AlgodClient
from dotenv import load_dotenv, set_key
from algosdk.account import address_from_private_key
import os

# Load the .env file
load_dotenv()

# Instantiate Algorand Client
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

# Get our first private key from .env
private_key_1 = os.getenv('private_key_1')

# Get address from private key
address_1 = address_from_private_key(private_key_1)

# Get the addresses of our other two accounts from .env
address_2 = os.getenv('address_2')
address_3 = os.getenv('address_3')

# Get the new asset ID from our .env, make sure to convert to an integer
asset_id = int(os.getenv('asset_id'))

# Get suggested transaction parameters
params = algod_client.suggested_params()

# Create two asset transfer transactions to provide each of the other two accounts some of our asset 
provide_asset_to_account_2 = AssetTransferTxn(
    sender=address_1,
    receiver=address_2,
    amt=10,
    index=asset_id,
    sp=params
)

provide_asset_to_account_3 = AssetTransferTxn(
    sender=address_1,
    receiver=address_3,
    amt=10,
    index=asset_id,
    sp=params
)

# Create a list from these two transactions and assign them a group ID
txs = [provide_asset_to_account_2, provide_asset_to_account_3]
assign_group_id(txs)

# Iteratively sign each transaction since account 1 is the sender of both
signed_txs = [tx.sign(private_key_1) for tx in txs]

# Submit the transactions and print the first transaction ID in the group
submit_txs = algod_client.send_transactions(signed_txs)
print(submit_txs)

5. Transferring Assets Between Accounts

We can also transfer assets directly between Account 2 and Account 3.

from algosdk.transaction import AssetTransferTxn, wait_for_confirmation, assign_group_id
from algosdk.v2client.algod import AlgodClient
from dotenv import load_dotenv, set_key
from algosdk.account import address_from_private_key
import os

# Load the .env file
load_dotenv()

# Instantiate Algorand Client
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

# Get the private key and address of our second account from .env
private_key_2 = os.getenv('private_key_2')
address_2 = address_from_private_key(private_key_2)

# Get the public key of our third account
address_3 = os.getenv('address_3')

# Get the new asset ID from our .env, make sure to convert to an integer
asset_id = int(os.getenv('asset_id'))

# Get suggested transaction parameters
params = algod_client.suggested_params()

# Create a transfer transaction from Account 2 to Account 3
transfer_account_2_to_account_3 = AssetTransferTxn(
    sender=address_2,
    receiver=address_3,
    amt=1,
    index=asset_id,
    sp=params,
)

# Sign the transaction
signed_transfer_transaction = transfer_account_2_to_account_3.sign(private_key_2)

# Submit the transaction and print the asset ID
tx_id = algod_client.send_transaction(signed_transfer_transaction)
print(tx_id)

6. Freezing Assets

Asset managers can freeze and unfreeze assets for specific accounts. This example demonstrates how to freeze an account's ability to transfer assets.

from algosdk.transaction import AssetFreezeTxn, wait_for_confirmation
from algosdk.v2client.algod import AlgodClient
from dotenv import load_dotenv, set_key
from algosdk.account import address_from_private_key
import os

# Load the .env file
load_dotenv()

# Instantiate Algorand Client
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

# Get our first account's private key from .env
private_key_1 = os.getenv('private_key_1')

# Get first account's address from private key
address_1 = address_from_private_key(private_key_1)

# Get address of our second account from .env
address_2 = os.getenv('address_2')

# Get the new asset ID from our .env, make sure to convert to an integer
asset_id = int(os.getenv('asset_id'))

# Get parameters for transaction
params = algod_client.suggested_params()

# Create the freeze transaction to prevent address 2 from sending until further notice by setting new_freeze_state to True
freeze_address_2 = AssetFreezeTxn(
    sender=address_1,
    target=address_2,
    new_freeze_state=True,
    index=asset_id,
    sp=params,
)

# Sign the transaction with account 1
signed_freeze_address_2 = freeze_address_2.sign(private_key_1)

# Submit the transaction
tx_id = algod_client.send_transaction(signed_freeze_address_2)
print(tx_id)

Error Handling Example: If you attempt to send an asset from Account 2 to Account 3 after freezing, it will fail with an error similar to this:

algosdk.error.AlgodHTTPError: TransactionPool.Remember: transaction 5RUU5XXTX7NHNWTWUDZ3RVN6SE5ZMGWNQZVRMS7Z2YM6LYVZ3YCQ: asset 730599733 frozen in RAVEP7VNGT37QSYY3VXNX7EYA2DVUSX67H4X3RXBFJMPIOAOZ7BVEMJJ2UPS 

If you want to allow the address to send assets again, resend the same transaction but set new_freeze_state to False.

Note: To have all accounts' assets frozen by default, set default_frozen to True during asset creation. This depends on your specific use case.

7. Creating an Asset with Clawback Functionality

Clawback functionality allows the asset manager to revoke assets from any account. Here's how to create an asset with the clawback feature enabled.

from algosdk.transaction import AssetCreateTxn, wait_for_confirmation
from algosdk.v2client.algod import AlgodClient
from dotenv import load_dotenv, set_key
from algosdk.account import address_from_private_key
import os

# Load the .env file
load_dotenv()

# Instantiate Algorand Client
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

# Get our first private key from .env
private_key_1 = os.getenv('private_key_1')

# Get address from private key
address_1 = address_from_private_key(private_key_1)

# Get parameters for transaction
params = algod_client.suggested_params()

# Define the asset creation transaction parameters with clawback
asset_creation_transaction = AssetCreateTxn(
    sender=address_1,
    sp=params,
    total=1000,
    decimals=0,
    manager=address_1,
    reserve=address_1,
    clawback=address_1,
    asset_name='Test Token',
    unit_name='TEST',   
    default_frozen=False,
)

# Sign the transaction
signed_asset_creation_transaction = asset_creation_transaction.sign(private_key_1)

# Submit the transaction, which returns a transaction ID
transaction_id = algod_client.send_transaction(signed_asset_creation_transaction)

# Wait for the transaction to be confirmed 
wait_for_confirmation(algod_client, transaction_id)

# Get the confirmed transaction information
transaction_info = algod_client.pending_transaction_info(transaction_id)

# Get the asset ID from the transaction information returned
new_asset_id = transaction_info['asset-index']

# Print the asset ID
print(new_asset_id)

# Create/Set a key in our .env called asset_id, with its value set to the asset ID as a string
set_key('.env', key_to_set='asset_id', value_to_set=str(new_asset_id))

# You may use the same code as we did previously to opt in the second and third accounts into the asset and transfer an amount to them

8. Performing Clawback Transactions

With clawback functionality enabled, the asset manager can revoke assets from any account. Below is an example of performing a clawback transaction from Account 2 to Account 3.

from algosdk.transaction import AssetTransferTxn, wait_for_confirmation, assign_group_id
from algosdk.v2client.algod import AlgodClient
from dotenv import load_dotenv, set_key
from algosdk.account import address_from_private_key
import os

# Load the .env file
load_dotenv()

# Instantiate Algorand Client
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

# Get the private key and address of our first account from .env
private_key_1 = os.getenv('private_key_1')
address_1 = address_from_private_key(private_key_1)

# Get the public key of our third account
address_2 = os.getenv('address_2')

# Get the new asset ID from our .env, make sure to convert to an integer
asset_id = int(os.getenv('asset_id'))

# Get suggested transaction parameters
params = algod_client.suggested_params()

# Create a clawback transaction as Account 1 to send the asset from Account 2 to Account 1 
clawback_asset_from_account_2 = AssetTransferTxn(
    sender=address_1,
    receiver=address_1,
    revocation_target=address_2,
    amt=1,
    index=asset_id,
    sp=params,
)

# Sign the transaction
signed_transfer_transaction = clawback_asset_from_account_2.sign(private_key_1)

# Submit the transaction and print the asset ID
tx_id = algod_client.send_transaction(signed_transfer_transaction)
print(tx_id)

# Additional Clawback Example: Revoking from Account 2 to Account 3
address_3 = os.getenv('address_3')
clawback_asset_from_account_2 = AssetTransferTxn(
    sender=address_1,
    receiver=address_3,
    revocation_target=address_2,
    amt=1,
    index=asset_id,
    sp=params,
)

# Sign the transaction
signed_transfer_transaction = clawback_asset_from_account_2.sign(private_key_1)

# Submit the transaction and print the asset ID
tx_id = algod_client.send_transaction(signed_transfer_transaction)
print(tx_id) 

Explanation of Clawback Fields:

  • sender: The account authorizing the clawback (typically the asset manager).
  • receiver: The account receiving the clawbacked assets.
  • revocation_target: The account from which assets are being clawed back.
  • amt: The amount of assets to claw back.
  • index: The asset ID.

Important Notes:

  • The sender of the clawback transaction must be the asset manager.
  • The receiver is where the clawed assets will be sent.
  • The revocation target is the account from which the assets are being removed.

Context of Clawback Transaction:

In the context of a clawback transaction, the sender field is not the account from which the asset is being sent but rather the authorizing party (the asset manager). The receiver is the destination for the clawbacked asset amount, and the revocation_target is the target account from which the asset amount is removed.

Example Error:

algosdk.error.AlgodHTTPError: TransactionPool.Remember: transaction 5RUU5XXTX7NHNWTWUDZ3RVN6SE5ZMGWNQZVRMS7Z2YM6LYVZ3YCQ: asset 730599733 frozen in RAVEP7VNGT37QSYY3VXNX7EYA2DVUSX67H4X3RXBFJMPIOAOZ7BVEMJJ2UPS 

This error occurs if you attempt to transfer assets from a frozen account.


Quiz

Question 1

What function is used to generate a new Algorand account in the algosdk library?





Question 2

How can you obtain the asset ID after creating a new asset?





Question 3

What transaction type is used to freeze an account's assets?





Question 4

Which field in the AssetCreateTxn specifies the ability to revoke assets?





Question 5

What function is used to assign a group ID to a list of transactions?





Code Editor