Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial Thumbnail
Beginner · 30 minutes

Deploying a bet dApp powered by Beaker

This tutorial will guide you through the deployment of an AlgoBet instance, a bet dApp developed on top of Beaker framework.
AlgoBet repository: github.
Authors: Nicola Elia, Alessandro Parenti, Domenico Tortola.

Requirements

  • General knowledge on Algorand Smart Contracts
  • Python 3.10+ (installation guide provided within the tutorial)
  • Beaker package (installation guide provided within the tutorial)
  • Algorand sandbox (installation guide provided within the tutorial)

Background

Tutorial breakthrough

The first step will be the setup of a suitable environment, which comprises a working installation of Beaker and the Algorand sandbox connected to the testnet.
It will be necessary to create a wallet, populate it with at least two accounts and fund those accounts using any of the available testnet faucets.
The next step will be to clone the repository containing the AlgoBet smart contract, compile it into TEAL code and deploy it on the testnet.
Finally, we will interact with the deployed dApp to place a bet, set the result and claim the reward.

All the scripts created through this tutorial are provided into AlgoBet (github repository), inside docs/tutorial/scripts directory.

Target smart contract

AlgoBet is a basic decentralized betting system based on Algorand smart contract, in which users can bet on the result of a particular event. The betting system is inspired by the classical sport bets, such as football or basketball. In these sports, the typical bet can cover three results for the game: Home team wins, Away team wins, Draw.
Each instance of AlgoBet will handle a particular event, therefore it will be necessary to deploy multiple AlgoBet instances to manage multiple events.

The bet stake is fixed in 140 milliAlgos. Users are not allowed to bet more than this fixed stake, neither to place more than one bet each. Events, and therefore related bets, are handled using a time management system to set the event start and end as two timestamps, and to define a payout time window. This mechanism also prevents the manager to delete the contract until the winning bets are paid.

Three actors are involved in the AlgoBet workflow:

  • Manager: the manager handles the smart contract instance lifecycle phases, such as creation and deletion, plus a manager can set the oracle account;
  • Oracle: the oracle account can set the event result once it terminates. Ideally, it is the link between the smart contract and the real world, and practically it is an Algorand account chosen by the manager at creation time;
  • Users: the users interact with the smart contract placing bets on the event and claiming their earnings if they placed a bet on the correct result. They need to opt-in to che contract in order to use these features.

AlgoBet was developed using Beaker, a novel development framework that makes easier to write PyTeal code, then it was tested on the Algorand sandbox.

Oracles

In order to work properly, a betting service needs to retrieve the necessary information about the relevant event. In blockchain enviroments, data-feeds from the real world are operated through Oracles, i.e., entities (human, software, sensor etc.) included in a smart contract to bridge on- and off-chain ecosystems.
AlgoBet needs to interact with an oracle for retrieving the event result. In this tutorial, you will create an Agorand account that will serve as oracle account.

Beaker

Beaker is a new programming framework for building smart contracts on Algorand. The traditional PyTeal framework has proven to be troublesome for may in that it may result unfamiliar, complex and with a counter-intuitive structure.
Beaker’s goal is to improve development experience both for current algorand developers and for newcomers.
Because of its recent release, few attempts were made in employing Beaker for DApps implementations. Such an activity is needed in order to test its potentialites and to identify what is to be improved.

Steps

1. Environment setup

In this section you will find the instructions to setup a suitable environment to deploy and interact with an AlgoBet smart contract instance by yourself. This setup was tested in a Ubuntu environment.

At first, please create a new folder to put all the stuff needed for going through this tutorial, naming it as you prefer. From now on, we will refer to that folder as root directory.

You will need to install some dependencies in order to execute the smart contract:

  • Python: we recommend to install Python 3.10+, due to Beaker framework requirements. Most of the time, a Python installation is already available in your operating system. To check your current Python version, open a terminal and issue python3 --version. If the Python version is less than 3.10, please refer to Python documentation to install a newer version.

We recommend to instantiate a virtual environment (venv) before proceeding with this tutorial. To do it, open a terminal,move to root directory and issue:

# On Ubuntu or MacOS
python3 -m venv venv
source ./venv/bin/activate

# On Windows
python3 -m venv venv
.\venv\Scripts\activate

