This post is an improved version of the previous article found here. To expand on the game, We will create an asset (Algorand Standard Asset - ASA) for this purpose. Our asset will have the following attributes:
- Asset Name: WINGOLDEN
- Unit Name: WIN
- Total Supply: 50000
- Decimal: 0
- Creator/Address: 4G7DRGB5NVVH5TCB2P7552XN7V5U3UOVHD67C6ZQGRZ4NFJDWOB47BTSQ4
- Manager Address: Separate address
- Freeze Address: Separate address
- Clawback Address: Separate address
- Reserve Address: Separate address
- Freeze State: False - Asset will never be frozen.
- Site Link: asset_info_link.com
Requirements/instructions/steps to get the game up and run smoothly
- Import required modules such as algosdk
- Establish connection (Mainnet/Testnet/Betanet).
- Generate accounts
- Ancillary functions:
- convert To Mnemonic
- restore Account
- wait_for_confirmation => Precedes and ancillary to asset creation, asset transfer and game class.
- print_created_asset => Precedes and ancillary to asset creation.
- print_asset_holding => Precedes and ancillary to asset creation, asset transfer and game class.
- getAssetIdv2 => You only want to declare this function and call it after asset creation is successful.
- Create Asset
- Opt in to accept asset
- Forward Transaction => Precedes and ancillary to transferring asset(Simply, it broadcasts signed transaction to the network
- Transfer Asset
- All of the above is ancillary to the GuessGame class.
To quickly create an ASA, with few changes, we will employ this boilerplate to creating an asset for our game. Firstly, let us declare few global variables and set up connection end-point, generate accounts and enable player to restore account from mnemonic words.
-->Algame.py<--
#File, where all our codes resides.
#Note: This solution uses v2 of the Algorandsdk as opposed to v1 used previously
from algosdk.v2client import algod
from algosdk import account, transaction
import time, sys, random
import logging, json
# Setup HTTP client w/guest key provided by PureStake
class Connect():
def __init__(self):
# declaring the third party API
self.algod_address = "https://testnet-algorand.api.purestake.io/ps2"
self.algod_token = os.environ.get('PERSONAL_API_TOKEN_PURESTAKE') # Sign up
# PURESTAKE.COM to get your personal token
self.headers = {"X-API-Key": self.algod_token}
def connectToNetwork(self):
# establish connection
return algod.AlgodClient(self.algod_token, self.algod_address, self.headers)
#Logs output to guessgame.log file
logging.basicConfig(filename='{}.log'.format("guessgame"), level=logging.INFO)
c = Connect()
algo_client = c.connectToNetwork()
#Determining winning algorithm
randomSeal = random.randint(49799, 50001)
winRounds = 0
playRound = 0
ACTIVE = True
max_reward = int(200)
# Get suggested parameters for transactions before every transaction.
params = algo_client.suggested_params()
# Get network params for transactions before every transaction.
params = algo_client.suggested_params()
Let s generate 3 accounts:
. One to serve the host
. One as game account (where player sends fee) and
. Another as player (for testing)
Initialize each account with ALGO. Visit https://bank.testnet.algorand.network/ to get Testnet Coin. Eligility to send transaction
requires that an account has aminimum transaction fee for as specified by network usuaily 0.001 Algo.
host_sk, host_pk = account.generate_account()
game_sk, game_pk = account.generate_account()
player_sk, player_pk = account.generate_account()
print("Host sk: {}".format(host_sk)
print("Host pk: {}".format(host_sk)
print("Game sk: {}".format(game_sk)
print("Game pk: {}".format(game_pk)
print("Player sk: {}".format(player_sk)
print("Player pk: {}".format(player_pk)
At this point, three accounts are ready. We will pause the execution for 1 minute. look up the addresses from terminal and fund them Quickly visit the dispenser- https://bank.testnet.algorand.network/
to funding accounts. By the time, execution resumes, python should never realize the magic that we just did otherwise, python will find faults.
# Hey python, this is a command go take some rest, wake up after 2 mins. I need some pizzas.
# Fund the accounts before Python wakes up.
time.sleep(120)
# create a list of addresses mapped to keys
# to easily loop through them excluding host's
accounts = {
game_sk: game_pk,
player_sk: player_pk
}
# generate seedphrase from secret key
def convertToMnemonic():
host_mnemonic = mnemonic.from_private_key(host_sk)
game_mnemonic = mnemonic.from_private_key(game_sk)
player_mnemonic = mnemonic.from_private_key(player_sk)
return {
"host_mnemonic": "\{}\".format(host_mnemonic),
"game_mnemonic": "\{}\".format(game_mnemonic),
"player_mnemonic": "\{}\".format(player_mnemonic)
}
We also want to allow an user recover an account in the event of lost devices same time able to provide the 25 mnemonic words
# Utility for restoring account from mnemonics
def restoreAccount(_seedPhrase):
p_addr = False
lostAccount = mnemonic.to_public_key(_seedPhrase) is None
s_key = mnemonic.to_private_key(_seedPhrase)
for key in accounts.keys():
if s_key == key:
p_addr = True
lostAccount = not None
return lostAccount, "\n", s_key
restore = restoreAccount(mnemonic.from_private_key(player_sk))
print(restore)
# In practice, you don not want to reveal secret key, but we only reference it here so we can
# access and retrieve it.
# Log accounts to file
logging.info("...@dev/created Asset WIN... \nHost Address: {}\nHost sk: {}\nGame Address : {}\nGame sk: {}\nPlayer Address: {}\nPlayer sk: {}\n".format(
host_pk,
host_sk,
game_pk,
game_sk,
player_pk,
player_sk
))
-
Create a .py file waitForConfirmation.py
#waitForConfirmation.py
# Utiltity for Waiting transaction to be confirmed
# Usually, transaction are confirmed in 2secs to 5 secs window
def wait_for_confirmation(txid):
"""Utility function to wait until the transaction is
confirmed before proceeding."""
last_round = algo_client.status().get('last-round')
txinfo = algo_client.pending_transaction_info(txid)
while not (txinfo.get('confirmed-round') and txinfo.get('confirmed-round') > 0):
wait = "Waiting for confirmation..."
last_round += 1
status = algo_client.status_after_block(last_round)
txinfo = algo_client.pending_transaction_info(txid)
logging.info("..@dev wait for confirmation.. \nStatus: {}\nTransaction {} confirmed in round {}\nTxn Info: {}\nResponse: {}".format(
status,
txid,
txinfo.get('confirmed-round'),
txinfo,
wait
))
return txinfo
-
Get created asset details.
# printCreatedAsset()
# Utility function used to print created asset for account and assetid
def print_created_asset(account, assetid):
# note: if you have an indexer instance available it is easier to just use this
# response = myindexer.accounts(asset_id = assetid)
# then use 'account_info['created-assets'][0] to get info on the created asset
account_info = algo_client.account_info(account)
idx = 0
for my_account_info in account_info['created-assets']:
scrutinized_asset = account_info['created-assets'][idx]
idx = idx + 1
if (scrutinized_asset['index'] == assetid):
asset_id = scrutinized_asset['index']
data_json = json.dumps(my_account_info['params'], indent=4)
logging.info("...##Asset holding... \nAddress: {}.\n Asset ID: {}\nData in Json: {}\nOperation: {}\n".format(
account,
asset_id,
data_json,
print_created_asset.__name__
))
return data_json
else:
ACTIVE = False
return("Asset does not exist")
-
Get holdings of accounts and assets
#getAssetHoldings.py
# Utility function used to print asset holding for account and assetid
def print_asset_holding(account, assetid):
# note: if you have an indexer instance available it is easier to just use this
# response = myindexer.accounts(asset_id = assetid)
# then loop thru the accounts returned and match the account you are looking for
account_info = algo_client.account_info(account)
idx = 0
for my_account_info in account_info['assets']:
scrutinized_asset = account_info['assets'][idx]
idx = idx + 1
if (scrutinized_asset['asset-id'] == assetid):
asset_id = scrutinized_asset['asset-id']
data_json = json.dumps(scrutinized_asset, indent=4)
logging.info("...##Asset holding... \nAddress: {}.\n Asset ID: {}\nData in Json: {}\nOperation: {}\n".format(
account,
asset_id,
data_json,
print_asset_holding.__name__
))
return data_json
else:
ACTIVE = False
return("You do not own WIN asset balance")
-
Extracting asset's ID
#-->getAssetId.py<--
# Utility for grabbing the assetID without the using Indexer (Note that this works
# only for v1). If you're using v2client, this obviously won't work.
def getAssetIdv1(creatorAddr):
keyList = []
list1 = []
# Get account info of asset creator
account_info = algo_client.account_info(creatorAddr)
# Loop through and target the value
for key, value in account_info.items():
list1.append(value)
print(list1)
# Target the key, strip off into a list
# First element in the list should be what we need
for key, value in list1[7].items():
keyList.append(key)
print(keyList)
return keyList[0]
# Utility for grabbing the assetID in v2client without using the Indexer
# If you run a node/goal, it should be easier getting the ID
# I am using a third party service i.e Purestake
# upgrade to v2client to use this function. See documentation
# https://developer.algorand.org/docs/reference/sdks/migration/
# Function getAssetIdv2() pulls asset's ID if called after asset is created. Calling at this point throws error.
# So it is only declared at this point. Intepreter reads and remebers it but not executing it.
# It is needed as a global function to inialized a global variable so as to be able to use it elsewhere in the program
# Be aware that behavior may be different if you use goal. This code is written using VSCode
# To execute, initialize and call it. For instance:
# asset_id = getAssetIdv2(<supply creator's addr as arguement>)
# print(asset_id)
# Get assetId using algosdk.v2client
def getAssetIdv2(creatorAddr):
# Get account info of asset creator
account_info = algo_client.account_info(creatorAddr)
_Id = account_info["assets"][0]["asset-id"]
return _Id
-
Creating Asset
#-->createAsset.py<--
# create asset
def createAsset(
creator,
sk,
asset_total,
toFreeze,
unitName,
assetName,
mngr_addr,
rsv_addr,
frz_addr,
clwbck_addr,
asst_link,
asset_decimal
):
# Account "host" creates an asset called "WIN" and
# sets Host as the manager, reserve, freeze, and clawback address.
# Asset Creation transaction
params = algo_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True
# Host Account creates an asset called WIN and
# Game Account as the manager, reserve, freeze, and clawback address.
# Asset Creation transaction
txn = AssetConfigTxn(
sender=creator,
sp=params,
total=asset_total,
default_frozen=toFreeze,
unit_name=unitName,
asset_name=assetName,
manager=mngr_addr,
reserve=rsv_addr,
freeze=frz_addr,
clawback=clwbck_addr,
url=asst_link",
decimals=asset_decimal)
# Sign with secret key of creator
stxn = txn.sign(sk)
# Send the transaction to the network and retrieve the txid.
txid = algo_client.send_transaction(stxn, headers={'content-type': 'application/x-binary'})
# Retrieve the asset ID of the newly created asset by first
# ensuring that the creation transaction was confirmed,
# then grabbing the asset id from the transaction.
# Wait for the transaction to be confirmed
wait_for_confirmation(txid)
time.sleep(3)
try:
# Pull account info of the creator
# get asset_id from tx
# Get the new asset's information from the creator account
# Using this method makes asset_id available only inside this try block
# Meanwhile I needd to use it elsewhere so an external function would be ideal
ptx = algo_client.pending_transaction_info(txid) #I tried this but didn't work for me hence I created alternative
asset_id = ptx["asset-index"] # function getAssetIdv2() to get asset_id
# asset_id = getAssetIdv2(host_pk) # Ignore this line if method above does work for you to get asset_id
createdAsset = print_created_asset(host_pk, asset_id)
assetHolding = print_asset_holding(host_pk, asset_id)
logging.info("...@dev/created Asset WIN... \nHost Address: {}\nPlayer Address: {}\nOperation 1 : {}\nOperation 2: {}\nOperation 3: {}\nAsset ID: {}\nCreated Asset: {} \nAsset Holding: {}\n".format(
host_pk,
player_pk,
createAsset.__name__,
print_created_asset.__name__,
print_asset_holding.__name__,
asset_id,
createdAsset,
assetHolding
))
except Exception as e:
print(e)
#comment out if you need make adjustment else an asset is created each time it runs
createAsset(host_pk, host_sk, 50000, False, "WIN", "Smarthead", game_pk, game_pk, game_pk, game_pk, "asset_info_link.com", 0)
Running this file creates an asset for us. Note that I am not receiving output directly from the terminal rather, I use python logging module which creates and track outputs for us. You may as well use it for debugging .
Now, we have an asset ready. To confirm that asset is existing, you may do so by scrutinizing account information of creator from the explorer or view using Algo wallet interface. Asset with ID 10314763.

Before we proceed, remember that player has opted in for our asset but with 0 balance in WIN . For the purpose of this tutorial, we will try to be fair by supporting player with 100 WIN so they can have their first guess. Initially, player opts in with 50 WIN, and subsequently pays 20 WIN.
Let us now configure the Game to using asset with ID => 10314763 for rewarding winners who find the lucky number. On Algorand, worthy of note it is that you can set up rules custom to the nature of your business or purpose of creating such asset. Player (s) who win in our game must opt in (i.e accept) before WIN can be sent to them. It requires sending a minimum transaction fee from the receiver to the network and 0 WIN to self in tandem with the asset ID. Let us write functions for this purpose.
-
Opt in for WIN
#optin.py
# RECEIVER TO OPT-IN
#utility for opting in for an aseet.
def optIn(pk, sk):
# RECEIVER TO OPT-IN FOR ASSET
# Check if asset_id is in player's asset holdings prior to opt-in
assetId = getAssetIdv2(host_pk)
account_info_pk = algo_client.account_info(pk)
holding = None
idx = 0
for my_account_info in account_info_pk['assets']:
scrutinized_asset = account_info_pk['assets'][idx]
idx = idx + 1
if (scrutinized_asset['asset-id'] == assetId):
holding = True
break
if not holding:
# Use the AssetTransferTxn class to transfer assets and opt-in
txn = AssetTransferTxn(
sender=pk,
sp=params,
receiver=pk,
amt=0,
index=assetId)
stxn = txn.sign(sk)
txid = algo_client.send_transaction(stxn, headers={'content-type': 'application/x-binary'})
msg = "Transaction was signed with: {}.".format(txid)
wait = wait_for_confirmation(txid)
time.sleep(5)
hasOptedIn = bool(wait is not None)
# Now check the asset holding for that account.
# This should now show a holding with balance of win.
assetHolding = print_asset_holding(player_pk, assetId)
logging.info("...##Asset Transfer... \nOpt in address: {}.\nMessage: {}\nHas Opted in: {}\nOperation: {}\n".format(
pk,
msg,
hasOptedIn,
optIn.__name__
))
return hasOptedIn
Before rewarding a winner in our game, we should perform some checks:
- Check if player already owns a portion of WIN else, optIn()
- If player has opted in, balance must have at least 50 WIN to be eligible for a round.
- Player's address must be valid.
- Must have minimum transaction fee in Algo for at least a round.
- We also want to allowed player a discount if playRound is greater than 1.
#-->transferAsset.py--<
# Broadcast signed transaction to the network
# Log outputs to guessgame.log file
def forwardTransaction(signedTrxn, sk, assetBal, sender, receiver, amt, algobalance):
# logging.basicConfig(filename='{}.log'.format("guessgame"), level=logging.INFO)
try:
assetId = getAssetIdv2(player_sk)
txid = "Transaction was signed with {}.".format(algo_client.send_transaction(signedTrxn, headers={'content-type': 'application/x-binary'}))
wait_for_confirmation(txid)
# The balance should now be updated.
holding = print_asset_holding(player_pk, assetId)
logging.info("...##Forward Transaction... \nPlayer's Alc info: {}.\nSender: {}\nReceiver : {}\nAmount: {} WIN\n Operation: {}\nAlgo Balance: {}\nTxn ID: {}\nHolding: {}\nAsset balance: {}\n".format(
algo_client.account_info(player_sk),
sender,
receiver,
amt,
forwardTransaction.__name__,
algobalance,
txid,
holding,
assetBal
))
except Exception as e:
print(e)
# transfer asset between accounts
def pay(senderAddr, receiverAddr, sk):
global ACTIVE
min_pay_1 = int(50)
sub_play = int(30)
assetBalnce = algo_client.account_info(senderAddr)["assets"][0]["amount"]
algoBalance = algo_client.account_info(senderAddr)['amount-without-pending-rewards']
assetId = getAssetIdv2(host_pk)
txn = AssetTransferTxn(
sender=senderAddr,
sp=params,
receiver=receiverAddr,
amt=50,
index=assetId)
#If sender not host and has not played in this game for once
if((senderAddr == host_pk) and not((sendRound > 0) and (playRound > 0))):
txn.amount = 50
signedTrxn = txn.sign(sk)
forwardTransaction(signedTrxn, host_sk, assetBalnce, senderAddr, receiverAddr, txn.amount, algoBalance)
sendRound = sendRound+1
if((senderAddr == host_pk) and (sendRound >= 1) and (playRound >= 1)):
txn.amount = 200
signedTrxn = txn.sign(sk)
forwardTransaction(signedTrxn, host_sk, assetBalnce, senderAddr, receiverAddr, txn.amount, algoBalance)
sendRound = sendRound+1
elif not((senderAddr == host_pk) and(playRound > 0) and winRounds > 0)):
# check that player's balances in WIN/ALGO are enough account
#check if address is valid and player is new
if ((len(senderAddr) == 58) and ((algoBalance > 1000) and (assetBalnce >= min_pay_1))):
if(txn.amount >= min_pay_1):
ACTIVE = ACTIVE
signedTrxn = txn.sign(sk)
# validate token transfer
txn.amount = min_pay_1
signedTrxn = txn.sign(sk)
forwardTransaction(signedTrxn, sk, assetBalnce, senderAddr, receiverAddr, txn.amounty, algoBalance)
# Check that player has made more than one round
# Give fee discount if true
elif(playRound > int(0) and winRounds > 0):
assert((algoBalance > 1000) and (assetBalnce > min_pay_1))
ACTIVE = True
txn.amount = sub_play
signedTrxn = txn.sign(sk)
forwardTransaction(signedTrxn, sk, assetBalnce, senderAddr, receiverAddr, txn.amount, algoBalance)
else:
ACTIVE = False
return
The Guess Game
#-->guessgame.py<--
# Modelling Guessgame Class
class GuessGame():
"""Prototyping"""
def __init__(self):
self.alcInfo = algo_client.account_info(host_pk)
self.round = self.alcInfo["round"]
self.__asset_id = getAssetIdv2(host_pk)
self.threshold = int(self.__asset_id/2)
self.roundminus = int((self.round + randomSeal)-self.threshold)
self.suggestedNumbers = range(self.roundminus - (int(self.threshold) - int(4500000)), self.round+randomSeal, randomSeal)
self.isactive = True
self.players = []
# suggested a series of numbers where winning number is among
def suggestedNNumbers(self):
sug_nums = set()
print("Find the winning number in one round?:\n")
for num in self.suggestedNumbers:
sug_nums.add(num)
# pop a random number from set, replaced with winning number
win_position = random.randint(int(0), len(sug_nums))
result = []
for i, j in enumerate(list(sug_nums)):
if i == win_position:
j = self.roundminus
result.append(j)
new_set = set(result)
print("There are {} suggested numbers in total".format(len(sug_nums)))
print(new_set)
# function for guessgame
def guessByRound(self, playeraddr, guess, sk):
# logging.basicConfig(filename='{}.log'.format("guessgame"), level=logging.INFO)
global winRounds
global playRound
# opt in player except for host, game address is the manager
# With this, player will be able to receive asset we send to them
for p_k, s_k in accounts.items():
optIn(p_k, s_k)
if not (playeraddr in self.players):
pay(host_pk, playeraddr, host_sk, 100)
self.players.append(playeraddr)
else:
pass
print(algo_client.account_info(playeraddr))
print(algo_client.account_info(host_pk))
# Display suggested nunmbers
i = GuessGame()
i.suggestedNNumbers()
luckyNum = self.roundminus
while isactive:
# send minimum play fee for this round to Guessgame Address
send = pay(playeraddr, game_pk, sk)
if guess != luckyNum:
winRounds = winRounds
print("Oop! This roll does not match.")
self.isactive = False
print("Last guess was: " + str(guess))
# break
# Player finds winning number, rounds equaL plus 1
# Host sends 200 WIN Asset to player
elif guess == luckyNum:
self.isactive = True
playRound = playRound+1
winRounds = winRounds+1
msg = "Congratulations! You won!" + "\n" + "200 WINGOLDEN was sent to you."
# Sent from Host account if player wins
send = pay(host_pk, playeraddr, host_sk)
msg_2 = "You are in the {} round and {} playround.".format(winRounds, playRound)
# Log output to file
logging.info("\n...##Guessgame... \nPlayer Address: {}\nYou guessed: {}\nMessage: {}\nOperation: {}\nAlgo Balance: {}\nAsset balance: {}\nReminder: {}".format(
player_pk,
guess,
msg,
GuessGame.__name__,
algo_client.account_info(playeraddr)['amount-without-pending-rewards'],
algo_client.account_info(playeraddr)["assets"][0]["amount"],
msg_2
))
# Player wishes to replay?
print("Do you want to make more guess? '\n'")
replay = input("Press 'y' to continue, 'n' to quit: ")
replay = replay.lower()
if(replay == "n"):
self.active = False
print("That was really tough right?", "\n", "Algorand Blockchain is awesome!.")
print("Your total balance is {}".format(algo_client.account_info(playeraddr)['assets'][0]["amount"]))
sys.exit(int(1))
elif(replay == "y"):
self.active == True
continue
# Now, run the game
guess = GuessGame()
guess.guessByRound(player_pk, <your guessedNumberHere>, player_sk)
If set conditions evaluate to TRUE , player can now participate. Should player guess correctly, host sends 200 WIN to player. The loop continues as long as player wishes to continue else program terminates.
guessgame.log/Output


You should have the dialogue prompted in the terminal. Full code is available on github. Suggestions and feedback are appreciated. Thank You.
Resources
...