
Issuing university diplomas on the Algorand blockchain
This tutorial provides a practical application of using PyTeal to generate smart contracts. It connects students with registrar for diplomas. This DApp demonstrates how the Algorand blockchain can facilitate diploma issuance for graduating university students. Graduating students can opt-in to this DApp. And then a privileged registrar account can issue specific diplomas to specific students. Students get their diploma recorded under their account which any 3rd party can audit and verify its integrity.
Additionally, the registrar can perform other operations for posterity. One such operation is transferring registrar duties to another account. After such transfer, the designated account will become the new registrar and the old registrar will lose all privileges. The registrar may also revoke diplomas if needed and also delete the entire DApp.
A detailed overview of all possible operations for both students and registrar can be found by running: python3 run_diploma.py help

Requirements
- Python 3
- Algorand Python SDK Version 1.6.0
- PyTEAL Version 0.7.0
- Access to an Algorand Node
- A few Algorand accounts funded with a balance
Background
This tutorial follows the Algo-Diploma repository on GitHub. This tutorial explains the code block by block. Reference the repository to see the whole project at once.
Warning
This solution is intended for learning purposes only. It does not cover error checking and other edge cases. The smart contract(s) in this solution have NOT been audited. Therefore, it should not be used as a production application.**
Configuration
List the test accounts in the config.yml
file. For example:
registrar: "alice"
alice:
mnemonic: "<MNEMONIC HERE>"
bob:
mnemonic: "<MNEMONIC HERE>"
charlie:
mnemonic: "<MNEMONIC HERE>"
APP_ID: -1
This example config file lists three users, of which alice
is the registrar. The role of a registrar is to issue diplomas to the students, in this case, bob
and charlie
.
In a production system, the config file would be replaced by a proper wallet key management system. See Algosigner, myAlgo or the official Algorand Wallet.
Repository Usage
The repository listed above is ready for use as follows:
i. Install the required Python packages: pip3 install -r requirements.txt
ii. Make the PyTEAL contract code: make
iii. Deploy the smart contract: python3 run_diploma.py deploy
This command will print an identifying number for APP_ID
. Be sure to record this in the config.yml
.
iv. Use the DApp:
-
Opt-in
bob
to the smart contract:python3 run_diploma.py opt-in bob
-
Issue a diploma to
bob
:python3 run_diploma.py issue-diploma bob "MIT,2020,BSc,Computer Science and Engineering"
-
Transfer registrar duties to
charlie
:python3 run_diploma.py reassign-registrar charlie
In order to transfer registrar duties, the current (old) registrar must still be set in theconfig.yml
. Once the duties have been transfered tocharlie
, update the registrar field in theconfig.yml
to listcharlie
. -
For more commands run:
python3 run_diploma.py help
Steps
1. Roles
- Registrar: The admin of the DApp. The registrar can issue and revoke diplomas. They also have the power to update and delete the DApp from the blockchain.
- Student: A regular account in this DApp. They can receive a diploma from the registrar. They must opt-in to receive a diploma and have the option to leave the DApp at will.
2. Smart Contract Code
This DApp is expressed as a stateful Algorand smart contract. It is written in PyTEAL following the suggested development guidelines. The smart contract source code is accessible here.
Storage
There is one global bytes field and one local bytes field (per opted-in account). The global field holds the address of the current registrar. The account with this address has all of the registrar privileges of this DApp. The local field per account is where an issued diploma is recorded. A diploma is represented as a bytes array of common metadata such as issuing institution, year, degree title and type, etc.
This storage schema is specified as follows during smart contract deployment.
# Declare application state storage (immutable)
local_ints = 0
local_bytes = 1
global_ints = 0
global_bytes = 1
global_schema = transaction.StateSchema(global_ints, global_bytes)
local_schema = transaction.StateSchema(local_ints, local_bytes)
Overall DApp Architecture
In this DApp, there is an account designated as a registrar and all other accounts are simply students. A global storage variable named "registrar"
delineates which account is the registrar. A local storage variable in each account stores any issued diploma metadata. Accounts must opt-in to this DApp in order to receive a diploma if one is issued to them.
This DApp implements a number of commands. A command is a sequence of PyTEAL operations which are called based on some conditional control-flow logic depending on a string argument passed in. Each command has its respective sanity checks, such as checking whether the caller is the registrar when necessary or the number of arguments supplied.
This DApp supports three commands, diploma issuance, diploma revocation, and registrar reassignment. Diploma issuance is handled by writing the diploma metadata to the local storage of the account. Diploma revocation is handled by clearing the account’s local storage. Lastly, registrar reassignment is handled by overwriting the global storage "registrar"
variable with the new registrar’s account address.
Contract Helpers
var_registrar = Bytes("registrar")
is_registrar = Txn.sender() == App.globalGet(var_registrar)
Helper code simplifies smart contract code that is repeated often. The first line assigns the name of the global registrar variable to a Python variable for easy access later on. The second line checks if the sender of the current transaction invoking this smart contract is the current registrar. The result is also saved in a Python variable for each access later on.
Contract Logic
program = Cond(
[Txn.application_id() == Int(0), init_contract],
[Txn.on_completion() == OnComplete.DeleteApplication, Return(is_registrar)],
[Txn.on_completion() == OnComplete.UpdateApplication, Return(is_registrar)],
[Txn.on_completion() == OnComplete.OptIn, Return(Int(1))],
[Txn.on_completion() == OnComplete.CloseOut, Return(Int(1))],
[Txn.application_args[0] == Bytes("issue_diploma"), issue_diploma],
[Txn.application_args[0] == Bytes("revoke_diploma"), revoke_diploma],
[Txn.application_args[0] == Bytes("reassign_registrar"), reassign_registrar]
)
This is the contract logic that directs how the DApp reacts to certain commands. The following breakdown explains this logic line-by-line.
Txn.application_id() == Int(0)
: When a smart contract is initially deployed, theTxn.application_id()
will be 0. This is the time to callinit_contract
to record the creator as the initial registrar of the DApp.Txn.on_completion() == OnComplete.DeleteApplication
: Only allow the current registrar to delete this DApp.Txn.on_completion() == OnComplete.UpdateApplication
: Only allow the current registrar to update this DApp.Txn.on_completion() == OnComplete.OptIn
: Allow any account to opt-in to this DAppTxn.on_completion() == OnComplete.CloseOut
: Allow any account to leave this DAppTxn.application_args[0] == Bytes("issue_diploma")
: Run theissue_diploma
logic of this DApp.Txn.application_args[0] == Bytes("revoke_diploma")
: Run therevoke_diploma
logic of this DApp.Txn.application_args[0] == Bytes("reassign_registrar")
: Run thereassign_registrar
logic of this DApp.
Contract Initialization
init_contract = Seq([
App.globalPut(var_registrar, Txn.sender()),
Return(Int(1))
])
This block records the creator of the DApp as the initial registrar. This block is only called when Txn.application_id() == Int(0)
signalling that the smart contract is currently being deployed. Thus the Txn.sender()
is the creator account currently deploying the DApp.
Diploma Issuance
diploma_metadata = Txn.application_args[1]
issue_diploma = Seq([
Assert(is_registrar),
Assert(Txn.application_args.length() == Int(2)),
App.localPut(Int(1), Bytes("diploma"), diploma_metadata),
Return(Int(1))
])
This block issues a diploma to an account. This code block is invoked by the "issue_diploma"
command of this DApp. It takes an additional argument diploma_metadata = Txn.application_args[1]
and an account Int(1)
which is the account to receive the diploma metadata. The body of the block performs some sanity checks, that the caller is the registrar and that precisely two arguments are passed. If those checks pass, then the account at index 1, the one passed in, will receive the diploma metadata.
Revoke Diploma
revoke_diploma = Seq([
Assert(is_registrar),
Assert(Txn.application_args.length() == Int(1)),
App.localDel(Int(1), Bytes("diploma")),
Return(Int(1))
])
This block revokes the diploma of an account. This code is invoked by the "revoke_diploma"
command of this DApp. It takes no additional arguments, only an account Int(1)
which is the account that will get its diploma revoked. Like above, the body of this block performs the same sanity checks, that the caller is the registrar and that there is only one argument passed. If these checks pass, then the account at index 1 will lose its diploma metadata.
Registrar Reassignment
new_registrar = Txn.accounts[1]
reassign_registrar = Seq([
Assert(is_registrar),
Assert(Txn.application_args.length() == Int(1)),
App.globalPut(var_registrar, new_registrar),
Return(Int(1))
])
This block reassigns the registrar of this DApp. This code is invoked by the "reassign_registrar"
command of this DApp. It takes no additional arguments, only an account Txn.accounts[1]
which is the account that will become the new registrar. This block performs some sanity checks before reassigning the registrar.
Clear Program
This DApp does not require any particular clean up during clearing. Therefore, the clear program always returns successfully.
#pragma version 3
int 1
3. DApp Interface Program
This DApp is interfaced with a Python program using the Algorand SDK. This program is used to deploy the DApp as well as invoke the various DApp commands. Much of the SDK code and helper functions are borrowed from an example Algorand SDK app here.
This SDK program has a host of functions. The help message from python3 run_diploma.py help
lists the available functions:
Available commands:
deploy: Deploy this smart contract for the first time
update: Update this smart contract with new TEAL code
opt-in <account-name>: Opt-in an account into this smart contract
close-out <account-name>: Close-out an account from this smart contract
delete <creator-name>: Delete this smart contract
clear <account-name>: Clear this smart contract
issue-diploma <account-name> <diploma-metadata>: Issue a degree to an account
revoke-diploma <account-name>: Nullify the diploma of an account
inspect <account-name>: Inspect an account's diploma on the Algorand blockchain
inspect-global <creator-name>: Inspect this smart contract's global state
reassign-registrar <account-name>: Assign an account to be the current registrar
help: Print this help message
Any arguments taken by each function are listed with angle brackets <...>
.
DApp Maintenance
The deploy
, update
, delete
, and clear
functions are used by a DApp administrator to maintain the DApp.
Deploy
Deployment of a smart contract begins with compiling the TEAL programs to a base64 encoded binary (the PyTEAL contract code is converted to TEAL by running make
).
# Read the smart contract source files
smart_contract_file = open("./assets/diploma_smart_contract.teal", "rb")
smart_contract_source = smart_contract_file.read()
smart_contract_program = common.compile_program(algod_client, smart_contract_source)
clear_program_file = open("./assets/clear_program.teal", "rb")
clear_program_source = clear_program_file.read()
clear_program = common.compile_program(algod_client, clear_program_source)
Then a transaction is sent into the network signaling the deployment of a new smart contract. The creator of this DApp is the account that sends this transaction. In this case, the registrar at the time of deployment is the DApp creator. The formulation of this transaction is handled by the Python SDK.
txn = transaction.ApplicationCreateTxn(
sender, params, on_complete, \
smart_contract_program, clear_program, \
global_schema, local_schema)
The global_schema
and local_schema
outline how many global and local storage variables this contract will use. For this DApp, both only indicate one Bytes
storage variable each.
Update
Updating the source code of this DApp begins with compiling the TEAL programs, similarly to deployment. However, the update event involves a different type of transaction. Most notably, the app_id
of the DApp must be submitted to point to which DApp to update. Only the current registrar may update this DApp. The Python SDK creates the transaction to update a smart contract as follows:
txn = transaction.ApplicationUpdateTxn(sender, params, app_id, \
smart_contract_program, clear_program)
Delete
DApp deletion is performed by sending a specific transaction with the app_id
of the DApp to be deleted. Only the current registrar may delete this DApp. This is handled concisely with the Python SDK.
txn = transaction.ApplicationDeleteTxn(sender, params, app_id)
Clear
Any account can remove their participation from the DApp. This is done by sending a clearing transaction to the network. This implicitly runs the clear_program
of the DApp to perform any residual clean up and regardless of whether the clear program succeeds or not, the user will be removed from the DApp. The Python SDK creates the clear transaction as follows:
txn = transaction.ApplicationClearStateTxn(sender, params, app_id)
DApp Common Usage
The opt-in
, close-out
, issue-diploma
, revoke-diploma
, and reassign-registrar
functions are the most common to be called by any user of this DApp.
Opt-In
Since this DApp utilizes local storage, any account wishing to participate in this DApp to receive a diploma must opt-in to it. This is performed by sending a specific opt-in transaction with the app_id
of the DApp to join. The Python SDK creates the opt-in transaction as follows:
txn = transaction.ApplicationOptInTxn(sender, params, app_id)
Close-Out
Separate from the clear function, an account can leave a DApp by closing out of it. An account leaves a DApp only if a close-out transaction with the corresponding app_id
succeeds, differing from the clear function which unconditionally removes an account. In the case of this DApp, close-out always succeeds. The Python SDK creates the close-out transaction as follows:
txn = transaction.ApplicationCloseOutTxn(sender, params, app_id)
Issue Diploma
The registrar issues a diploma by performing a call to the smart contract of this DApp. A call to the DApp is simply a transaction with the appropriate arguments required by the call. In the case of diploma issuance, that is the receiving account of the diploma as well as the diploma metadata – degree type, institution, year, etc. This is facilitated by the Python SDK as a NoOp application call as follows:
txn = transaction.ApplicationNoOpTxn(sender, params, index, app_args, accounts)
The first argument of the app_args
is the string "issue-diploma"
which designates this call to issue a diploma.
Revoke Diploma
Similar to the diploma issuance, the registrar calls the DApp by sending a transaction with the appropriate arguments. In this case, this is only the account that will get its diploma revoked. This is facilitated by the same Python SDK call as above, but where the first argument of the app_args
is the string "revoke-diploma"
.
Reassign Registrar
Similar to the other calls, registrar reassignment is a call to the DApp by the current registrar. Passed along is the account of the registrar to-be. The Python SDK call is the same as above, but the first argument of the app_args
is the string "reassign-registrar"
.
DApp Inspection
The inspect
and inspect-global
functions allow a third party to view the state variables of this DApp. These functions are most useful to determine the integrity and validity of a student’s diploma for example.
Inspect
Any 3rd party with access to the Algorand blockchain can inspect an account’s local storage to view their diploma. The account
to inspect is passed in as an argument to the DApp Interface Program. The local Bytes
storage variables are base 64 encoded, so inspection decodes the value to be human-readable. A read_local_state
helper function adopted from an example Algorand SDK app serves this function.
common.read_local_state(algod_client, pub_keys[account], app_id)
Inspect Global
A 3rd party can also inspect the global Bytes
storage variable to see the current registrar. Global variables are stored in the creator
account, the account that deployed the DApp. The DApp Interface Program uses the registrar during deployment as the creator account. However, even after registrar reassignment, the original creator account must be passed in to inspect the global current registrar variable. A read_global_state
helper function from an example Algorand SDK app serves this function.
common.read_global_state(algod_client, pub_keys[creator], app_id)
4. Conclusion
This DApp exemplifies a simple yet tangible use-case for the Algorand blockchain. The code is openly available at the Algo-Diploma respository on GitHub. This diploma application is not complicated, yet covers many fundamental concepts for Algorand smart contract programming. Key concepts explored include:
- Writing a PyTEAL smart contract adhering to all of the best standard practices
- Supports the full life cycle of a smart contract, from deployment to deletion
- Multiple account types with different privilege levels
- Permission checking to ensure application security and integrity
- Supports multiple commands with different behaviors
- Writing an interface program using an Algorand SDK
- An easy interface to develop, maintain and use the DApp
This code is free to be used as outlined by the MIT License.