This will create a virtual env for you. The virtual environment will be successfully activated if your command prompt starts with the (venv) word.

  • Beaker: it is a Python framework for building Smart Contracts on Algorand using PyTeal. The AlgoBet smart contract has been developed using this framework.

    This tutorial has been developed on top of Beaker version 0.3.6. It can be installed using pip:

    pip install beaker-pyteal==0.3.6
    
  • Docker and Docker Compose: a working Docker installation is needed in order to execute the Algorand sandbox. If you are new to Docker, we recommend referring to Docker documentation to install Docker Desktop.

  • Algorand Sandbox: this tool provides a quick and easy way to create, configure and deploy an Algorand node using Docker. The containerized node may be joined to any of the three Algorand networks, and you will be able to manage it through the sandbox environment.

    To install the Algorand sandbox, move into the root directory and then clone the sandbox repository:

    Note for Windows users: you may need to perform some more steps before being able to run the Algorand sandbox. Please follow the instructions provided here.

    git clone https://github.com/algorand/sandbox.git
    

    To spin up the sandbox and attach it to the Algorand testnet, make sure that Docker is running, and then execute:

    cd sandbox
    ./sandbox up testnet
    

At this point, your environment satisfies all the requirements, and we can start to dive into the core of this tutorial!

2. Wallet and accounts creation

To deploy and interact with AlgoBet, we will pick and use some accounts from sandbox. When using the sandbox in testnet mode, it will be necessary to manually create a new wallet, populate it with new accounts and fund them using a testnet faucet.

You can use the terminal that is running the sandbox to do that. Execute the following commands:

./sandbox enter algod
goal wallet new <walletName>

You first have to enter into the node, then you can start the wallet creation passing the wallet name.
The command execution will ask for a password for the wallet, and some security settings.
Once the process is over, you can check if the wallet was correctly instantiated executing:

goal wallet list

And look for your just created wallet.
If everything is fine, you can go further adding to the wallet some accounts. Staying in the algod node, you can execute:

goal account new -w <walletName>

We need to create 4 accounts.
You can check if the account are correctly created by typing:

goal account list

If everything is correct, you can finally exit from the algod node:

exit

And this will lead you to the standard terminal.

3. Account funding

To provide your test accounts with some virtual funds, you can use this testnet dispenser.

Each account may be funded by following these steps:

  • complete the Captcha puzzle
  • copy and paste the account address into the target address textbox
  • press the “Dispense” button

You can check your accounts balances at any time by using the goal account list command seen in the previous step.

4. Clone repository

Once our sandbox environment has been set up, we need to retrieve the AlgoBet repository.
To do so, open a terminal from the root folder and clone the algobet repository:

git clone https://github.com/n-elia/algobet.git

The algobet directory will be created and populated with the repository content. There you will find all the files necessary to set up and deploy an AlgoBet DApp: * docs/: contains the project documentation; * src/: contains the Smart Contract source code.
* src/teal/: stores the Smart Contract, compiled into TEAL code;
* src/test/: stores the Smart Contract tests;
* src/contract.py: Smart Contract source code;
* src/requirements.txt: Python requirements to be installed for managing the Smart Contract; * README.md: readme file.

Now, install the requirements needed from the .txt file in the src folder by opening up a terminal and executing:

cd algobet/src/

pip install -r requirements.txt

Your Python environment is now ready to deploy an AlgoBet DApp.

5. (optional) Compile to TEAL

One of the most user-friendly features of the Beaker framework is that it allows deploying a Smart Contract on the Algorand network without having to go through the manual generation of the TEAL bytecode. Indeed, this step is operated automatically by Beaker upon the moment of the Smart Contract create Application Call.

However, knowing the TEAL code might be important to understand how an application interacts with the Algorand Virtual Machine or to identify bugs and issues which occur during its execution.

In order to compile our Beaker contract into TEAL bytecode, we can exploit Beaker functionalities.
Create a new Python script with the content proposed below.

import json

from src.contract import AlgoBet as App

app = App()
print(app.approval_program)
print(app.clear_program)
print(json.dumps(app.contract.dictify()))

for filename, content in [
    ("approval_program.teal", app.approval_program),
    ("clear_program.teal", app.clear_program),
    ("contract.json", json.dumps(app.contract.dictify()))
]:
    with open(filename, "w") as fp:
        fp.write(content)

