Group Transaction

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.

TransactionComprehension_4.py
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