Creating Launching and Interacting with an Archer Smart Contract

Video Walkthrough

Watch these videos to understand the process of setting up the Archer smart contract:

Part 1:

Part 2:

Generating Accounts

This contract will require two accounts. Use the code below to generate them:

from algosdk.account import generate_account
  
private_key, address = generate_account()
print(private_key, address)

Funding the Accounts

To fund the accounts, visit the Algorand testnet dispenser: https://bank.testnet.algorand.network/

Setting Environment Variables (.env)

Define the following environment variables:

# Private keys for both generated accounts
  private_key=ENTER FIRST PRIVATE KEY
  private_key_2=ENTER SECOND PRIVATE KEY
  
  # Algod token and server (Nodely API)
algod_token=
algod_server=https://testnet-api.4160.nodely.dev

# Application ID generated by LaunchSmartContract.py
app_id=722568747

Archer Class Example

class Archer:
    def __init__(self, x):
        self.arrows = x
        self.bow_equipped = False
        
    def equip_bow(self):
        assert self.bow_equipped == False
        self.bow_equipped = True
        
    def unequip_bow(self):
        self.bow_equipped = False
        
    def shoot_arrow(self):
        assert self.bow_equipped == True
        self.arrows -= 1
      
  
  my_archer = Archer(10)
  print(my_archer.arrows)
  print(my_archer.bow_equipped)
  
  my_archer.equip_bow()
  print(my_archer.bow_equipped)
  
  my_archer.unequip_bow()
  print(my_archer.bow_equipped)
  
  for i in range(5):
      my_archer.shoot_arrow()
      print(my_archer.arrows)

Archer Smart Contract

from algopy import ARC4Contract, GlobalState, LocalState, UInt64, Txn
from algopy.arc4 import abimethod, String

class Archer(ARC4Contract):
    def __init__(self) -> None:
        self.arrows = LocalState(UInt64)
        self.bow_equipped = LocalState(bool)
        self.archersCreated = GlobalState(UInt64(0))
        
    @abimethod(allow_actions=['OptIn'])
    def createArcher(self, x: UInt64) -> tuple[String, String, UInt64, String, UInt64]:
        self.arrows[Txn.sender] = x
        self.bow_equipped[Txn.sender] = False
        self.archersCreated.value += UInt64(1)
        
        return String('An Archer was Created!'), String("Archer has this many arrows: "), x, String("Contract has created this many archers so far: "), self.archersCreated.value
        
    @abimethod()
    def EquipBow(self) -> tuple[String, bool]:
        assert self.bow_equipped[Txn.sender] == False
        self.bow_equipped[Txn.sender] = True
        return String('Archer Equipped their Bow: '), self.bow_equipped[Txn.sender]
        
    @abimethod()
    def UnequipBow(self) -> tuple[String, bool]:
        assert self.bow_equipped[Txn.sender] == True
        self.bow_equipped[Txn.sender] = False
        return String('Archer Unequipped their Bow: '), self.bow_equipped[Txn.sender]

    @abimethod()
    def ShootArrow(self) -> tuple[String, String, UInt64]:
        assert self.bow_equipped[Txn.sender] == True
        assert self.arrows[Txn.sender] > UInt64(0)
        self.arrows[Txn.sender] -= UInt64(1)
        return String('Archer shot an arrow!'), String('Arrows Remaining: '), self.arrows[Txn.sender]

Launching the Contract

Note: Ensure the contract name matches the approval and clear TEAL file names.

If your class is defined as:

class Archer(ARC4Contract):

Your approval and clear TEAL file names in LaunchSmartContract.py should be:

approval_teal_file_name = 'Archer.approval.teal'
clear_teal_file_name = 'Archer.clear.teal'
import os
from algosdk.v2client.algod import AlgodClient
from algosdk.account import address_from_private_key
from base64 import b64decode
from algosdk.transaction import StateSchema, ApplicationCreateTxn, OnComplete, wait_for_confirmation
from algosdk import logic
from dotenv import load_dotenv

load_dotenv()

node_token = os.getenv('algod_token')
node_server = os.getenv('algod_server')
private_key = os.getenv('private_key')

algod_client = AlgodClient(node_token, node_server)

address = address_from_private_key(private_key)

params = algod_client.suggested_params()

approval_teal_file_name = 'Archer.approval.teal'
clear_teal_file_name= 'Archer.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 = b64decode(approval_result['result'])

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

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

tx = 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 = tx.sign(private_key)

try:
    tx_id = algod_client.send_transaction(signed_txn)
except Exception as e:
    print("Failed due to:", e)
    
print(f'Tx ID: {tx_id}')
wait_for_confirmation(algod_client, tx_id)
tx_info = algod_client.pending_transaction_info(tx_id)
application_id = tx_info['application-index']
print(f'Application ID: {application_id}')

app_address = logic.get_application_address(application_id)
print(f'Application Address: {app_address}')

Interacting with the Archer Smart Contract

from algosdk.v2client.algod import AlgodClient
from algokit_utils import ApplicationClient
from algosdk.atomic_transaction_composer import AccountTransactionSigner, AtomicTransactionComposer
from algosdk.account import address_from_private_key
from algosdk.transaction import OnComplete
from pathlib import Path
import os
from dotenv import load_dotenv

load_dotenv()

node_token = os.getenv('algod_token')
node_server = os.getenv('algod_server')

private_key = os.getenv('private_key_2')

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

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

atc = AtomicTransactionComposer()

application_client.compose_call(atc, call_abi_method='createArcher', transaction_parameters={'on_complete': OnComplete.OptInOC}, x=5)
application_client.compose_call(atc, call_abi_method='EquipBow')
application_client.compose_call(atc, call_abi_method='UneqipBow')
application_client.compose_call(atc, call_abi_method='EquipBow', transaction_parameters={'note': 'Extra Equip Bow Transaction'})
application_client.compose_call(atc, call_abi_method='ShootArrow')

result = atc.execute(algod_client, 2)
tx_ids = [result.abi_results[i].tx_id for i in range(0, len(result.abi_results))]
abi_results = [result.abi_results[i].return_value for i in range(0, len(result.abi_results))]

for i in range(len(result.abi_results)):
    print(tx_ids[i])
    print(abi_results[i])
    print('\n')

Code Editor