Through this script we are going to generate the two programs that ultimately constitute the TEAL code: the Approval Program and the Clear State Program. The latter is used to handle accounts using the clear call to remove the smart contract from their balance record while the former is responsible for processing all application calls to the contract.

Once created the script, save it as src/teal/compile.py.

Now open the terminal and, from the algobet directory, run the Python script:

python src/teal/compile.py

This will generate the approval_program.teal and clear_program.teal files, that you can inspect and that you could also use for manually deploying the AlgoBet Smart Contract.

6. Define the event

At this point, we need to define an event. Then, we will properly instantiate and AlgoBet dApp to bet on the defined event.

To instantiate the AlgoBet dApp, we will need:
- An Algorand account which will serve as manager of the dApp.
- An Algorand account which will serve as oracle of the dApp.
- The Unix timestamp which represents the event start time.
- The Unix timestamp which represents the event end time.
- The minimum allowed amount of time for winning participants to claim their rewards.

Let’s suppose that we want to build a bet system for the soccer match Catanzaro - Crotone, which starts 5 minutes after the creation of the associated AlgoBet dApp and lasts for 10 minutes. Also suppose that we want to allow the winning participants to request the payout for at least 15 minutes after the end of the match.

We will use the Algorand accounts that we created and funded in the previous steps.
Taking advantage of beaker.sandbox, we can easily pop the accounts from the sandbox within a Python script. Please notice that sandbox.get_accounts() returns an ordered list of sandbox accounts. Therefore, among the code snippets of the remaining tutorial steps, we will always pop the accounts in the same order to ensure consistency:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

The first popped account will be the manager account; the second one will be the oracle account; the remaining two accounts will be two different participant accounts. Each account will be identified by means of its address.

7. Create the AlgoBet dApp as Manager

We’re now ready to deploy our AlgoBet dApp, associated to Catanzaro - Crotone soccer match.

To deploy our dApp we will leverage Beaker. Let’s build a Python script together.

At first, we have to retrieve sandbox accounts as explained in previous steps:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

Next, we instantiate a Beaker’s ApplicationClient (docs), which will provide us a convenient way to interact with AlgoBet. The client’s operations will be signed using the Manager account.

from beaker.client import ApplicationClient

from contract import AlgoBet

# Create an Application client
app_client_manager = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Select the Manager account as transaction signer
    signer=manager_acct.signer
)

We are now ready to call the ApplicationClient.create() method, that will submit an Application Call of type Create transaction signed by the Manager account to create the AlgoBet application. The method’s arguments are chosen based on the event.

import time
from beaker import consts

# Unix timestamp of the event start, computed as 5minutes since code execution
event_start_unix_timestamp = int(time.time() + 5 * 60)
# Unix timestamp of the event end, computed as 10 minutes since event start
event_end_unix_timestamp = event_start_unix_timestamp + 10 * 60
# Minimum time that participants are allowed for requesting the payout, set to 15 minutes
payout_time_window_s = int(15 * 60)

# Create the application on chain using the Application Client signed by Manager account
app_id, app_addr, tx_id = app_client_manager.create(
    # Address of the Manager account
    manager_addr=manager_acct.address,
    # Address of the oracle  account
    oracle_addr=oracle_acct.address,
    # Unix timestamp of the event start
    event_start_unix_timestamp=event_start_unix_timestamp,
    # Unix timestamp of the event end
    event_end_unix_timestamp=event_end_unix_timestamp,
    # Minimum time that participants are allowed for requesting the payout
    payout_time_window_s=payout_time_window_s
)

# Print the Application ID, the Application account address and the ID of creation transaction
print(f"Created AlgoBet dApp with id: {app_id} and address: {app_addr} in tx: {tx_id}")

# Fund the app account with 1 algo
app_client_manager.fund(1 * consts.algo)

# Print the current application state
print(f"Current application state: {app_client_manager.get_application_state()}")

Save the Python script as create_algobet_dapp.py, then open a terminal and execute the script by issuing:

$> python create_algobet_dapp.py

The script should have an output similar to:

Found 5 accounts into the wallet

