Inner Transaction Comprehension

Video Tutorial

Compiling Contracts

Compile contracts with:

algokit compile py filename.py

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(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}')

Inner Transaction Comprehension (ITXN and abi_calls)

Inner Txn with itxn Class

from algopy import ARC4Contract, UInt64, itxn, Global
from algopy.arc4 import abimethod

class TransactionComp(ARC4Contract):
    def __init__(self) -> None:
        pass   

    @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
interact_with_contract_2.py

Inner Txn with abi_call Class

Note: Deploy a second application and place the generated app ID in the new inner transaction method, inner_txn_two.

from algopy import ARC4Contract, LocalState, Txn, UInt64, gtxn
from algopy.arc4 import abimethod

class MySecondContract(ARC4Contract):
    def __init__(self) -> None:
        self.opted_in = LocalState(UInt64)        
        
    @abimethod()
    def return_nothing(self) -> None:
        pass        
        
    @abimethod(allow_actions=['OptIn'])
    def local_state_return_something(self,
                                     payment_txn: gtxn.PaymentTransaction) -> UInt64:
        self.opted_in[Txn.sender] = UInt64(1)
        return UInt64(1)

Replace the second App ID in the contract below, compile the contract, and include the new app in the Foreign Assets Array.

from algopy import ARC4Contract, UInt64, itxn, Global, OnCompleteAction
from algopy.arc4 import abimethod, arc4_signature, abi_call

class TransactionComp(ARC4Contract):
    def __init__(self) -> None:
        pass    

    @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
        
from algokit_utils import ApplicationClient
from algosdk.v2client.algod import AlgodClient
from algosdk.atomic_transaction_composer import AccountTransactionSigner, AtomicTransactionComposer, TransactionWithSigner
from algosdk.transaction import PaymentTxn, AssetTransferTxn
from algosdk.account import address_from_private_key
from pathlib import Path
import os
from algosdk.util import algos_to_microalgos
from algosdk.abi import ABIType
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)

path = Path(__file__).parent / './TransactionComp.arc32.json'
app_id = int(os.getenv('app_id'))
private_key = os.getenv('private_key')
signer = AccountTransactionSigner(private_key)
address = address_from_private_key(private_key)
params = algod_client.suggested_params()

app_client = ApplicationClient(
    algod_client=algod_client,
    app_spec=path,
    app_id=app_id,
    signer=signer,
    sender=address,
    suggested_params=params,
)

atc = AtomicTransactionComposer()

#asset_id = 723609532

#arbitrary_account = 'MXOT5NO6DVCGIS22AWEP7N5WD72RSV6QJG4OWGDOTEH3DQKCG4R4WLLMR4'

#app_client.compose_call(atc, call_abi_method='txn_comprehension', transaction_parameters={'accounts': [arbitrary_account], 'foreign_assets': [asset_id]})

#app_client.compose_call(
#    atc, 
#    call_abi_method='inner_txn_comprehension',
#)

second_app = int(os.getenv('second_app_id'))

app_client.compose_call(
    atc, 
    call_abi_method='inner_txn_two',
        transaction_parameters={
        'foreign_apps': [second_app]
    }
)

result = atc.execute(algod_client, 2)

all_tx_ids = [result.abi_results[i].tx_id for i in range(len(result.abi_results))]
all_abi_results = [result.abi_results[i].return_value for i in range(len(result.abi_results))]

print(all_tx_ids)
print(all_abi_results)

Code Editor