AlgoMinter - a Web App for Minting Assets Using Python, AlgoSigner, and Anvil Platform
This tutorial is mainly aimed at developers with beginner python and web development experience who are starting their journey into the Algorand development ecosystem, want to build prototypes that are easy to host and maintain, and lastly need to enable essential dApp capabilities without hosting an maintaining an Algorand blockchain node by utilizing AlgoSigner and PureStake APIs.
What is Anvil?
Anvil is essentially an online web-app building service that is especially perfect for rapid prototyping, handles the infrastructure configuration and hosting, and most importantly allows using pure python for both frontend and backend code of your web app. One can name a lot of use cases for when an ability to develop deploy and test an idea as a rapid prototype in python can be of value.
What is AlgoSigner?
AlgoSigner is a web browser wallet extension for Algorand-based applications on the web.
Requirements
- Free account created on Anvil Platform
- Chromium based browser with AlgoSigner extension
Background
The steps described in this tutorial are generally very straightforward to follow, however, it is highly recommended to get familiar with Anvil Editor documentation. This will allow you to easily navigate in Anvil’s app-building web-based IDE. Optionally, you can also go over Anvil Quick Start guides to get a further grasp of how to interact with the IDE. However, simply getting familiar with Editor’s documentation should be sufficient to easily follow the steps in this tutorial.
Steps
1. Create a new Blank App
Assuming that you have signed up on the Anvil platform, once you click Start Building
you should see a starting page that allows the creation of new blank web app projects.
Click on New Blank App
and choose Material Design
template. The reason for using that particular template is due to some pre-defined components that will make our UI slightly more customizable out of the box.
1.1 Set the app name to AlgoMinter
Let’s call our new sample app AlgoMinter
since it will provide a simple user interface to mint new assets on Algorand.
As demonstrated on Fig 1.1
, click on Rename
and set the name for our app to AlgoMinter
.
1.2 Setup a blank Anvil form
By default, you could see that since we selected a Material Design
template, inside the Form builder (middle element in Anvil’s IDE) there are header and navigation panel components loaded up default. However, let’s keep our app plain and simple.
As demonstrated in Fig 1.2
, click on Add Form
and then select Blank Panel
. Once clicked, you will immediately see a new blank form loaded into the form editor. Rename the newly created Form2
to AlgoMinterMainForm
and delete Form1
entirely. Lastly, click on the drop-down button on AlgoMinterMainForm
under Client Code
on the left side of the web page and select Set as startup Form
. This will ensure that we always load into our new blank form when we run our web app.
2. Creating the UI using Drag and Drop builder
The next step is to define basic UI components.
As demonstrated on the video above, follow the video’s steps and make sure to create a form that will result in something like Fig 2
.
An important detail to follow is to name the components as follows:
TextBoxes
- asset_name_textbox
- unit_name_textbox
- total_units_textbox
- decimals_textbox
- note_textbox
DropDowns
- wallets_dropdown
Buttons
- mint_button
- connect_button
2.1 Setting up basic form’s logic
Essentially, we want our app to operate in 2 steps. Firstly all components below Connect Algosigner
will be invisible. Secondly, after AlgoSigner
is connected we want to hide the Connect Algosigner
button and display rest of the components. As a last detail, we want the Mint
button to be disabled by default unless all required fields are filled.
On each TextBox
component in the form navigate to Container Properties
in the right bottom corner and click on the blue arrows button next to a field called change
(see Fig 2.1
). Upon clicking on each you will see how Anvil redirects you to auto-generated event functions in the Code
tab.
Similarly to above, for each Button
component perform a similar procedure but use the click
field instead. This will generate event functions to be triggered when the user clicks on that particular button element.
Click on the Code
tab in the Form builder and copy paste a snippet from below:
from ._anvil_designer import AlgoMinterMainFormTemplate
from anvil import *
class AlgoMinterMainForm(AlgoMinterMainFormTemplate):
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
self.asset_name_textbox.visible = False
self.unit_name_textbox.visible = False
self.total_units_textbox.visible = False
self.decimals_textbox.visible = False
self.note_textbox.visible = False
self.mint_button.visible = False
self.wallets_dropdown.visible = False
def _reload_mint_button_state(self):
has_asset_name = self.asset_name_textbox.text
has_unit_name = self.unit_name_textbox.text
has_total_units = self.total_units_textbox.text
has_decimals = self.decimals_textbox.text
has_note = self.note_textbox.text
self.mint_button.enabled = all(
[has_asset_name, has_unit_name, has_total_units, has_decimals, has_note]
)
def connect_button_click(self, **event_args):
"""This method is called when the button is clicked"""
# 1. Load wallets
self.wallets_dropdown.items = []
# 2. Enable form
self.connect_button.visible = False
self.asset_name_textbox.visible = True
self.unit_name_textbox.visible = True
self.total_units_textbox.visible = True
self.decimals_textbox.visible = True
self.note_textbox.visible = True
self.mint_button.visible = True
self.wallets_dropdown.visible = True
def note_textbox_change(self, **event_args):
"""This method is called when the text in this text box is edited"""
self._reload_mint_button_state()
def decimals_textbox_change(self, **event_args):
"""This method is called when the text in this text box is edited"""
self._reload_mint_button_state()
def total_units_textbox_change(self, **event_args):
"""This method is called when the text in this text box is edited"""
self._reload_mint_button_state()
def unit_name_textbox_change(self, **event_args):
"""This method is called when the text in this text box is edited"""
self._reload_mint_button_state()
def asset_name_textbox_change(self, **event_args):
"""This method is called when the user presses Enter in this text box"""
self._reload_mint_button_state()
def mint_button_click(self, **event_args):
"""This method is called when the button is clicked"""
The snippet above defines the basic validation logic for our web app. Observe how we hide the components below the Connect Algosigner
button in the __init__
method. In addition to that, observe the _reload_mint_button_state
method that is triggered on each change in each TextBox
and dynamically changes the state of the Mint
button. This gives us essential validation needed for our app’s functionality. The example can always be expanded to handle various intricate details like extra input validation for each field, etc. However, for the sake of simplicity in this beginner tutorial, various tricky validation edge cases are not covered.
3. Integrating AlgoSigner
In this step, we are finally approaching the most interesting parts of this tutorial. We already know at this point that the entire development process in the Anvil platform is done primarily in python. But let’s be honest, what is the world’s most dominant language when it comes to anything web and web-development-related? It’s JavaScript
!
Luckily for us, Anvil got us covered with an ability to integrate custom code snippets in JavaScript, which is exactly what we are going to use to integrate AlgoSigner. As mentioned on the official AlgoSigner dApp integration guide on GitHub, AlgoSigner injects a JavaScript library into every web page the browser user visits, which allows the site to interact with the extension. So in theory, regardless of the stack behind a web app running on a specific URL, when we open that
URL in a browser with the extension available if the AlgoSigner extension is installed we should be able to access it directly to invoke a command that it provides.
One important detail to note is we are going to use TestNet
for our web app for demonstration purposes. You could always switch to the main net by simply renaming mentions of TestNet
to MainNet
in the snippets provided below.
3.1 Add AlgoSigner handler functions to Anvil’s Native Libraries
Navigate to Native Libraries
in the App Browser
and paste the following JavaScript snippet that defines all operations that our python code would need to execute to interact with AlgoSigner.
<script>
function hasAlgo() {
return (typeof AlgoSigner !== 'undefined');
}
function algoConnect(algo_node_type) {
return AlgoSigner.connect({
ledger: "TestNet"
}).then((d) => {
return d;
})
}
function algoAccounts(algo_node_type) {
return AlgoSigner.accounts({
ledger: "TestNet"
});
}
function algoTxParams(algo_node_type) {
return AlgoSigner.algod({
ledger: "TestNet",
path: "/v2/transactions/params"
});
}
function algoSignAssetTx(fromWallet, assetName, assetUnitName, assetTotal, assetDecimals, note, txParams) {
return AlgoSigner.sign({
from: fromWallet,
assetName: assetName,
assetUnitName: assetUnitName,
assetTotal: assetTotal,
assetDecimals: assetDecimals,
note: note,
type: 'acfg',
fee: txParams['min-fee'],
firstRound: txParams['last-round'],
lastRound: txParams['last-round'] + 1000,
genesisID: txParams['genesis-id'],
genesisHash: txParams['genesis-hash'],
flatFee: true
})
}
function algoSendTx(signedTx, algo_node_type) {
return AlgoSigner.send({
ledger: "TestNet",
tx: signedTx.blob
});
}
</script>
What this does is giving an ability to invoke custom JavaScript functions via Python code inside our python forms and modules on the Anvil platform. Methods themselves define basic operations to establish a connection, get recommended transaction parameters, get accounts attached to the wallet, sign an asset creation transaction and finally send a signed transaction. What happens on a side of AlgoSigner during invocation is mainly invocation of various REST APIs provided by PureStake. A simple web app like ours is a perfect use case for utilizing a third-party API like that, eliminating compute overhead and time spent on deploying your Algorand Blockchain node.
3.2 Writing a helper Python class to invoke JavaScript code
Now that we have our javascript code defined in Native Libraries, this means that we can now easily invoke those custom functions using a special helper mechanism that transpiles javascript code into wrappers invokable from python.
Click on a +
sign in the top left corner under Client Code
and select Add Module
. Name the new module as AlgoSignerClient
. This will be a simple python class with a set of static methods to be used by our AlgoMinerMainForm
to perform communication with the user and invoke AlgoSigner to ask users to sign the asset creation transaction.
Cope the snippet below and replace the content of the module:
import anvil.js
from anvil.js.window import hasAlgo, algoConnect, algoAccounts, algoTxParams, algoSignAssetTx, algoSendTx
class AlgoSignerClient:
@staticmethod
def has_algo_signer():
status = hasAlgo()
return status
@staticmethod
def algo_signer_connect():
connection = algoConnect()
return connection
@staticmethod
def algo_signer_accounts():
accounts = algoAccounts()
wallets = []
for item in accounts:
wallets.append(item["address"])
return wallets
@staticmethod
def algo_signer_tx_params():
try:
tx_params_response = algoTxParams()
return (
{
"consensus-version": tx_params_response["consensus-version"],
"fee": tx_params_response["fee"],
"min-fee": tx_params_response["min-fee"],
"last-round": tx_params_response["last-round"],
"genesis-id": tx_params_response["genesis-id"],
"genesis-hash": tx_params_response["genesis-hash"],
},
None,
)
except Exception as exp:
return ({}, exp)
@staticmethod
def algo_signer_sign_asset_tx(
from_wallet,
asset_name,
asset_unit_name,
asset_total,
asset_decimals,
note,
tx_params,
):
try:
signed_tx = anvil.js.call(
algoSignAssetTx,
from_wallet,
asset_name,
asset_unit_name,
asset_total,
asset_decimals,
note,
tx_params,
)
print(signed_tx)
return ({"txID": signed_tx["txID"], "blob": signed_tx["blob"]}, None)
except Exception as exp:
return ({}, exp)
@staticmethod
def algo_signer_send_tx(signed_tx):
try:
send_tx = anvil.js.call(algoSendTx, signed_tx)
return (
{
"txID": send_tx["txId"],
},
None,
)
except Exception as exp:
return ({}, exp)
3.3 Final tweaks in AlgoMinterMainForm
Import the following extra abstractions into AlgoMinterMainForm
‘s code:
import math
from ..AlgoSignerClient import AlgoSignerClient
from time import sleep
Update the connect_button_click
as follows:
def connect_button_click(self, **event_args):
"""This method is called when the button is clicked"""
has_algosigner = AlgoSignerClient.has_algo_signer()
if not has_algosigner:
alert(
"Make sure to use a browser that supports Algosigner extension!",
title="Warning",
dismissible=True,
)
return
# 1. Connect algosigner
AlgoSignerClient.algo_signer_connect()
# 2. Load wallets
self.wallets_dropdown.items = []
self.wallets_dropdown.items = AlgoSignerClient.algo_signer_accounts()
# 3. Enable form
self.connect_button.visible = False
self.asset_name_textbox.visible = True
self.unit_name_textbox.visible = True
self.total_units_textbox.visible = True
self.decimals_textbox.visible = True
self.note_textbox.visible = True
self.mint_button.visible = True
self.wallets_dropdown.visible = True
The snippet above is pretty straightforward. We can see that firstly we check if the user’s browser has an AlgoSigner and display a warning in case it does not. If it does, we load the wallets into our wallets_dropdown
component, and the rest of the snippet is identical to the initial snippet from Step 2.1
.
Lastly copy the snippet and update the mint_button_click
method:
def mint_button_click(self, **event_args):
"""This method is called when the button is clicked"""
asset_name = self.asset_name_textbox.text
unit_name = self.unit_name_textbox.text
total_units = int(self.total_units_textbox.text) * math.pow(10, 2) # needed because units defined in microalgos
decimals = int(self.decimals_textbox.text)
note = self.note_textbox.text
wallet = self.wallets_dropdown.selected_value
# 1. Get tx params
tx_params, tx_params_error = AlgoSignerClient.algo_signer_tx_params()
sleep(1)
if tx_params_error:
alert(
f"Error occured in algosigner, please try again! {tx_params_error}",
title="Error",
dismissible=True,
)
return
# 2. Sign transaction
signed_tx, signed_tx_error = AlgoSignerClient.algo_signer_sign_asset_tx(
wallet, asset_name, unit_name, total_units, decimals, note, tx_params
)
sleep(1)
if signed_tx_error:
print(signed_tx_error)
alert(
f"Error occured in algosigner, please try again! {signed_tx_error}",
title="Error",
dismissible=True,
)
return
# 3. Send transaction
send_tx_response, send_tx_response_error = AlgoSignerClient.algo_signer_send_tx(
signed_tx
)
sleep(1)
if send_tx_response_error:
print(send_tx_response_error)
alert(
f"Error occured in algosigner, please try again! {send_tx_response_error}",
title="Error",
dismissible=True,
)
return
# 4. Inform user about successful asset minting
alert(
Label(text=f'Here is your transaction id : {send_tx_response}', role="text-wrap"),
title="Success! Here is your asset info:",
dismissible=True,
)
# 5. Thats it! You can now optionally await for a transaction
# but this is not covered in this tutorial
The snippet above is pretty simple and straightforward, we obtain the transaction parameters, sign the transaction based on field values provided by the user and lastly send the transaction. Once the transaction id is obtained we display a simple popup that provides the transaction id to the user. Users then can go to the algoexplorer website to observe the minted asset!
4. Testing the web app
It is time to test the code and functionality of our AlgoMinter
app. Click on a gear icon in the top left corner and select Publish App
. The reason why we will perform testing separately from using the Run
functionality is that when we run the app inside the Anvil’s builder, it won’t be able to access the AlgoSigner
even if it is installed in that browser. Hence, we want to run it in a separate tab via the URL that Anvil generates for us and automatically hosting the web app on their infrastructure. Another cool detail to note is that all changes performed in the builder can be reflected live on the published website (mainly for test purposes, do not do that in production!).
Once you clicked on Publish App
, select either Share via private link
or Share via the public link
, keep in mind that on a free plan you might not have an option to choose the domain name. Once you obtained the URL, simply open it in your new tab and you should observe something similar to Fig 4
.
Enter values for some dummy asset to be created, sign your transaction, and observe the asset being created in your AlgoSigner wallet!
5. Conclusion
Congratulations! At this point, you should have your AlgoMinter
up and running on Anvil’s infrastructure. Even with the free plan, you are already able to share your application with anyone on the web. Additionally, by relying on AlgoSigner, any user of your web app can create their unique Assets on Algorand Blockchain.
In case you would like to clone the instance that was created and demonstrated in the tutorial you can use the following link to clone my instance directly and have used it as a base template for your Anvil web apps with AlgoSigner integration pre-configured!
Thank you for following the tutorial and I hope that you found ways to apply this sample to build simple rapidly prototyped web apps powered by Algorand and Anvil’s platform!