Manager account: SKBBUTCFKLPOGWMHLMA2ME3OAR4D2KIXLBXX366R2N7EOBS56QNYWN3SKE
Oracle account: P7LUQ4IIU43LGBHSEMDQ2K7OK75ZMLJC2ACEVX2BUAGCCYOGQIQ3LTJGWM
Participant 1 account: FLD3ATI6H3MTVBVE6X3OF7DJF7PC42Z4XTCVXQ3DMDK6O3NRRCQMGUFDKU
Participant 2 account: DB6U6AAH56T2SIMQVUMI4O3SFKTY2QIU2GAPI3N2PRXTNATWFTYGXJUBKI

Created AlgoBet dApp with id: 144934470 and address: EVTLVHOMFDKGWUJN54TBZB5KIIMVKI3A65T77O6ZWUIX5UXE3YTPN754NI in tx: KO5OMLN34ZVRYBGLN5OM3BP4YX5M26CG5HVBUAIOQWR5WCDHICJQ

Process finished with exit code 0

Warning: please take note of the Application ID and the Application Account Address as they will enable us to interact with the application during the next steps.

The AlgoBet dApp related to Catanzaro - Crotone soccer match is now deployed on the testnet, and it is reachable through its Application Id.

8. Place a bet as Participant 1

We’re now ready to place our bets on Catanzaro - Crotone soccer match, by interacting with the deployed AlgoBet dApp. Let’s suppose that Giovanni, which is the Participant 1, is forecasting a win of the home team.

To interact with the deployed dApp, we will leverage Beaker. Let’s build a Python script together.

At first, we have to retrieve sandbox accounts as explained in previous steps:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

Next, we instantiate a Beaker’s ApplicationClient (docs), which will provide us a convenient way to interact with AlgoBet.
This time, the application client must be signed by the Participant 1 account, and it must interact with the already deployed AlgoBet instance.

Warning: please remember to paste the Application ID and the Application Account Address copied before.

from beaker.client import ApplicationClient

from contract import AlgoBet

APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Create an Application client signed by Participant 1
app_client_participant_1 = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Participant 1 account as transaction signer
    signer=participant_1_acct.signer
)

Before a Participant account can interact with AlgoBet dApp it must opt-in. An opt-in transaction is simply an asset transfer with an amount of 0. When using Beaker framework, the opt-in call can be automatically issued using the ApplicationClient.opt_in() method.

Once the opt-in is completed, we are ready to call the ApplicationClient.call() method, that will submit an Application Call transaction to the AlgoBet application signed by the Participant 1 account.

The transaction that we are willing to request an execution to is AlgoBet.bet. This transaction requires a contextual deposit transaction, that must transfer 140 milliAlgo to the dApp account as bet deposit. We will therefore compose an atomic transaction group using Beaker.

Note: learn more about Algorand Atomic Transfers here on the developer portal or here on the Algorand documentation.

The bet option must be chosen, too. It’s possible to choose among 0 (Home team wins), 1 (Away team wins) or 2 (Draw). In this case, since the Participant 1 wants to bet on the home team victory, the chosen option is 0.

from algosdk.atomic_transaction_composer import TransactionWithSigner
from algosdk.future import transaction

from beaker import consts

# Opt-in into the dApp
app_client_participant_1.opt_in()

# Place a bet as Participant 1 forecasting a home team win
result = app_client_participant_1.call(
    # Transaction to be requested
    AlgoBet.bet,
    # Bet deposit transaction
    bet_deposit_tx=TransactionWithSigner(
        # Transaction of type PaymentTxn
        txn=transaction.PaymentTxn(
            # Address of the account requesting the transfer
            participant_1_acct.address,
            # Transaction parameters (use suggested)
            app_client_participant_1.client.suggested_params(),
            # Receiver account address
            APP_ADDR,
            # Transfer amount
            140 * consts.milli_algo),
        # Payment transaction signer
        signer=participant_1_acct.signer
    ),
    # Option to bet on: home team win
    opt=0
)

# Output the transaction ID.
print(f"Transaction completed with ID: {result.tx_id}")

Save the Python script as algobet_participant_1_bet.py, then open a terminal and execute the script by issuing:

$> python algobet_participant_1_bet.py

The script should have an output similar to:

