Group Transaction Comprehension

Video Tutorial

Generate Two Accounts

Create two accounts and add their private keys to your .env file.

from algosdk.account import generate_account

private_key, address = generate_account()
print(private_key, address)

Generate One Asset

Create one asset and place its ID in your .env file.

Note: Remember the account that owns the asset, as it will be used for asset transfers later.

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

load_dotenv()

algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)

private_key = os.getenv('private_key')
address = address_from_private_key(private_key)
params = algod_client.suggested_params()

asset_create_txn = AssetCreateTxn(
    sender=address,
    sp=params,
    total=10,
    decimals=0,
    default_frozen=False,
    manager=address,
    reserve=address,
    asset_name = 'Test Asset One',
    unit_name= 'TEST#1',
)

signed_asset_create_txn = asset_create_txn.sign(private_key)
tx_id = algod_client.send_transaction(signed_asset_create_txn)
print(tx_id)

wait_for_confirmation(algod_client, tx_id)
tx_info = algod_client.pending_transaction_info(tx_id)
print(tx_info)

Launching Contracts

Note: Adjust approval and clear teal filenames, as well as global/local states, as needed.

In this tutorial, we use a second contract, SecondApp, which has 1 local state uint.

from algosdk.transaction import ApplicationCreateTxn, StateSchema, OnComplete, wait_for_confirmation, PaymentTxn
from algosdk.account import address_from_private_key
from algosdk.v2client.algod import AlgodClient
from algosdk import logic
from dotenv import load_dotenv
import base64
import os

load_dotenv()

node_token = os.getenv('algod_token')
node_server = os.getenv('algod_server')
algod_client = AlgodClient(node_token, node_server)

private_key = os.getenv('private_key')
address = address_from_private_key(private_key)

params = algod_client.suggested_params()

approval_teal_file_name = 'TransactionComp.approval.teal'
clear_teal_file_name = 'TransactionComp.clear.teal'

with open(f'./{approval_teal_file_name}', 'r') as f:
    approval_teal_source = f.read()

with open(f'{clear_teal_file_name}', 'r') as f:
    clear_teal_source = f.read()

approval_result = algod_client.compile(approval_teal_source)
approval_program = base64.b64decode(approval_result['result'])

clear_result = algod_client.compile(clear_teal_source)
clear_program = base64.b64decode(clear_result['result'])

global_schema = StateSchema(num_uints=0, num_byte_slices=0)
local_schema = StateSchema(num_uints=0, num_byte_slices=0)

txn = ApplicationCreateTxn(
    sender = address,
    sp = params,
    on_complete=OnComplete.NoOpOC,
    approval_program=approval_program,
    clear_program=clear_program,
    global_schema=global_schema,
    local_schema=local_schema,
)
signed_txn = txn.sign(private_key)

try:
    txid = algod_client.send_transaction(signed_txn)
except Exception as e:
    print(e)
    
print(f'Tx ID: {txid}')
wait_for_confirmation(algod_client, txid)
tx_info = algod_client.pending_transaction_info(txid)
print(f'App ID: {tx_info['application-index']}')

app_address = logic.get_application_address(tx_info['application-index'])
print(f'Application Address: {app_address}')


activate_contract = PaymentTxn(
    sender = address,
    sp = params,
    receiver = app_address,
    amt = 100_000
)

signed_activation = activate_contract.sign(private_key)
activation_tx = algod_client.send_transaction(signed_activation)
print(f'MBR For Contract to be Active Account Funded: {activation_tx}')

Group Transaction Comprehension (GTXN)

In this section, we put it all together and use relative indexing in our group transaction method to enhance scalability.

from algopy import ARC4Contract, Txn, Bytes, UInt64, String, itxn, Global, OnCompleteAction, gtxn, TransactionType
from algopy.arc4 import abimethod, Address, arc4_signature, abi_call
from algopy.arc4 import UInt64 as arc4UInt64

