A small project demonstrating the capabilities of blockchain technology.
Create a new fork of this project on you Github repository.
- Click on the Code button (green button with a dropdown arrow on the top right).
- Select the Codespaces tab.
- Click on the Create codespace on main button.
- Wait for the setup to complete before proceeding to the next step.
It will prompt you a message to install the "Python extension for Visual Studio Code", I would recommand to accept to avoid some warnings about missing dependencies
$ algokit doctorYou should have something similar to the following:
timestamp: 2025-01-07T12:12:41+00:00
AlgoKit: 2.5.1
AlgoKit Python: 3.12.1 (main, Dec 12 2024, 22:30:56) [GCC 9.4.0] (location: /usr/local/py-utils/venvs/algokit)
OS: Linux-6.5.0-1025-azure-x86_64-with-glibc2.31
docker: 27.3.1-1
docker compose: 2.31.0
git: 2.47.1
python: 3.12.1 (location: /home/codespace/.python/current/bin/python)
python3: 3.12.1 (location: /home/codespace/.python/current/bin/python3)
pipx: 1.7.1
poetry: Command not found!
Poetry is required for some Python-based templates;
install via `algokit project bootstrap` within project directory, or via:
https://python-poetry.org/docs/#installation
node: 20.18.1
npm: 10.8.2$ gh codespace portsPort 4001/4002/5173/8980 should be open:
LABEL PORT VISIBILITY
4001 public
4002 public
5173 public
5443 private
8980 public AlgoKit compromises of a number of components that make it the one-stop shop tool for developers building on the Algorand network.
flowchart LR
Developer[Developer_]
A[High-level language__<br>e.g., Python]
B[TEAL Template_]
C[TEAL]
D[Byte code_]
E[Deployed Application]
%% Development Stage
A -->|Transpile__| B
B -->|Substitute__| C
C -->|Compile__| D
%% Deployment Stage
D -->|Deploy__| E
%% Runtime Stage
E -->|Mutate state__| E
%% Developer Interactions
Developer -->|Write contract__| A
Developer -->|Provide params__| B
Developer -->|Verify_| C
Developer -->|Call contract<br> using <br>ABI methods__<br>| E
Use the command algokit explore
It will open a new page with the Algorand Explorer "lora".
Anything you do on your local environment will be displayed here.
You can also see in real time what's going on the blockchain by switching to MainNet.
You can use the workshop.py file already here OR do the following:
For this workshop you will need to import:
To make this less cumbersome, you can import methods from the custom utils file. We can also import os, to avoid having to type some commands in the terminal.
import os
import algosdk
import algokit_utils as au
import algokit_utils.transactions.transaction_composer as att
from utils import (
account_creation,
display_info,
)We will stay in localnet for this introduction, feel free to modify the
utils.pyfile to change network.
We need to setup the Indexer & Algod API that are used to interact with the blockchain.
algorand = au.AlgorandClient.from_environment()
algod_client = client_configuration()
indexer_client = indexer_configuration()You can print these 2 values to check the network configuration is working.
print(algod_client.block_info(0))
print(indexer_client.health())We usually use Alice, Bob and Charly as users to illustrate blockchain documentation. We will give some Algo to Alice. Bob & Charly will get 1000 by default.
alice = account_creation(algorand, "ALICE", au.AlgoAmount(algo=10000))
bob = account_creation(algorand, "BOB", au.AlgoAmount(algo=100))
charly = account_creation(algorand, "CHARLY", au.AlgoAmount(algo=100)) result = algorand.send.asset_create(
au.AssetCreateParams(
sender=alice.address,
signer=alice.signer,
total=15,
decimals=0,
default_frozen=False,
unit_name="PY-CL-FD", # 8 Max
asset_name="Proof of Attendance Py-Clermont",
url="https://pyclermont.org/",
note="Hello Clermont",
)
)
asset_id = result.confirmation["asset-index"]Following line are still true, we just prefer to use the above version
Create an Asset Configuration Transaction
create_asa_txn = algosdk.transaction.AssetCreateTxn(
sender=alice.address,
sp=algod_client.suggested_params(),
total=15,
decimals=0,
default_frozen=False,
unit_name="POA", # 8 Max
asset_name="Proof of Attendance",
url="https://github.com/SudoWeezy/introduction",
note="Hello Github",
)
create_asa_signed = create_asa.sign(alice.private_key)On Algorand you can create a token without a smart-contract by using Algorand Standard Asset
sequenceDiagram
participant Alice
participant Bob
participant Algod
participant Indexer
Alice->>Algod: AssetCreate
Algod->>Indexer: Store creation of x Token from Alice
Algod-->>Alice: Send x Token
create_asa_tx_id = algod_client.send_transaction(create_asa_signed) res = algosdk.transaction.wait_for_confirmation(algod_client, create_asa_tx_id, 4)
asset_id = res["asset-index"] composer = algorand.new_group()
composer.add_asset_opt_in(
au.AssetOptInParams(
sender=bob.address,
signer=bob.signer,
asset_id=asset_id
)
)
composer.add_payment(
au.PaymentParams(
sender=bob.address,
signer=bob.signer,
receiver=alice.address,
amount=au.AlgoAmount(algo=10),
)
)
composer.add_asset_transfer(
au.AssetTransferParams(
sender=alice.address,
signer=alice.signer,
receiver=bob.address,
amount=1,
asset_id=asset_id,
)
)
result = composer.send()This simply means that transactions that are part of the transfer either all succeed or all fail.
We will bind 3 transactions in the atomic transfer:
- Bob allow the asset created by Alice to enter his account -> Opt-in
- Bob send the money to Alice -> Payment
- Alice send the token to Bob -> Asset Transfer
sequenceDiagram
participant Alice
participant Bob
participant Algod
participant Indexer
rect rgba(200, 200, 255, 0.2)
Note over Bob,Algod: Atomic transfer
Bob->>Algod: Opt-in
Algod->>Indexer: Store "Allow Bob to receive Token"
Algod-->>Bob: Send 0 Token
Bob->>Algod: Algo Payment
Algod->>Indexer: Store "Allow Payment from Bob to Alice"
Bob-->>Alice: Send Algo
Alice->>Algod: Asset transfer
Algod->>Indexer: Store "Alice Transfer ASA to Bob"
Alice-->>Bob: Send Y ASA
end
Now that you have some experience in building transactions. Even if we are using blockchain, we still need a backend to trigger each transaction. This is not really efficient. Let's deploy a smart contract on the blockchain so that everyone can interact with it directly.
We will define 5 methods:
create_application(Initialize the smart contract with the asset_id and the unitary_price)- [Optional]
set_price(In case Alice changes her mind and wants to change the price) opt_in_to_asset(Like Any account, to hold an asset, the smart contract needs to opt-in)buy(Allows someone to buy an asset from the smart contract)delete_application(When Alice don't wants to send her token anymore and get the money back)
You can use the app.py file already here OR do the following:
from algopy import (
Asset, # On Algorand, assets are native objects rather than smart contracts
Global, # Global is used to access global variables from the network
Txn, # Txn is used access information about the current transaction
UInt64, # By default, all numbers in the AVM are 64-bit unsigned integers
arc4, # ARC4 defines the Algorand ABI for method calling and type encoding
gtxn, # gtxn is used to read transaction within the same atomic group
itxn, # itxn is used to send transactions from within a smart contract
)class DigitalMarketplace(arc4.ARC4Contract):
asset_id: UInt64 # We want to store the ID for the asset we are selling
unitary_price: UInt64 # We want to store the price for the asset we are selling
@arc4.abimethod(allow_actions=["NoOp"],create="require")
# There are certain actions that a contract call can do
# Some examples are UpdateApplication, DeleteApplication, and NoOp
# NoOp is a call that does nothing special after it is exected
# Require that this method is only callable when creating the app
def create_application(self, asset_id: Asset, unitary_price: UInt64) -> None:
self.asset_id = asset_id.id # The ID of the asset we're selling
self.unitary_price = unitary_price # The initial sale price
@arc4.abimethod
def set_price(self, unitary_price: UInt64) -> None:
assert Txn.sender == Global.creator_address # We don't want anyone to be able to modify the price, only the app creator can
self.unitary_price = unitary_price
# Before any account can receive an asset, it must opt-in to it
# This method enables the application to opt-in to the asset
@arc4.abimethod
def opt_in_to_asset(self, mbr_pay: gtxn.PaymentTransaction) -> None: # Need to send a payment to cover data usage
# We want to make sure that the application address is not already opted in
assert not Global.current_application_address.is_opted_in(Asset(self.asset_id))
assert mbr_pay.receiver == Global.current_application_address
# Every accounts has an MBR of 0.1 ALGO (Global.min_balance)
# Opting into an asset increases the MBR by 0.1 ALGO (Global.asset_opt_in_min_balance)
assert mbr_pay.amount == Global.min_balance + Global.asset_opt_in_min_balance
itxn.AssetTransfer(
xfer_asset=self.asset_id,
asset_receiver=Global.current_application_address,
asset_amount=0,
).submit()
@arc4.abimethod
def buy(self, buyer_txn: gtxn.PaymentTransaction, quantity: UInt64) -> None:
# To buy assets, a payment must be sent
# The quantity of assets to buy
# We need to verify that the payment is being sent to the application
# and is enough to cover the cost of the asset
assert buyer_txn.sender == Txn.sender
assert buyer_txn.receiver == Global.current_application_address
assert buyer_txn.amount == self.unitary_price * quantity
# Once we've verified the payment, we can transfer the asset
itxn.AssetTransfer(
xfer_asset=self.asset_id,
asset_receiver=Txn.sender,
asset_amount=quantity,
).submit()
@arc4.abimethod(allow_actions=["DeleteApplication"])
def delete_application(self) -> None: # Only allow the creator to delete the application
assert Txn.sender == Global.creator_address # Send all the unsold assets to the creator
itxn.AssetTransfer(
xfer_asset=self.asset_id,
asset_receiver=Global.creator_address,
asset_amount=0,
# Close the asset to unlock the 0.1 ALGO that was locked in opt_in_to_asset
asset_close_to=Global.creator_address,
).submit()
itxn.Payment(receiver=Global.creator_address, amount=0, close_remainder_to=Global.creator_address).submit()
# Get back ALL the ALGO in the creatoraccountThe following command will compile the smart contract and output the ABI and the TEAL File.
Teal is the language of the Algorand Virtual Machine
os.system("algokit compile py --out-dir ./app app.py")Similar to an API. Now that we have the ABI and the Teal file, we will generate the client to interact with the application.
os.system("algokit generate client app/DigitalMarketplace.arc32.json --output client.py") import client as cl
factory = algorand.client.get_typed_app_factory(
cl.DigitalMarketplaceFactory, default_sender=alice.address
) result, _ = factory.send.create.create_application(
cl.CreateApplicationArgs(
asset_id=asset_id, unitary_price=price.micro_algo
)
)
app_id = result.app_id
ac = factory.get_app_client_by_id(app_id, default_sender=alice.address)sequenceDiagram
participant Alice
participant Charly
participant SmartContract
participant Algod
participant Indexer
Alice->>Algod: CreateApp
Algod->>Indexer: Store creation of DigitalMarketPlace
Algod-->>SmartContract: Create
sp = algorand.get_suggested_params()
mbr_pay_txn = algorand.create_transaction.payment(
au.PaymentParams(
sender=alice.address,
receiver=ac.app_address,
amount=au.AlgoAmount(algo=0.2),
extra_fee=au.AlgoAmount(micro_algo=sp.min_fee)
)
)
ac.send.opt_in_to_asset(
cl.OptInToAssetArgs(
mbr_pay=att.TransactionWithSigner(mbr_pay_txn, alice.signer)
),
au.CommonAppCallParams(
asset_references=[asset_id]
)
)
print(f"App can now Hold ASA-ID= {algod_client.account_info(app_client.app_address)['assets']}")
print("Alice send ASAs to the App")
algorand.send.asset_transfer(
au.AssetTransferParams(
sender=alice.address,
signer=alice.signer,
amount=10,
receiver=ac.app_address,
asset_id=asset_id
)
)
print(f"Hold ASA-ID= {algod_client.account_info(app_client.app_address)['assets']}")To simplify, from now on, I will replace Algod, Indexer and Smart_Contract by "Smart_Contract".
sequenceDiagram
participant Alice
participant Charly
participant SmartContract
rect rgba(200, 200, 255, 0.2)
Note over Alice,SmartContract: Atomic transfer
Alice->>SmartContract: Send Min_Balance Payment
SmartContract->>SmartContract: Opt-in Token
end
Alice->>SmartContract: Send Token
amt_to_buy = 2
print(f"Charly buy {amt_to_buy} token")
composer = algorand.new_group()
composer.add_asset_opt_in(
au.AssetOptInParams(
sender=charly.address,
signer=charly.signer,
asset_id=asset_id
)
)
buyer_payment_txn = algorand.create_transaction.payment(
au.PaymentParams(
sender=charly.address,
receiver=ac.app_address,
amount=au.AlgoAmount(
micro_algo=amt_to_buy*ac.state.global_state.unitary_price
),
extra_fee=au.AlgoAmount(micro_algo=sp.min_fee)
)
)
atc = au.AtomicTransactionComposer()
composer.add_app_call_method_call(
ac.params.buy(
cl.BuyArgs(
buyer_txn=att.TransactionWithSigner(
txn=buyer_payment_txn,
signer=charly.signer
),
quantity=2
),
au.CommonAppCallParams(
sender=charly.address,
signer=charly.signer,
# Inform the AVM that the transaction uses this asset
asset_references=[asset_id],
)
)
)
composer.send()sequenceDiagram
participant Alice
participant Charly
participant SmartContract
rect rgba(200, 200, 255, 0.2)
Note over Charly,SmartContract: Atomic transfer
Charly->>Charly: Opt-in Token
Charly->>SmartContract: Buy
SmartContract-->>Charly: Send ASA
end
Now that some user bought Tokens, Alice wants to delete the Smart-Contract to get Algo and tokens back.
print("Alice delete the app and get ASA and Algo back")
ac.send.delete.delete_application(
au.CommonAppCallParams(
# Tell the AVM that the transaction involves this asset
asset_references=[asset_id],
extra_fee=au.AlgoAmount(micro_algo=3*sp.min_fee)
)
)sequenceDiagram
participant Alice
participant Charly
participant SmartContract
rect rgba(200, 200, 255, 0.2)
Note over Alice,SmartContract: Atomic transfer
Alice->>SmartContract: Delete
SmartContract-->>Alice: Send EVERY ASA
SmartContract->>Alice: Pay
end
- If you have the following error:
algosdk.error.AlgodHTTPError: HTTP Error 502: Bad Gateway - Or to reset your local network
Run the
algokit localnet reset command.
- Full code for Workshop.py
- Full code for app.py
- Install AlgoKit
- Quick Start Tutorial
- Documentation
- Transactions
You can find here The code to deploy the app to testnet.
Here our main goal is to make you interact with the testnet network.
Try to use Chrome based browser You might need to go to
chrome://flagsand enable Experimental Web Platform features
- Go to Testnet Lora Explorer User Interface
- Click on
Connect Wallet - Select
Connect Lute - Click on
Add an Account - Click on
Hot Account - Here You can either import what's in your .env file (if you used the script provided earlier) or create a new account.
- You should now be able to connect to Lora
- You can get some free tesnetAlgo by going to fund (1 Algo is enough for what we will do on the next part)
- Go to Txn Wizard
- Click on
Add Transaction - Select
Asset opt-in - Enter 734076981 in the
Asset IDinput box - Click on
AddthenSend - Now go to application/734077678
- Click on
buy - Enter the 1 in the
Valueinput Box thenAdd - Click on
+ Build the transaction passed in as an argument - Enter
ABQBXTDWUY56T2KUUTBAGYTH4W6G2PFHKMFZ6JOELM3MEBAAUI65ZJPGVMin theReceiverCheck box (it's the address of the smart contract) - Enter
0.1in Amount to pay - Uncheck
Set fee automaticallyand enter0.002( there are 2 Transaction, the Payment and the Buy) thenAdd - Click on
Populate Ressource - Click on
Send - Now if you click on your Account on the top Left and on
View, you will see that you haveProof of Attendance Py-Clermontin your Assets held.
To be able to use the ABI methods, the ARC-32 json file has been added here here