Transaction completed with ID: CTV34P2G5G2DO6VHOM436JTJP6RUBVNIE57MNPHRDX7QRJPFY46Q

Troubleshooting tip: if you obtain the following error, then the event start timestamp is already passed, therefore Participants are no more allowed to place bets. You can go back to the dApp creation step to restart the tutorial with a fresh AlgoBet instance.

global LatestTimestamp
bytec 13 // "event_start_timestamp"
app_global_get
<
// Event has already started
assert      <-- Error
load 8
gtxns Amount
bytec 14 // "bet_amount"
app_global_get

9. (optional) Inspect Application State

Beaker framework refers to Global State as Application State.

We can query the Application State of our deployed dApp to observe the values of global variables, such as the oracle_addr which stores the address of the Oracle account.

Let’s build a Python script together. At first, we have to retrieve sandbox accounts and create an Application client signed by Participant 1, as explained in previous steps.
Please note that any account may be used for querying the application state.

from beaker import sandbox
from beaker.client import ApplicationClient

from contract import AlgoBet

# Application ID and address
APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

# Create an Application client signed by Participant 1
app_client_participant_1 = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Participant 1 account as transaction signer
    signer=participant_1_acct.signer
)

Then, we can use the ApplicationClient.get_application_state() method to retrieve the current application state:

# Print the current application state
print(f"Current application state: {app_client_participant_1.get_application_state()}")

Save the Python script as inspect_application_state.py, then open a terminal and execute the script by issuing:

$> python inspect_application_state.py

The script should have an output similar to:

Current application state: {'counter_opt_1': 0, 'counter_opt_2': 0,
 'oracle_addr': '7a7a63bdf93c4ef87ba758a439023c423d9e44eedd3c79a03f7f01b6a26c3d5f', 'event_result': 99, 'counter_opt_0': 1, 'event_start_timestamp': 1668789100, 'manager': '9110d3abec8f898b606bd36cfdd518e0b5ac7b5723e1c42a0ee8311fbd439a66', 'event_end_timestamp': 1668789400, 'bet_amount': 140000, 'winning_count': 0, 'stake_amount': 140000, 'winning_payout': 0, 'payout_time_window_s': 900}

The output string is a JSON document. It represents the key-value variables stored into the AlgoBet dApp Global State.

10. (optional) Inspect Participant 1 Account State

Beaker framework refers to Local State as Account State.

We can query the Account State of our deployed dApp to observe the values of local variables, such as chosen_opt, which stores the bet forecast chosen by the account.

Let’s build a Python script together. At first, we have to retrieve sandbox accounts and create an Application client signed by Participant 1, as explained in previous steps.
Please note that each account may be used for querying its own local state only after having opted-in.

from beaker import sandbox
from beaker.client import ApplicationClient

from contract import AlgoBet

# Application ID and address
APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

# Create an Application client signed by Participant 1
app_client_participant_1 = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Participant 1 account as transaction signer
    signer=participant_1_acct.signer
)

Then, we can use the ApplicationClient.get_application_state() method to retrieve the current application state:

# Print the current account state
print(f"Current account state: {app_client_participant_1.get_account_state()}")

Save the Python script as inspect_account_state.py, then open a terminal and execute the script by issuing:

$> python inspect_account_state.py

The script should have an output similar to:

Current account state: {'has_placed_bet': 1, 'chosen_opt': 0, 'has_requested_payout': 0}

The output string is a JSON document. It represents the key-value variables stored into the AlgoBet dApp Local State inherent to the account of Participant 1.

11. Place a bet as Participant 2

We can place a bet as Participant 2 using the same script we already used for Participant 1. The only difference that we will introduce is that the Application Client will be signed by the Participant 2 account instead of Participant 1 one.

At first, we have to retrieve sandbox accounts:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

Next, we instantiate a Beaker’s ApplicationClient. This time, the application client must be signed by the Participant 2 account.

Warning: please remember to paste the Application ID and the Application Account Address copied before.

from beaker.client import ApplicationClient

from contract import AlgoBet

APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Create an Application client signed by Participant 2
app_client_participant_2 = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Participant 2 account as transaction signer
    signer=participant_2_acct.signer
)

We can then opt-in and place a bet. We will make Participant 2 choose a different option than Participant 1: 1 (Away team wins).