class TransactionComp(ARC4Contract):
    def __init__(self) -> None:
        pass
    
    @abimethod
    def txn_comprehension(self) -> tuple[Address, Address, Bytes, arc4UInt64, arc4UInt64, String, String, arc4UInt64, Address, arc4UInt64, String, Address]:
        sender = Address(Txn.sender)
        foreign_accounts = Address(Txn.accounts(1))
        app_arg_1 = Txn.application_args(0)
        txn_foreign_applications = Txn.applications(0)
        txn_foreign_application_id = arc4UInt64(txn_foreign_applications.id)
        foreign_asset_1 = Txn.assets(0)
        foreign_asset_id = arc4UInt64(foreign_asset_1.id)
        foreign_asset_name = String.from_bytes(foreign_asset_1.name)
        foreign_asset_unit_name = String.from_bytes(foreign_asset_1.unit_name)
        sender_foreign_asset_balance = arc4UInt64(foreign_asset_1.balance(Txn.sender))
        foreign_asset_creator_address = Address(foreign_asset_1.creator)
        fee_for_this_transaction = arc4UInt64(Txn.fee)
        transaction_type = String.from_bytes(Txn.type)
        transaction_id = Address(Txn.tx_id)
        return sender, foreign_accounts, app_arg_1, txn_foreign_application_id, foreign_asset_id, foreign_asset_name, foreign_asset_unit_name, sender_foreign_asset_balance, foreign_asset_creator_address, fee_for_this_transaction, transaction_type, transaction_id        

    @abimethod
    def inner_txn_comprehension(self) -> tuple[UInt64, UInt64, UInt64]:
        asset_config_txn = itxn.AssetConfig(
            total=1,
            unit_name="TEST#1",
            asset_name="TEST ASSET ONE",
            decimals=0,
            default_frozen=False,
            manager=Global.current_application_address,
            reserve=Global.current_application_address,
            fee=Global.min_txn_fee,
            ).submit()        
        
        asset_config_txn_2 = itxn.AssetConfig(
            total=1,
            unit_name="TEST#2",
            asset_name="TEST ASSET TWO",
            decimals=0,
            default_frozen=False,
            manager=Global.current_application_address,
            reserve=Global.current_application_address,
            fee=Global.min_txn_fee,
            )        
        
        asset_config_txn_3 = itxn.AssetConfig(
            total=1,
            unit_name="TEST#3",
            asset_name="TEST ASSET THREE",
            decimals=0,
            default_frozen=False,
            manager=Global.current_application_address,
            reserve=Global.current_application_address,
            fee=Global.min_txn_fee,
            )
        
        submit_tx_1, submit_tx_2 = itxn.submit_txns(asset_config_txn_2, asset_config_txn_3)        
            
        return asset_config_txn.created_asset.id, submit_tx_1.created_asset.id, submit_tx_2.created_asset.id    
    
    
    @abimethod
    def inner_txn_two(self) -> UInt64:
        
        method_signature = arc4_signature('return_nothing()void')
        
        itxn.ApplicationCall(
            app_id=727594507,
            app_args=(method_signature,),
            on_completion=OnCompleteAction.NoOp,
            fee=Global.min_txn_fee
        ).submit()        
        
        result, txn = abi_call[UInt64](
            'local_state_return_something',
            app_id=727594507,
            on_completion=OnCompleteAction.OptIn,
            fee=Global.min_txn_fee
        )
        
        txn = abi_call(
            'return_nothing',
            app_id=727594507,
            fee=Global.min_txn_fee
        )
        
        return result        

    @abimethod
    def group_txn_comprehension(
        self,
        first_transaction: gtxn.PaymentTransaction
    ) -> tuple[UInt64, UInt64, UInt64]:
        
        assert first_transaction.amount == 1000
        
        second_transaction = gtxn.Transaction(Txn.group_index + 1)
        
        if second_transaction.type == TransactionType.Payment:
            second_transaction_amount = second_transaction.amount
            assert second_transaction_amount == 2000
            
        elif second_transaction.type == TransactionType.AssetTransfer:
            second_transaction_amount = second_transaction.asset_amount
            assert second_transaction_amount == 1
            
        third_transaction = gtxn.Transaction(Txn.group_index + 2)

        if third_transaction.type == TransactionType.Payment:
            third_transaction_amount = third_transaction.amount
            assert third_transaction_amount == 2000

        elif third_transaction.type == TransactionType.AssetTransfer:
            third_transaction_amount = third_transaction.asset_amount
            assert third_transaction_amount == 1            
                    
        return first_transaction.amount, second_transaction_amount, third_transaction_amount

Code Editor