_______ ___ _______ _______ ______ ___ _______
| _ || | | || || _ | | | | |
| |_| || | | ___|| _ || | || | | | ___|
| || | | | __ | | | || |_||_ | | | | __
| || |___ | || || |_| || __ || | | || |
| _ || || |_| || || | | || | | |_| |
|__| |__||_______||_______||_______||___| |_||___| |_______|
Algorig is a very compact and intuitive cli tool which tries to ease your Algorand smart contract development and tooling while still preserving all the flexibility. It simply utilizes PyTeal and Algorand Python SDK to make sure that your smart contract development feels quite native and fluent.
Before we get started, we'll need an Algorand Node containing the Algod and KMD services up and running preferably in local environment to be able to interact with Algorand blockchain and manage our accounts.
The shortest way to run an Algorand node for development purposes is installing Algorand sandbox locally. To accomplish this, simply checkout the Algorand sandbox repository and run it by typing the commands below.
$ git clone https://github.com/algorand/sandbox.git
$ cd sandbox
$ ./sanbox up
Once the sandbox up and ready, you will have the both Algod and KMD services running locally under Release
configuration which is a private network suitable for local development.
Here, before getting started to our smart contract development, we'll need a Algorand account that will own the smart contract we develop on the Algorand blockchain. Thankfully, the Algorand sandbox default configuration comes with a default wallet named unencrypted-default-wallet
also containing a couple of accounts those already has a significant $algo balance which is quite sufficient enough to start with.
$ ./sandbox goal wallet list
##################################################
Wallet: unencrypted-default-wallet (default)
ID: de87213c0d084688bb4e40fe2045e16a
##################################################
You can also list all the accounts under the default wallet comes with your sandbox installation.
$ ./sandbox goal account list -w unencrypted-default-wallet
[online] DNSNRCJ6WO4LCVNE6O6JHYSZQ7C725RVJMMTT4GCBOUH4VPMPMZYUVBFLU DNSNRCJ6WO4LCVNE6O6JHYSZQ7C725RVJMMTT4GCBOUH4VPMPMZYUVBFLU 4000008000000000 microAlgos
[offline] DRE4XUEJBPVHHFIUWT5SGH3JIGS5RDUBWYXASA72PINNUVFJIY6SFKSLEE DRE4XUEJBPVHHFIUWT5SGH3JIGS5RDUBWYXASA72PINNUVFJIY6SFKSLEE 1000002000000000 microAlgos
[offline] HPAMDTCS3B2BZUGP5JUNDNBKVWXYPKPC56VEYT3WYBEONPVD6HJLUHMTVQ HPAMDTCS3B2BZUGP5JUNDNBKVWXYPKPC56VEYT3WYBEONPVD6HJLUHMTVQ 4000008000000000 microAlgos
Please refer the Algorand sandbox project page itself for more configuration options and any other details.
Now feel free to pick one of the accounts listed above as a contract owner so that we can assign it while setting up our Algorig project.
Since we're done with our local Algorand node setup, now we're ready to develop an Algorand smart sontract using Algorig. Before we start, let's first configure a virtual environment located under a project directory which will be the home folder of our smart contract project.
$ mkdir mydefi
$ cd mydefi
$ python -m venv venv
$ source venv/bin/activate
Once your virtual environment is up and ready, you may now install the Algorig itself. The best way to install Algorig is simply using pip command as shown below:
$ pip install algorig
At this point, since you put your tool belt on, you're ready initialize a fresh new Algorig project by typing the command below.
$ rig init --signing-address DNSNRCJ6WO4LCVNE6O6JHYSZQ7C725RVJMMTT4GCBOUH4VPMPMZYUVBFLU
This command will create two things. The first one is the main configuration file which you will be able to configure your smart contract development environment.
$ cat protocol.json
{
"algod_address": "http://localhost:4001",
"algod_token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"kmd_address": "http://localhost:4002",
"kmd_token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"wallet_name": "unencrypted-default-wallet",
"wallet_password": "",
"signing_address": "DNSNRCJ6WO4LCVNE6O6JHYSZQ7C725RVJMMTT4GCBOUH4VPMPMZYUVBFLU",
"teal_version": 4,
"num_global_byte_slices": 0,
"num_global_ints": 0,
"num_local_byte_slices": 0,
"num_local_ints": 0,
"app_id": 0
}
Here, you might have also noticed how we used the contract owner account address by utilizing the --signing-address
parameter. Likewise, the use of such cli parameters will help us overriding the default configuration values above those mostly supposed to conform the default settings of our Algorand sandbox setup done before.
Furthermore, init
command also proposes a stub python module called protocol.py
which contains the main entry point to your smart contract development. It's simply a class declaration with a special name Application
which also inherits and implements a base class BaseApplication
comes with Algorig module itself.
# protocol.py
from pyteal import *
from algorig.app import BaseApplication
class Application(BaseApplication):
def get_approval_program(self):
# Implement your contract here using pyteal
return Int(1)
def op_example_command(self, my_str: str, my_int: int = 42):
# This is an example method which can be used as cli command
print(f'my_str: {my_str}, my_int: {my_int}')
You may notice that Application
class simply proposes a method called get_approval_program()
the one you're expected to implement your main smart contract logic under it.
Also you might have noticed the op_example_command
method starting with a special prefix op
. Algorig simply translates these special named class methods into cli commands which can be directly used to manage your smart contract operations those are supposed to interact with your smart contracts.
To see all the available commands, simple type rig
or rig --help
$ rig --help
sage: rig [-h] [-v] {init,create,update,example_command} ...
positional arguments:
{init,create,update,example_command}
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
You'll notice the example_command
ready to be typed at your finger tips. Just type the command to see the expected output.
$ rig example_command myparam123
Hello myparam123
While this command does nothing rather than simply printing into console, such commands are generally supposed to perform some operations with your deployed smart contract by presumably utilizing ApplicationCall
or any other available transactions calls which we'll see later on.
As mentioned previously, get_approval_program()
method is the main entry point to your Algorand smart contract. You're here simply expected to return a PyTeal node object and Algorig will handle the rest for you.
Ok, let's write a very simple contract which only accepts application create and update transactions respectively.
# protocol.py
def get_approval_program(self):
# Implement your contract here using pyteal
return Cond(
[Txn.application_id() == Int(0), Int(1)],
[Txn.on_completion() == OnComplete.UpdateApplication, Int(1)],
)
Congrats, you just implemented your first Algorand smart contract. At this point, since you'll need to deploy your contract to Algorand blockchain, Algorig here will help us to deploy it with a built-in command create
by performing some magic behind in order to save us writing so many boilerplate code to achive the same.
First of all, let's find out how the command works before acutally running it.
$ rig create --help
usage: rig create [-h] [--app_args APP_ARGS]
optional arguments:
-h, --help show this help message and exit
--app_args APP_ARG
It seems to be legit using the command directly without any given parameteres in our case.
Let's run the command.
$ rig create
Processing transaction: A35EASTS6ANOO5HTAFIWZAAPSWJJ653ZFM74APMHE46ZIKXDNQDQ
........
Confirmed at round: 2342525
Application created with id: 1
Success! This built-in command essentially compiled your teal code, created an application create transaction automatically and sent it to Algorand blockchain throughout the Algod service by referring the algod_address
and algod_token
settings located under our config file. In our case, those settings have already the default values referring the sandbox Algod service we just started locally.
# protocol.json
{
"algod_address": "http://localhost:4001",
"algod_token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}
Another side effect of this commmand is that whenever our application initially created on the blockchain, this command will save and keep the id
of our application under our config file using the configuration key of app_id
. At this point, if you dump the contents of our config file, you'll notice the app_id
setting which was absent previously.
{
"app_id": 1
}
This setting will basically help Algorig to locate your application whenever you want to interact with it. For example, there is also another built-in command called update
which uses this setting while locating your application created previously.
So now, it's a good time to also see how to deploy any changes in the contract.
Ok, let's change a bit of our contract. This time let our contract to accept an ApplicationCall
transaction to perform some tasks on the contract. In our case, it will simply expect a sigle arg "Hello World"
to approve the transaction.
# protocol.py
def get_approval_program(self):
# Implement your contract here using pyteal
def on_call():
return Txn.application_args[0] == Bytes('Hello World')
return Cond(
[Txn.application_id() == Int(0), Int(1)],
[Txn.on_completion() == OnComplete.UpdateApplication, Int(1)],
[Txn.on_completion() == OnComplete.NoOp, on_call()],
)
Now you should be able to send your updates and override your existing contract using the command below:
$ rig update
Processing transaction: H3ZBGVX4SVPAPHPZT23Q3LHIMTOQBEY2H6SDHGARCABLWRB7JNLA
......
Confirmed at round: 5493
Application updated successfully.
Congratulations, you just developed, deployed and also updated your first smart contract to Alogrand blockchain.
So far, we've already seen how Algorig smoothens our Algorand smart contract development with some smart tooling methods. We already created our first smart contract and updated it by using the built-in commands. This time, let's now assume that we need to interact with our contract with a purpose. By our last update, our contract now expects us to send an application call transaction with a single parameter with the value of "Hello World
.
So let's try to implement such a command together to see how to develop and perform the contract operations.
# protocol.py
from algorig.app import BaseApplication
from algosdk.future import transaction
class Application(BaseApplication):
...
def op_send_greeting(self, greeting):
self.submit(transaction.ApplicationCallTxn(
sp=self.algod.suggested_params(),
on_complete=transaction.OnComplete.NoOpOC,
index=self.config.getint('app_id'),
sender=self.config['signing_address'],
app_args=[greeting)],
))
That's simple as it. We just implemented a send_greeting
command to interact with our contract. Let's first locate it.
$ rig --help
usage: rig [-h] [-v] {init,create,update,send_greeting} ...
positional arguments:
{init,create,update,send_greeting}
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
Also you may check the further details about our new command by seeing the helper instructions of the command itself.
$ rig send_greeting --help
usage: rig send_greeting [-h] greeting
positional arguments:
greeting
optional arguments:
-h, --help show this help message and exit
Since the send_greeting
command ready at your fingertips, just run it to see if it works as expected.
$ rig send_greeting "Hello World"
Processing transaction: LBF5NQOM2WWIZ4ZUPAVTPKXTK7MD5TODPW2JO65UGNSASQ4IZT2Q
........
Confirmed at round: 6054
Perfect! You just interacted with your contract deployed on the blockchain. Now let's see what happens when we send a different parameter to our contract.
$ rig send_greeting "Boooo"
algosdk.error.AlgodHTTPError: TransactionPool.Remember: transaction 4GFYWBASWI7T5GERJKO5R4GUUXUDH4LQGCO55BVUKJNXNCQQAKJA: transaction rejected by ApprovalProgram
Very cool, our last transaction simply rejected because you did not supply the desired parameter.
Most transactions need to be grouped in some way and ensured all they are executed successfully which we call atomic operations.
In Algorig grouping more than one transactions is done by BaseApplication.submit_transactions()
method. Let's see how it works:
We'll take the popular Algorand Auction Demo's setupAuctionApp
operation with some brevity:
# protocol.py
from algorig.app import BaseApplication
from algosdk.future import transaction
class Application(BaseApplication):
...
def op_auction_setup(self, nft_holder_address, nft_id: int,
nft_amount: int = 1):
sp = self.algod.suggested_params()
fund_app_txn = transaction.PaymentTxn(
sp=sp,
sender=nft_holder_address,
receiver=self.config['app_address'],
amt=1_000
)
setup_app_txn = transaction.ApplicationCallTxn(
sp=sp,
on_complete=transaction.OnComplete.NoOpOC,
sender=self.config['signing_address'],
index=self.config['app_id'],
app_args=['setup'],
)
transfer_nft_txn = transaction.AssetTransferTxn(
sp=sp,
sender=nft_holder_address,
receiver=self.config['app_address'],
index=nft_id,
amt=nft_amount,
)
self.submit_group([
fund_app_txn,
setup_app_txn,
transfer_nft_txn
])
You may end up adding your own settings to protocol.json
configuration file and refer them accordingly while implementing your contract.
For example, you may want to keep an address belonging to an actor of an operation. In such case, simply add it to protocol.json
file and refer it by using the instance field config
comes with the BaseApplication
class instance.
Let;s add one as an example.
{
"funder_address": "DNSNRCJ6WO4LCVNE6O6JHYSZQ7C725RVJMMTT4GCBOUH4VPMPMZYUVBFLU"
}
Now use it in your Application
class where needed.
# protocol.py
from algorig.app import BaseApplication
from algosdk.future import transaction
class Application(BaseApplication):
...
def op_fund(self, amt: int):
sp = self.algod.suggested_params()
fund_app_txn = transaction.PaymentTxn(
sp=sp,
sender=self.config['funder_address'],
receiver=self.config['app_address'],
amt=amt
)
self.submit(fund_app_txn)
Add settings as many as you want to keep them in the config file.
You're always free to change the underlying functionality comes with BaseApplication
. For instance, if you would like to modify the default behaviour of the clear state program, simply override the corresponding method as shown below.
class Application(BaseApplication):
...
def get_clear_state_program(self):
# default behaviour was simply returning Int(1)
return Txn.sender() ==\
'HPAMDTCS3B2BZUGP5JUNDNBKVWXYPKPC56VEYT3WYBEONPVD6HJLUHMTVQ'
...
Please checkout app.py
to review BaseApplication
class for the base implementations
Any contributions are always very welcome, just before sending any pull requests, please make sure:
- You have setup your development environment correctly
- You have added/modifed the corresponding unit tests regarding your contribution
- You have checked your code agains
flake8
style rules
Please don't hesitate to contirubute if you like and use Algorig.
To start contributing, simply checkout the repository, create a virtual env and install the project locally.
$ git checkout https://github.com/kadirpekel/algorig
$ cd algorig
$ python -m venv venv
$ source venv/bin/activate
$ pip install -e ".[dev]"
Now you're ready to rock!
Algorig utilizes pytest
as test runner, just type below the command to run all the unit tests
$ pytest
Algorig code base follows the default flake8
style guiding rules. So before sending any pull requests, please also make sure that your code passes the flake8
checks.
$ flake8 algorig
This project is in very early stages so please use it at your own risk.
Copyright (c) 2021 Kadir Pekel.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.