Warning: please remember that Participants may bet until the match starts. Therefore, you must complete the bet steps before the event start timestamp.

from algosdk.atomic_transaction_composer import TransactionWithSigner
from algosdk.future import transaction

from beaker import consts

# Opt-in into the dApp
app_client_participant_2.opt_in()

# Place a bet as Participant 2 forecasting an away team win
result = app_client_participant_2.call(
    # Transaction to be requested
    AlgoBet.bet,
    # Bet deposit transaction
    bet_deposit_tx=TransactionWithSigner(
        # Transaction of type PaymentTxn
        txn=transaction.PaymentTxn(
            # Address of the account requesting the transfer
            participant_2_acct.address,
            # Transaction parameters (use suggested)
            app_client_participant_2.client.suggested_params(),
            # Receiver account address
            APP_ADDR,
            # Transfer amount
            140 * consts.milli_algo),
        # Payment transaction signer
        signer=participant_2_acct.signer
    ),
    # Option to bet on: away team win
    opt=1
)

# Output the transaction ID.
print(f"Transaction completed with ID: {result.tx_id}")

Save the Python script as algobet_participant_2_bet.py, then open a terminal and execute the script by issuing:

$> python algobet_participant_2_bet.py

The script should have an output similar to:

Transaction completed with ID: CTV34P2G5G2DO6VHOM436JTJP6RUBVNIE57MNPHRDX7QRJPFY46Q

12. Set the result as Oracle

Now that bets are placed, we must wait for the event to start. We set the event start 5 minutes after the dApp creation.

Please note that Participants are not allowed to bet after the event start, and therefore bet transactions that occur after event start timestamp will fail.

Moreover, the Oracle account won’t be able of setting the event result before the event end.
We set the event end timestamp 10 minutes after the event start. Thus, after 15 minutes from its creation, our AlgoBet dApp will consider the event finished, and will allow the Oracle account to set the event result.

Let’s assume that Catanzaro finally scored twice, winning the game 2–1. Therefore, the winning option is “Home team win”, represented by option 0.

We can set the result as Oracle using another Python script. Similar as we did with Participants to make them bet, we will instantiate an Application Client signed by the Oracle account to allow it requesting transactions against the deployed dApp.

At first, we have to retrieve sandbox accounts:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

Next, we instantiate a Beaker’s ApplicationClient. This time, the application client must be signed by the Oracle account.

Warning: please remember to paste the Application ID and the Application Account Address copied before.

from beaker.client import ApplicationClient

from contract import AlgoBet

APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Create an Application client signed by Oracle account
app_client_oracle = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Oracle account as transaction signer
    signer=oracle_acct.signer
)

We don’t need to opt-in, because the Oracle account won’t use the Local State. We will make Oracle account to choose 0 (Home team wins) as winning option.

Warning: please remember that Oracles must wait the match end before being allowed to set the event result.

# Set the result "Home team win"
result = app_client_oracle.call(
    # Transaction to be requested
    AlgoBet.set_event_result,
    # Winning option: home team win
    opt=0
)

# Output the transaction ID.
print(f"Transaction completed with ID: {result.tx_id}")

Save the Python script as set_result.py, then open a terminal and execute the script by issuing:

$> python set_result.py

The script should have an output similar to:

Transaction completed with ID: CTV34P2G5G2DO6VHOM436JTJP6RUBVNIE57MNPHRDX7QRJPFY46Q

Troubleshooting tip: if you obtain the following error, then the event end timestamp has not been reached yet, therefore Oracle is not yet allowed to set the event result. You shall wait the event end time before requesting the execution of this transaction.

global LatestTimestamp
bytec_1 // "event_end_timestamp"
app_global_get
>=
// Event expiry time not reached, yet.
assert      <-- Error
load 19
intc_0 // 0
==
bnz seteventresult_10_l16

13. Request the payout as Participant 1 (winner)

At this point, the winning Participants are allowed to claim their rewards.

In this tutorial, we made Participant 1 to bet on “Home team win” (option 0), and Participant 2 to bet on “Away team win” (option 1). Thus, the former will be a winner while the latter will be a looser.

Let’s write a Python script that requests the payout as Participant 1.
At first, we have to retrieve sandbox accounts:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

