ARC69 Minter and Modifier Part 1
Video Tutorial
Contract - Registering and Determining the User's Pokemon
Compile the contract using:
algokit compile arc69NFTmodifier.py
Python Code
from algopy import ARC4Contract, Global, GlobalState, gtxn, BoxRef, Txn, op
from algopy.arc4 import abimethod, Struct
from algopy.arc4 import UInt64 as arc4UInt64
from algopy.arc4 import String as arc4String
class availablePokemon(Struct):
pokemon_name: arc4String
pokemon_type: arc4String
pokemon_description: arc4String
pokemon_ipfs_hash: arc4String
class arc69NFTmodifier(ARC4Contract):
def __init__(self) -> None:
self.pokemonUnitCounter = GlobalState(arc4UInt64(0))
@abimethod
def registerNewPokemonData(
self,
pokemon_name: arc4String,
pokemon_type: arc4String,
pokemon_description: arc4String,
pokemon_ipfs_hash: arc4String,
payment_txn: gtxn.PaymentTransaction,
) -> arc4String:
assert payment_txn.receiver == Global.current_application_address
self.pokemonUnitCounter.value = arc4UInt64(self.pokemonUnitCounter.value.native + 1)
box_ref = BoxRef(key=self.pokemonUnitCounter.value.bytes)
value, exists = box_ref.maybe()
assert not exists
new_pokemon_info = availablePokemon(
pokemon_name=pokemon_name,
pokemon_type=pokemon_type,
pokemon_description=pokemon_description,
pokemon_ipfs_hash=pokemon_ipfs_hash
)
box_ref.create(size=new_pokemon_info.bytes.length)
box_ref.put(new_pokemon_info.bytes)
return arc4String('Pokemon Registered to Contract: ') + pokemon_name
@abimethod
def determineUserPokemon(
self,
payment_txn: gtxn.PaymentTransaction
) -> tuple[arc4String, arc4UInt64]:
assert payment_txn.amount == 18_500
assert payment_txn.receiver == Global.current_application_address
randomizer = arc4UInt64(
Global.latest_timestamp + Global.round + arc4UInt64.from_bytes(Txn.tx_id[0:8]).native % arc4UInt64.from_bytes(Txn.tx_id[-8:]).native
)
hashed_randomizer = op.sha256(randomizer.bytes)
pokemon_selection = arc4UInt64((arc4UInt64.from_bytes(hashed_randomizer[0:8]).native % self.pokemonUnitCounter.value.native) + 1)
users_claim_box = BoxRef(key=Txn.sender.bytes)
value, exists = users_claim_box.maybe()
assert not exists
users_claim_box.create(size=8)
users_claim_box.put(pokemon_selection.bytes)
return arc4String('Users Pokemon # Selected:'), pokemon_selection
Setting Up Environment Variables (.env)
private_key = ENTER PRIVATE KEY
address = ENTER ADDRESS (OPTIONAL)
api_key = ENTER API KEY
api_secret = ENTER API SECRET
jwt = ENTER JWT
algod_token =
algod_server = https://testnet-api.4160.nodely.dev
app_id = ENTER APP ID GENERATED
Upload Pokemon Images to IPFS
from pinata import Pinata
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv('api_key')
api_secret = os.getenv('api_secret')
jwt = os.getenv('jwt')
pinata = Pinata(api_key=api_key, secret_key=api_secret, access_token=jwt)
file_names = ['./pokeimages/bulba.png','./pokeimages/charmander.png','./pokeimages/squirtle.png']
for name in file_names:
response = pinata.pin_file(name)
image_ipfs_hash = response['data']['IpfsHash']
viewable_hash = f'https://gateway.pinata.cloud/ipfs/' + image_ipfs_hash
print(image_ipfs_hash, viewable_hash)
Registering Pokemon for Minting to the Contract
from algosdk.v2client.algod import AlgodClient
from algokit_utils import ApplicationClient
from algosdk.atomic_transaction_composer import AtomicTransactionComposer, AccountTransactionSigner, TransactionWithSigner
from algosdk.transaction import PaymentTxn
from algosdk.account import address_from_private_key
from pathlib import Path
from algosdk.abi import ABIType
from dotenv import load_dotenv
import os
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)
app_spec = Path(__file__).parent / './arc69NFTmodifier.arc32.json'
app_id = int(os.getenv('app_id'))
signer = AccountTransactionSigner(private_key=private_key)
address = address_from_private_key(private_key=private_key)
params = algod_client.suggested_params()
application_client = ApplicationClient(
algod_client=algod_client,
app_spec=app_spec,
app_id=app_id,
signer=signer,
sender=address,
suggested_params=params
)
pokemon_name_1 = 'Bulbasaur'
pokemon_name_2 = 'Charmander'
pokemon_name_3 = 'Squirtle'
pokemon_type_1 = 'Grass'
pokemon_type_2 = 'Fire'
pokemon_type_3 = 'Water'
pokemon_description_1 = 'A grass pokemon'
pokemon_description_2 = 'A fire pokemon'
pokemon_description_3 = 'A water pokemon'
pokemon_ipfs_hash_1 = 'Qmc75zNUFoFFX3uPcjnUMqhvuzezHF9tTpgbf5pph2XegF'
pokemon_ipfs_hash_2 = 'QmP2M7nPDjhU9hPr8ManjzyFzBVEJDT3LKAAKC2o5f639q'
pokemon_ipfs_hash_3 = 'QmRbPCwbQ7vps64kPUHygCkSmW36FkCLbET9X7cEjtPHJV'
atc = AtomicTransactionComposer()
pokemon_info_coder = ABIType.from_string('(string,string,string,string,uint64)')
total_bytes_1 = len(pokemon_info_coder.encode((pokemon_name_1, pokemon_type_1, pokemon_description_1, pokemon_ipfs_hash_1, 0)))
total_bytes_2 = len(pokemon_info_coder.encode((pokemon_name_2, pokemon_type_2, pokemon_description_2, pokemon_ipfs_hash_2, 0)))
total_bytes_3 = len(pokemon_info_coder.encode((pokemon_name_3, pokemon_type_3, pokemon_description_3, pokemon_ipfs_hash_3, 0)))
box_cost_1 = 2500 + (400 * total_bytes_1)
box_cost_2 = 2500 + (400 * total_bytes_2)
box_cost_3 = 2500 + (400 * total_bytes_3)
mbr_fee_payment_tx_1 = PaymentTxn(
sender=address,
sp=params,
receiver=application_client.app_address,
amt=box_cost_1,
note='#1'
)
wrapped_payment_tx_1 = TransactionWithSigner(mbr_fee_payment_tx_1, signer)
mbr_fee_payment_tx_2 = PaymentTxn(
sender=address,
sp=params,
receiver=application_client.app_address,
amt=box_cost_2,
note='#2'
)
wrapped_payment_tx_2 = TransactionWithSigner(mbr_fee_payment_tx_2, signer)
mbr_fee_payment_tx_3 = PaymentTxn(
sender=address,
sp=params,
receiver=application_client.app_address,
amt=box_cost_3,
note='#3'
)
wrapped_payment_tx_3 = TransactionWithSigner(mbr_fee_payment_tx_3, signer)
box_ref_1 = (1).to_bytes(8, 'big')
box_ref_2 = (2).to_bytes(8, 'big')
box_ref_3 = (3).to_bytes(8, 'big')
application_client.compose_call(
atc,
call_abi_method='registerNewPokemonData',
pokemon_name=pokemon_name_1,
pokemon_type=pokemon_type_1,
pokemon_description=pokemon_description_1,
pokemon_ipfs_hash=pokemon_ipfs_hash_1,
payment_txn=wrapped_payment_tx_1,
transaction_parameters={
'boxes':[[app_id, box_ref_1]]
}
)
application_client.compose_call(
atc,
call_abi_method='registerNewPokemonData',
pokemon_name=pokemon_name_2,
pokemon_type=pokemon_type_2,
pokemon_description=pokemon_description_2,
pokemon_ipfs_hash=pokemon_ipfs_hash_2,
payment_txn=wrapped_payment_tx_2,
transaction_parameters={
'boxes':[[app_id, box_ref_2]]
}
)
application_client.compose_call(
atc,
call_abi_method='registerNewPokemonData',
pokemon_name=pokemon_name_3,
pokemon_type=pokemon_type_3,
pokemon_description=pokemon_description_3,
pokemon_ipfs_hash=pokemon_ipfs_hash_3,
payment_txn=wrapped_payment_tx_3,
transaction_parameters={
'boxes':[[app_id, box_ref_3]]
}
)
results = atc.execute(algod_client, 2)
tx_ids = [results.tx_ids[i] for i in range(len(results.tx_ids))]
abi_results = [results.abi_results[i].return_value for i in range(len(results.abi_results))]
print(tx_ids)
print(abi_results)
Determining the User's Pokemon
from algosdk.v2client.algod import AlgodClient
from algokit_utils import ApplicationClient
from algosdk.atomic_transaction_composer import AtomicTransactionComposer, AccountTransactionSigner, TransactionWithSigner
from algosdk.transaction import PaymentTxn
from algosdk.account import address_from_private_key
from pathlib import Path
from algosdk.abi import ABIType
from algosdk.encoding import decode_address
from dotenv import load_dotenv
import os
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)
app_spec = Path(__file__).parent / './arc69NFTmodifier.arc32.json'
app_id = int(os.getenv('app_id'))
signer = AccountTransactionSigner(private_key=private_key)
address = address_from_private_key(private_key=private_key)
params = algod_client.suggested_params()
application_client = ApplicationClient(
algod_client=algod_client,
app_spec=app_spec,
app_id=app_id,
signer=signer,
sender=address,
suggested_params=params
)
atc = AtomicTransactionComposer()
mbr_fee_payment_tx_1 = PaymentTxn(
sender=address,
sp=params,
receiver=application_client.app_address,
amt=18_500,
note='#1'
)
wrapped_payment_tx_1 = TransactionWithSigner(mbr_fee_payment_tx_1, signer)
box_ref_1 = decode_address(address)
application_client.compose_call(
atc,
call_abi_method='determineUserPokemon',
payment_txn=wrapped_payment_tx_1,
transaction_parameters={
'boxes':[[app_id, box_ref_1]]
}
)
results = atc.execute(algod_client, 2)
tx_ids = [results.tx_ids[i] for i in range(len(results.tx_ids))]
abi_results = [results.abi_results[i].return_value for i in range(len(results.abi_results))]
print(tx_ids)
print(abi_results)
Confirming Existence of Pokemon Info in Contract
from algosdk.v2client.algod import AlgodClient
import os
from base64 import b64decode
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)
app_id = int(os.getenv('app_id'))
app_boxes = algod_client.application_boxes(app_id)['boxes']
pokemon_info_coder = ABIType.from_string('(string,string,string,string)')
for box in app_boxes:
box_name = b64decode(box['name'])
if len(box_name) == 8: #This is a pokemon info box
box_value = algod_client.application_box_by_name(app_id, box_name)['value']
decoded_box_value = pokemon_info_coder.decode(b64decode(box_value))
print(decoded_box_value)
Confirming Pokemon Selected for the User
from algosdk.v2client.algod import AlgodClient
import os
from base64 import b64decode
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)
app_id = int(os.getenv('app_id'))
app_boxes = algod_client.application_boxes(app_id)['boxes']
pokemon_info_coder = ABIType.from_string('(uint64)')
for box in app_boxes:
box_name = b64decode(box['name'])
if len(box_name) == 32: #This is the users selected pokemon ready to claim
#contains only the pokemon #, eg; pokemon #1, pokemon #2, or pokemon #3
box_value = algod_client.application_box_by_name(app_id, box_name)['value']
decoded_box_value = pokemon_info_coder.decode(b64decode(box_value))
print(decoded_box_value)