Algorand Smart Contracts Using PyTeal
Get started with Algorand by writing a simple smart contract using Python
In this article, I’ll show how to write a simple smart contract on Algorand using PyTeal, deploy it on a local development network, and interact with it using Python and then in a browser. I will try simplifying the tooling used so the reader can focus on the PyTeal code.
Setting Up the Development Environment
The first step to configure your local development environment is to set up the Algorand sandbox. The sandbox creates a local private network allowing the developer full control to reset its state at any time, and an indexer for this network. The only requirement for using the Algorand sandbox is installing Docker and Docker-Compose.
To run a sandbox, clone the repository:
git clone https://github.com/algorand/sandbox
Enter the directory and start the sandbox by running the following command (the -v
shows the log):
sandbox up -v
Now you should have three containers running: (i) an indexer, (ii) a Postgres database for the indexer, and most importantly, the (iii) algod client.
In the algod container, you can run, for example, goal, which is Algorand’s CLI for interacting with the network. You can use this to get a list of accounts created in the sandbox, either by entering the following into the container:
./sandbox enter algod
goal account list
or by running the command:
./sandbox goal account list
This example will show the list of Algorand accounts on this machine:
For this tutorial, I won’t go into the available CLI tools like goal, kmd, algokey, etc. I will try to use only Python, mainly the Python algoSDK but also briefly touch on Beaker, a smart contract development framework for PyTeal.
Beaker makes development on Algorand much easier, and maybe I’ll write a future article on it, but for now, we will use its’ helpers functions to get the algod client and accounts from the sandbox.
Install the required libraries
To write, deploy and call the smart contract, we will create on the sandbox, then install PyTeal, Python Algorand SDK, and Beaker.
python -m venv venv
source venv/bin/activatepip install pyteal beaker-pyteal
(The SDK will also be installed as it is a dependency)
We can now create a connected instance of the Algod client using the Python Algorand SDK.
However, Beaker gives us some helper functions that make it easier to create the client (it runs the same as above with the default sandbox settings) and also get the existing accounts.
We can now check the existing accounts with respective ALGO balance
.
Account balance DNP46…6MIYI: 4,000,000,000,000,000 microAlgos
Account balance KM23D…NL3NA: 4,000,000,000,000,000 microAlgos
Account balance WHNMM…SNHAI: 1,000,000,000,000,000 microAlgos
Send a Simple Payment Transaction
Let’s test that we have everything correctly set up by sending a transaction for a transfer of 1000 microALGOs between two accounts.
If we then run the code to list accounts again, we will see the balance change between those two accounts.
Blockchain Explorer for the Sandbox
At this point, I would suggest checking out https://dappflow.org/, a development tool for the browser that enables us to connect to the sandbox for information on accounts, transactions, assets, and applications.
With this tool, you can easily check the transaction sent to the sandbox, existing applications, application calls, etc.
PyTeal
In this tutorial, we will write a simple Storage smart contract (application) using PyTeal.
PyTeal is a Python language binding for Algorand Smart Contracts (ASC1s), implemented using a stack-based language called Transaction Execution Approval Language (TEAL).
TEAL is essentially an assembly language and is not user-friendly to write. PyTeal provides high-level functional programming abstraction over TEAL, where developers can express smart contract logic purely using Python through PyTeal objects.
The PyTeal objects we will be using for writing our simple smart contract are:
Approve
/Reject
— expressions cause the program to exit immediately. IfApprove
is used, then the execution is marked as successful, and ifReject
is used, then the execution is marked as unsuccessful.Bytes
— A byte slice is a binary string.Int
— An integer wheren >= 0
andn < 2 ** 64
Seq
— a chaining expression to create a sequence of multiple expressionsCond
— an expression that chains a series of tests to select a result expressionBtoi()
— function to convert a byte string to a uint64App.globalPut
— a method to write to the global state of the current applicationTxn.application_args
— represents the application call arguments arrayTxn.application_id()
— get the application ID from theApplicationCall
portion of the current transactionTxn.on_completion()
— Get the on-completion action from theApplicationCall
portion of the transactionOnComplete
— an enum of values thatTxnObject.on_completion() may return
For a complete reference on these and other PyTeal objects, refer to the documentation here: https://pyteal.readthedocs.io/.
Just as an example, if we compile the Approve()
PyTeal object:
we will get this TEAL code as output:
#pragma version 6
int 1
return
As a second example, if we compile a sequence of writing the integer 5
to the global state of the current application under the “number” key and then approving the transaction:
we will get this TEAL code as output:
#pragma version 6
byte “number”
int 5
app_global_put
int 1
return
Create a Simple Storage Smart Contract
For a complete explanation of how smart contracts on Algorand work, I recommend reading the first few paragraphs of the Smart Contracts page on the Algorand Developer docs, especially the section “The lifecycle of a smart contract.”
We will create a Simple Storage smart contract that holds a value in a variable called “number,” allowing the user to retrieve the current value and store a new value over it. We will use PyTeal to create the approval and clear state programs.
In short, the Approval Program is responsible for implementing most of the logic of an application. The program will succeed only if one nonzero value is left on the stack upon program completion or the return
opcode is called with a positive value on the top. The ClearStateProgram
is used to handle accounts using the clear call to remove the smart contract from their balance record.
For the ClearStateProgram
, we won’t handle any logic, and we’ll always leave one on the stack so the call will always be successful.
We are running the compileTeal
function on the Approve()
. That returns Int(1)
with the arguments mode Application and TEAL version 6. This will generate the following TEAL code:
#pragma version 6
int 1
return
Now for the ApprovalProgram
, which handles all transaction call types (except the ClearState
):
NoOp
— Generic application calls to execute theApprovalProgram
.OptIn
— Accounts use this transaction to begin participating in a smart contract. Participation enables local storage usage.DeleteApplication
— Transaction to delete the application.UpdateApplication
— Transaction to update TEAL Programs for a contract.CloseOut
— Accounts use this transaction to close out their participation in the contract. This call can fail based on the TEAL logic, preventing the account from removing the contract from its balance record.
In our contract, we will reject all transaction calls except NoOp
. Here is the full code of the approval program. This is probably not the best way to write this application, but it’s to showcase the different parts of logic that can be defined with PyTeal.
The main part of the program is:
program = Cond(
[Txn.application_id() == Int(0), init],
[Txn.on_completion() == OnComplete.NoOp, no_op], [Txn.on_completion() == OnComplete.DeleteApplication, Reject()],
[Txn.on_completion() == OnComplete.UpdateApplication, Reject()],
[Txn.on_completion() == OnComplete.OptIn, Reject()],
[Txn.on_completion() == OnComplete.CloseOut, Reject()],
)
Where we use Cond
to indicate conditions, like a series of “if” statements written in pairs of logical statements and actions.
The first one will run the “init” expression on application creation, i.e., when the transaction is called but does not contain an application id:
[Txn.application_id() == Int(0), init]
The init expression stores the value 0
in the global var number and then approves the transaction.
init = Seq(App.globalPut(number, Int(0)), Approve())
Note that “number” in the above code is just a Python variable that is pointing to the byteslice also named number
defined at the beginning.
number = Bytes("number")
Continuing the Cond
expression for generic application calls (NoOp
), the “store” expression gets called:
[Txn.on_completion() == OnComplete.NoOp, store]
The store expression takes the first argument of the transaction call, Tx.application_args[0]
, converts it from bytes to integer Btoi()
and stores it in the number variable, App.globalPut(number, …)
.
store = Seq(
App.globalPut(number, Btoi(Txn.application_args[0])),
Approve()
)
And finally, continuing the Cond
expression, the application call gets rejected using the Reject()
or Int(0)
for DeleteApplication
, UpdateApplication
, OptIn
, and CloseOut
transaction types. Here’s the code:
[Txn.on_completion() == OnComplete.DeleteApplication, Reject()],
[Txn.on_completion() == OnComplete.UpdateApplication, Reject()],
[Txn.on_completion() == OnComplete.OptIn, Reject()],
[Txn.on_completion() == OnComplete.CloseOut, Reject()],
The TEAL code generated by the function we defined above is too long to publish here, but I would advise the reader to output and look through it.
It must be said that there are currently better ways to write applications on Algorand than using only these simple objects, namely, using the ABI Router. It exposes the methods from the application that can be called or, even better, using Beaker. But this smart contract was simple enough to implement. Also, Algorand Foundation has hinted at even further improvements to the development toolkits, namely Algokit, coming soon.
Deploy the Smart Contract
We will deploy the smart contract, called application on Algorand, by sending an ApplicationCreate
transaction using Python SDK functions.
First, we will create a helper function to provide the TEAL code of the approval and clear programs as bytes to the ApplicationCreate
transaction.
Next, we create the transaction with ApplicationCreateTxn
and supply the appropriate inputs:
sender
— the address of the contract creatorsp
— suggested parameters that we can get from the clienton_complete
— what application should so once the program is done being runapproval_program
andclear_program
— The TEAL code in bytesglobal_schema
andlocal_schema
—StateSchema
objects indicating how many integers and byteslices are in the global and local variables.
Then we sign the transaction with the sender’s private key and send the transaction to the client.
Running this code should output a string with the application id and the transaction hash.
Created App with id: 13 in tx: 5E5PXN24CBRZVWPD2KMMFFIV3LJZYXNGMOKH2NGANFQYXZ5ELQDA
Making Application Calls
Now we will make a call to the application with the integer 5
as the argument so that it gets stored in the global state of the application under the number
key.
We will use the ApplicationCallTxn
, adding the argument app_args
with a list of inputs. Here’s the code:
After the call transaction is successful, we can query the application’s global state.
Which would output:
{‘key’: b’number’, ‘value’: {‘bytes’: ‘’, ‘type’: 2, ‘uint’: 5}}
The value stored in the key number
is of type 2
, i.e., an integer, and has the value of 5
.
(Notice we had to decode the keys to see the number
as they are stored as base64 encoded string.)
Indexer
I will briefly mention the indexer, which is also available in the sandbox and allows querying a lot of information.
Like getting the state from the application defined above:
Here’s the output:
{'application': {'created-at-round': 1792,
'deleted': False,
'id': 13,
'params': {'approval-program': 'BiACAAEmAQZudW1iZXIxGCISQAA2MRkiEkAAKDEZgQUSQAAeMRmBBBJAABQxGSMSQAALMRmBAhJAAAEAIkMiQyJDIkMoNhoAZyNDKCJnI0M=',
'clear-state-program': 'BoEBQw==',
'creator': 'DNP46TUHD55E7ZFF4GBLDL4ZQGTHX4V623FVIJPIZWMV32EAXOMVO6MIYI',
'global-state': [{'key': 'bnVtYmVy',
'value': {'bytes': '', 'type': 2, 'uint': 5}}],
'global-state-schema': {'num-byte-slice': 0, 'num-uint': 1},
'local-state-schema': {'num-byte-slice': 0, 'num-uint': 0}}},
'current-round': 16881}
Or getting the information on a specific account:
Here’s the output:
{'account': {'address': 'UBIVVQ72ZI6MUPGAF7EIXKNGBDQJFHY4BXP4NUEE5UQ6H6Y7MRI4HVLVE4',
'amount': 30002040,
'amount-without-pending-rewards': 30000000,
'created-at-round': 13826,
'deleted': False,
'pending-rewards': 2040,
'reward-base': 345,
'rewards': 2040,
'round': 16528,
'status': 'Offline',
'total-apps-opted-in': 0,
'total-assets-opted-in': 0,
'total-created-apps': 0,
'total-created-assets': 0},
'current-round': 16528}
Or searching for applications by supplying filters, although, with no filters, we will get all applications:
Here’s the truncated output:
{'applications': [{'created-at-round': 1521,
'deleted': False,
'id': 8,
'params': {'approval-program': 'BiACAAEmAQZudW1iZXIxGCISQAA2MRkiEkAAKDEZgQUSQAAeMRmBBBJAABQxGSMSQAALMRmBAhJAAAEAIkMiQyJDIkMoNhoAZyNDKCJnI0M=',
'clear-state-program': 'BoEBQw==',
'creator': 'DNP46TUHD55E7ZFF4GBLDL4ZQGTHX4V623FVIJPIZWMV32EAXOMVO6MIYI',
'global-state': [{'key': 'bnVtYmVy',
'value': {'bytes': '', 'type': 2, 'uint': 0}}],
'global-state-schema': {'num-byte-slice': 1, 'num-uint': 1},
'local-state-schema': {'num-byte-slice': 0, 'num-uint': 0}}},
(...)
Interacting With the Contract Through a Frontend in the Browser
Now we will write a basic HTML file to interact with our deployed application from the browser.
We will use the Javascript Algorand SDK to build and send the transactions and MyAlgo Connect to sign them.
The minified version of the MyAlgo Connect Javascript library can be downloaded here: https://github.com/randlabs/myalgo-connect/releases.
The Algorand SDK can be downloaded here: https://github.com/algorand/js-algorand-sdk. It can also be used directly in the browser with a minified bundle.
I will leave out error-catching and other tests to make the code less verbose and easier to read in the article.
To test, we can start by connecting to the client and getting information from an account with JavaScript:
To get the following example output:
Account balance: 29998014 microAlgos
Now we will test sending a payment transaction that an account will sign from MyAlgo Wallet. For that, we will use the KMD Client with the Python SDK to create a test account and fund it to import that account into MyAlgo Wallet.
The sandbox will default have an “ unencrypted-default-wallet “ wallet with three pre-funded accounts. We can check that by running the following command:
./sandbox goal wallet list
to see the following output:
##################################################
Wallet: AssetWallet
ID: e3113df264e17bb996fdb1fc06a37fc9
##################################################
Wallet: unencrypted-default-wallet (default)
ID: d62580046d0e0e78828f7ef9e86efae6
##################################################
and then to see the accounts:
./sandbox goal account list
So we can create an account in this wallet with the following Python snippet:
(Please insert your own mnemonic in the code.)
Let’s import that account into MyAlgo wallet (https://wallet.myalgo.com) by selecting “Add Account” and then “Import Phrase” and entering the same 25 words from the mnemonic used to create the account. (I know, it can be a bit cumbersome.)
We can now send a payment transaction. The imported account will sign that in MyAlgo wallet.
When sending this transaction, MyAlgo wallet will prompt you to connect an account in your wallet (normally, you would have a single “Connect Wallet” for the dApp) and then to sign the transaction.
If successful, you can check the balances on the sender and receiver accounts and verify that the payment amount was transferred.
To finish off, we’ll now read the state and send a call transaction to our deployed application.
This would send the application call with the value from the input box on the page:
And if successful, the number
key in the application state should change to the value in the call.
Finally, and to show yet another way to get information on the application, we’ll make a GET
request to the indexer in the sandbox.
We are just outputting the value in the global-state
key.
But the full output would be:
{‘application’: {‘created-at-round’: 3827,
‘deleted’: False,
‘id’: 13,
‘params’: {‘approval-program’: ‘BiACAAEmAQZudW1iZXIxGCISQAA3MRkiEkAAKDEZgQUSQAAeMRmBBBJAABQxGSMSQAALMRmBAhJAAAEAIkMiQyJDIkMoNhoAF2cjQygiZyND’,
‘clear-state-program’: ‘BoEBQw==’,
‘creator’: ‘DNP46TUHD55E7ZFF4GBLDL4ZQGTHX4V623FVIJPIZWMV32EAXOMVO6MIYI’,
‘global-state’: [{‘key’: ‘bnVtYmVy’,
‘value’: {‘bytes’: ‘’, ‘type’: 2, ‘uint’: 39}}],
‘global-state-schema’: {‘num-byte-slice’: 1, ‘num-uint’: 1},
‘local-state-schema’: {‘num-byte-slice’: 0, ‘num-uint’: 0}}},
‘current-round’: 37710}
Thanks for reading! Stay tuned for more.
Connect with meTwitter | LinkedIn | Github | Instagram