What you will learn
- How to write and deploy smart contract on Algorand blockchain.
- Understanding Pyteal/Teal language
- Nesting In Pyteal
Tools/Requirement
- Run a node or use Docker sandbox or an endpoint/API service if you cannot run a node on your machine. It is advisable that you run a goal-CLI which comes with running a node. Follow the node installation guide.
- An editor. If you would like me suggest one, I'd recommend Pycharm.
- Python application.
- Algorand python SDK
- Algodesk
Join the developer forum to learn more.
Overdraft/Line Of Credit
Things to note
I have imported a few modules from my past tutorial available on github. To keep it short and simple, I will concentrate more on the pyteal codes.
The source code is contained in overdraft.py file. Create one in your editor. Copy and paste the code below.
from pyteal import *
from algosdk.future import transaction
from connection import algo_client, params
from assetInfo import transferasset, asset_manage_authorized, assetinformation, jointAuthorization,
accounts_sk
from algosdk.transaction import AssetTransferTxn, encoding
from printAssetHolding import print_asset_holding
from waitForConfirmation import wait_for_confirmation
from algosdk.future.transaction import AssetTransferTxn, transaction
import base64
# Set the global variables
tmpl_fee = Int(1000)
tmpl_timeout = Int(6)
tmpl_period = Int(5)
min_algo_bal = Int(1000)
min_Freth_bal = Int(5000)
int_rate = Div(Int(8), Int(100)) # Interest on OD
flat_charge = Int(100) # Flat charges for overdrawing in custom asset
max_gold_client = Int(300000)
max_ruby_client = Int(70000)
stake = False
assetId = 9604118
noReEntrancy = Bytes("base64", "19faf42c")
tmpl_OD = Sha256
sender = 'N6SJLDQXCXZ7IJMLM5VVEAWJ36JUJSDQVNQFA2I3BKFOWE3QLKKJGATPJ4'
userDict = {
"user100": "...",
"user101": "G26HOW2LWOV2PRE3WN7S7JDOWE32GR4SRZ34MLANCJPKVLIX4YNBNBT2XA",
"user102": "..."
}
# Check if user can enjoy this service i.e is a potential client list and has required amount of asset staked.
def isAClient(addr):
global stake
if addr in userDict:
stake = True
return int(1)
else:
stake = stake
return int(0)
# Function requesting overdraft
def requestOdrft(addr, amt):
bal_In_Algo = algo_client.account_info(addr)['amount-without-pending-rewards'] # To check if user is
eligible, scrutinize their balance in
bal_In_Freth = algo_client.account_info(addr)['assets'][0]['amount'] # Both in Freth and Algo so the
transaction will not fail due to low balance.
alc_bal_Freth = Int(bal_In_Freth) # Balance of Drawer in Freth
alc_bal_ALGO = Int(bal_In_Algo) # Balance of Drawer in ALGO
is_pot_client = isAClient(addr) # Is this user contained in the userList?
isRubyUser = Le(Int(amt), max_ruby_client) # A Ruby user can obtain loan not greater than 70000 Freth
isGoldUser = And(isRubyUser, Gt(Int(amt), max_ruby_client), Le(Int(amt), max_gold_client)) # A Gold
user can overdraw up to 300,000 Freth. This is the maximum we can allow subscribers to overdraw.
# Check that the account balance is less than requested amount
# Drawer must be holding at least 20,000 worth of Freth at this time.
# Balance in ALGO at this time must not go below 2000.
# To enable for transaction fee and in this contract, balance is considered zero when
# goes below 2000
# compute total loan to disburse less interest and other charges if any
net_loan = Minus(Int(amt), Add(Mul(int_rate, Int(amt)), flat_charge))
# Check transaction fields are correct
# Check the status of user's account.
# User must be holding an amount of Freth as staking at this time.
# Balance in ALGO at this time must not go below amount for transaction fee.
# To enable for transaction fee and in this contract, balance is considered zero when
# goes below 2000
# Conditions for approving transaction
ovdrft_trxn_cond = And( # including
And( # including
And( # And inclusive of..
Txn.type_enum() == Int(4), # Transaction type is asset transfer
Le(Txn.fee(), tmpl_fee), # Transaction fee cannot exceed stipulated amount to guard against any
attack intending to overstate transaction fee.
Eq(Txn.amount(), net_loan), # Amount requested as overdraft cannot exceed balance less interest
plus other charges
Eq(Txn.lease(), noReEntrancy), # Guard for transaction parameters
Eq(Txn.xfer_asset(), Int(assetId)), # Pointing to specific asset we have access to.
),
Eq(Int(is_pot_client), Int(1)), # This caller must be an already subscriber. ie A user in our list
Le(alc_bal_ALGO, min_algo_bal), # and balances of user in ALGO & Freth(custom asset) are within
our requirements.
Lt(alc_bal_Freth, min_Freth_bal)
),
Or(
Eq(isRubyUser, Int(1)), # check that requested loan is within specified windows
Eq(isGoldUser, Int(1)) # The caller should be either a Ruby user or a Gold user.
),
And(
Le(Txn.last_valid(), Int(6)), # Transaction valid round should not exceed set time, else panic.
Eq(Txn.asset_close_to(), Addr(asset_manage_authorized)), # Asset balance is closed to a contract
account so no ambiguous interpretation for an asset can pass.
Eq(Txn.asset_receiver(), Addr(addr)) # Receiver cannot be any other than caller.
)
)
opcodes = ovdrft_trxn_cond.teal() # To create teal
return opcodes
Converting the pyteal codes to teal generates the following opcodes.
txn TypeEnum
int 4
==
txn Fee
int 1000
<=
&&
txn Amount
int 20000
int 8
int 100
/
int 20000
*
int 100
+
-
==
&&
txn Lease
byte base64 19faf42c
==
&&
txn XferAsset
int 10897078
==
&&
int 0
int 1
==
&&
int 99999000
int 1000
<=
&&
int 1000
int 5000
<
&&
int 20000
int 70000
<=
int 1
==
int 20000
int 70000
<=
int 20000
int 70000
>
&&
int 20000
int 300000
<=
&&
int 1
==
||
&&
txn LastValid
int 6
<=
txn AssetCloseTo
addr NFZ3L6E4MLIOQ5RWYYRJLHCELIA4Q2HA2U654DTKHMBCUQASCDVLLBEXAQ
==
&&
txn AssetReceiver
addr G26HOW2LWOV2PRE3WN7S7JDOWE32GR4SRZ34MLANCJPKVLIX4YNBNBT2XA
==
&&
&&
Importing from the boilerplate:
Now that the basic contract is implemented we need to build an example of instantiating the contract. We will do this by creating a python file named ‘odrft_deploy.py’. We first set the template variables and then call the ovdrft function we created in the previous steps. Next, we use python to call out to the command line to compile the TEAL program by saving the TEAL code to a file and using the execute function. Finally we read the file containing the compiled TEAL bytes back into a local variable.
Note: I made some changes. Swap the htlc file with ovdrft
#!/usr/bin/env python3
import uuid, base64
from algosdk import algod, transaction, account, mnemonic
from overdraft import requestOdrft
#--------- compile & send transaction using Goal and Python SDK ----------
tmpl_hash_fn = Sha256
tmpl_hash_img = Bytes("base64", "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=")
teal_source =
requestOdrft("G26HOW2LWOV2PRE3WN7S7JDOWE32GR4SRZ34MLANCJPKVLIX4YNBNBT2XA", 20000)
#print( teal_source )
# compile teal
teal_file = str(uuid.uuid4()) + ".teal"
with open(teal_file, "w+") as f:
f.write(teal_source)
lsig_fname = str(uuid.uuid4()) + ".tealc"
stdout, stderr = execute(["goal", "clerk", "compile", "-o", lsig_fname,
teal_file])
if stderr != "":
print(stderr)
raise
elif len(stdout) < 59:
print("error in compile teal")
raise
with open(lsig_fname, "rb") as f:
teal_bytes = f.read()
Additional information
Import all utilities from the pyteal module.
from python import *
Every pyteal expression must evaluate to a binary (either a TealType.bytes or TealType.uint64) expression.
Both the right and left operands must be of the same type.
Int(2) == Int(4). This is right:
Bytes(address) == Bytes(address). This is right:
Int(2) == Bytes(address) . This is wrong
Comparison between the operands must result in either True or False equals to Int(1) or Int(0)
Pyteal supports operator overloading for example:
Le(Int(3), Int(2)) simply says: check that 3 of type TealType.uint64 equate with 2 of the same type. More information is found in Pyteal documentation.
Create the Logic Signature And Sign A Transaction With It.
Get the program and parameters and use them to create an lsig
For the contract account to be used in a transaction
In this example 'hype sense black soap loop lucky king number'
hashed with sha256 will produce our a byte hash
# This creates a lock for the ovdrft contract args = [ "hype sense black soap loop lucky king number".encode() ] # Add the program bytes and args to a LogicSig object lsig = transaction.LogicSig(teal_bytes, args) print( lsig.address() )
# Transfer asset
def transferAssets(rec, amount):
params.fee = 1000
params.flat_fee = True
txn = AssetTransferTxn(
sender=sender,
sp=params,
receiver=rec,
amt=amount,
index=assetId
)
lstx = transaction.LogicSigTransaction(txn, lgsig)
txns = [lstx]
transaction.write_to_file(txns, "ovrdrft.stxn", False)
# Submit transaction to the network
tx_id = algo_client.send_transaction(lstx, headers={'content-type': 'application/x-binary'})
message = "Transaction was signed with: {}.".format(tx_id)
wait = wait_for_confirmation(tx_id)
isSuccessful = bool(wait is not None)
print(isSuccessful)
# Now check the asset holding for receiver.
# This should now show a holding with the sent balance.
assetHolding = print_asset_holding(rec, assetId)
logging.info(
"...##Asset Transfer... \nReceiving account: {}.\nMessage: {}\nOperation: {}\nHoldings: {}\n".format(
rec,
message,
transferAssets.__name__,
assetHolding
))
Congratulations @bob-elr! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s) :
You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word
STOP