Next, we instantiate a Beaker’s ApplicationClient. This time, the application client must be signed by the Participant 1 account.

Warning: please remember to paste the Application ID and the Application Account Address copied before.

from beaker.client import ApplicationClient

from contract import AlgoBet

APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Create an Application client signed by Participant 1
app_client_participant_1 = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Participant 1 account as transaction signer
    signer=participant_1_acct.signer
)

Let’s now request the payout transaction. The account balance is also printed before and after the payout, to make you see the effect of payout transaction.

# Query the account balance before payout
participant_1_acct_balance_before = sandbox_client.account_info(participant_1_acct.address)['amount']
print(f"Account balance before requesting the payout: {participant_1_acct_balance_before}")

# Request the payout
result = app_client_participant_1.call(
    # Transaction to be requested
    AlgoBet.payout
)

# Output the transaction ID
print(f"Transaction completed with ID: {result.tx_id}")

# Query the account balance after payout
participant_1_acct_balance_after = sandbox_client.account_info(participant_1_acct.address)['amount']
print(f"Account balance after requesting the payout: {participant_1_acct_balance_after}")

Save the Python script as payout_participant_1.py, then open a terminal and execute the script by issuing:

$> python payout_participant_1.py

The script should have an output similar to:

Account balance before requesting the payout: 7855000
Transaction completed with ID: RHZQQQQ4KPQUCMKFSTPKYNJZJG5J3E2W2BB33TMTK42MMEDN6S5A
Account balance after requesting the payout: 7993000

14. (optional) Request the payout as Participant 2 (looser)

Participants which did not choose the winning option will not be allowed to claim their rewards.

In this tutorial, we made Participant 2 to bet on “Away team win” (option 1), which does not correspond to the winning option.

Let’s write a Python script that requests the payout as Participant 2. The expected result is that the AlgoBet dApp refuses the execution of the payout transaction.
At first, we have to retrieve sandbox accounts:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

Next, we instantiate a Beaker’s ApplicationClient. This time, the application client must be signed by the Participant 2 account.

Warning: please remember to paste the Application ID and the Application Account Address copied before.

from beaker.client import ApplicationClient

from contract import AlgoBet

APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Create an Application client signed by Participant 2
app_client_participant_2 = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Participant 2 account as transaction signer
    signer=participant_2_acct.signer
)

Let’s now request the payout transaction.

# Query the account balance before payout
participant_2_acct_balance_before = sandbox_client.account_info(participant_2_acct.address)['amount']
print(f"Account balance before requesting the payout: {participant_2_acct_balance_before}")

# Request the payout
result = app_client_participant_2.call(
    # Transaction to be requested
    AlgoBet.payout
)

print("This line should not be reached.")

Save the Python script as payout_participant_2.py, then open a terminal and execute the script by issuing:

$> python payout_participant_2.py

We expect this Python script to throw an error, since the Participant 2 is not a winner participant.

The script should have an output similar to:

Found 5 accounts into the wallet
Manager account: SKBBUTCFKLPOGWMHLMA2ME3OAR4D2KIXLBXX366R2N7EOBS56QNYWN3SKE
Oracle account: P7LUQ4IIU43LGBHSEMDQ2K7OK75ZMLJC2ACEVX2BUAGCCYOGQIQ3LTJGWM
Participant 1 account: FLD3ATI6H3MTVBVE6X3OF7DJF7PC42Z4XTCVXQ3DMDK6O3NRRCQMGUFDKU
Participant 2 account: DB6U6AAH56T2SIMQVUMI4O3SFKTY2QIU2GAPI3N2PRXTNATWFTYGXJUBKI

Account balance before requesting the payout: 9714000

Traceback (most recent call last):
  [...]
urllib.error.HTTPError: HTTP Error 400: Bad Request

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  [...]
algosdk.error.AlgodHTTPError: TransactionPool.Remember: transaction RZMP642OUQZ63HPLUH7E6C77TUAEDGABYFIVICDGBDJFL7AQ4POQ: logic eval error: assert failed pc=834. Details: pc=834, opcodes=app_local_get
==
assert

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  [...]
beaker.client.logic_error.LogicException: Txn RZMP642OUQZ63HPLUH7E6C77TUAEDGABYFIVICDGBDJFL7AQ4POQ had error 'assert failed pc=834' at PC 834 and Source Line 416: 

    txn Sender
    bytec 10 // "chosen_opt"
    app_local_get
    ==
    // You did not choose the winning option
    assert      <-- Error
    txn Sender
    bytec 12 // "has_requested_payout"
    app_local_get
    intc_0 // 0

15. (optional) Delete the AlgoBet dApp

Let’s write a Python script that deletes the AlgoBet dApp as Manager account.

Remember that you will be allowed to request the AlgoBet dApp deletion only if the time allowed for payouts is elapsed. In this tutorial, we set a 15 minutes time interval.

At first, we have to retrieve sandbox accounts:

from beaker import sandbox

# Get sandbox `algod` client
sandbox_client = sandbox.get_algod_client()

# Retrieve sandbox accounts
wallet_name = "mywallet"  # <-- put wallet's name here
wallet_password = "mywalletpass"  # <-- put wallet's password here
sandbox_accounts = sandbox.get_accounts(
    wallet_name=wallet_name,
    wallet_password=wallet_password,
)
print(f"Found {len(sandbox_accounts)} accounts into the wallet")

# Pop accounts from sandbox
manager_acct = sandbox_accounts.pop()
print(f"Manager account: {manager_acct.address}")

oracle_acct = sandbox_accounts.pop()
print(f"Oracle account: {oracle_acct.address}")

participant_1_acct = sandbox_accounts.pop()
print(f"Participant 1 account: {participant_1_acct.address}")

participant_2_acct = sandbox_accounts.pop()
print(f"Participant 2 account: {participant_2_acct.address}")

Next, we instantiate a Beaker’s ApplicationClient. This time, the application client must be signed by the Manager account.

Warning: please remember to paste the Application ID and the Application Account Address copied before.

from beaker.client import ApplicationClient

from contract import AlgoBet

APP_ID = 888  # <-- Paste Application ID here
APP_ADDR = 'XXXXXX'  # <-- Paste Application Account Address here

# Create an Application client signed by Manager account
app_client_manager = ApplicationClient(
    # Use the `algod` client connected to sandbox
    client=sandbox_client,
    # Provide an AlgoBet instance to the client
    app=AlgoBet(),
    # Provide a deployed AlgoBet dApp ID
    app_id=APP_ID,
    # Select the Manager account as transaction signer
    signer=manager_acct.signer
)

Let’s now request the delete transaction.

# Perform a delete application call
tx_id = app_client_manager.delete()

# Print the result
print(f"Deleted AlgoBet dApp with id: {APP_ID} in tx: {tx_id}")

Save the Python script as delete_algobet_dapp.py, then open a terminal and execute the script by issuing:

$> python delete_algobet_dapp.py

The script should have an output similar to:

Found 5 accounts into the wallet

Manager account: SKBBUTCFKLPOGWMHLMA2ME3OAR4D2KIXLBXX366R2N7EOBS56QNYWN3SKE
Oracle account: P7LUQ4IIU43LGBHSEMDQ2K7OK75ZMLJC2ACEVX2BUAGCCYOGQIQ3LTJGWM
Participant 1 account: FLD3ATI6H3MTVBVE6X3OF7DJF7PC42Z4XTCVXQ3DMDK6O3NRRCQMGUFDKU
Participant 2 account: DB6U6AAH56T2SIMQVUMI4O3SFKTY2QIU2GAPI3N2PRXTNATWFTYGXJUBKI

Deleted AlgoBet dApp with id: 145117913 in tx: LHFJ2KROWM5XAHPYYRVGQ2CGPZA5J6HW6SIB7QYGFPNUU7K5TWZQ

Note that the Manager account which requests the dApp deletion will receive, with a payment transaction, all the content of dApp account at the moment of its deletion.

Summary

Congrats, you reached the end of the tutorial!

So far, you learned:

  • how to setup an Algorand environment for developing Smart Contracts using Beaker framework and for deploying them leveraging the sandbox;
  • how to generate TEAL code from a Beaker Smart Contract;
  • how to deploy a Beaker dApp on Algorand testnet;
  • how to setup a decentralized bet system running on Algorand network, using AlgoBet Smart Contract.

Have fun with Beaker!