Tutorials
No Results
Tutorial Image

Intermediate · 30 minutes

Periodic Payment using PyTeal

This tutorial demonstrates the steps involved in creating a periodic payment delegated TEAL smart contract and deploying it using the Python SDK.

Requirements

Background

PyTeal is a language binding for Algorand Smart Contracts (ASC). PyTeal allows Algorand developers to express their smart contract logic in Python. The PyTeal library then compiles the smart contract logic to TEAL source code for the developers. In this tutorial we are building a PyTeal sample that shows how to create a periodic payment delegated signature smart contract.

1. Define Template Variables

This tutorial creates a delegated logic signature to allow a periodic payment to be withdrawn from a specific account. The from account must sign the logic and this signature can be used by the receiver to withdraw the funds. To create this template we first will create a python file that contains the parameters for the periodic payment and the logic needed to create the TEAL program. These parameters are defined as follows:

  • TMPL_RCV: address which is authorized to make withdrawals
  • TMPL_PERIOD: the time between a pair of withdrawal periods
  • TMPL_DUR: the duration of a withdrawal period
  • TMPL_AMT: the maximum number of funds allowed for a single withdrawal
  • TMPL_LEASE: string to use for the transaction lease
  • TMPL_FEE: maximum fee used by the withdrawal transaction

(Note: we didn’t specify the ending round of this periodic payment, so it can go on forever. This is a little homework for you to add that.)

Create a file called periodic_payment.py. First, we define some configuration variables that will be used in the contract.

#!/usr/bin/env python3

from pyteal import *

#template variables
tmpl_fee = Int(1000)
tmpl_period = Int(1000)
tmpl_dur = Int(1000)
tmpl_lease = Bytes("base64", "y9OJ5MRLCHQj8GqbikAUKMBI7hom+SOj8dlopNdNHXI=")
tmpl_amt = Int(200000)
tmpl_rcv = Addr("ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM")

In this example we are setting the values to allow a maximum withdrawal from the account of tmpl_amount to be sent to tmpl_rcv every tmpl_period rounds and the transaction is only valid for tmpl_dur rounds. You should replace tmpl_lease‘s second argument with a string that represents 32 random bytes using RFC 4648 base64 encoding.


Learn More
- Delegated Approval

2. Define PyTeal Conditionals

Next, we implement the logic of a periodic payment in periodic_payment.py.

We first check some common conditions that should be verified in any smart contract. These include conditions like: is this indeed a payment transaction and is the transaction fee less than a specific amount. These conditions are assigned to the periodic_pay_core variable.

    periodic_pay_core = And(Txn.type_enum() == Int(1),
                            Txn.fee() <= tmpl_fee)

periodic_pay_transfer checks that the transaction has set CloseRemainderTo to the global zero address (which means the transaction will not be closing out the balance of the address). It also sets the receiver of the transaction to the intended receiver, and sets the amount of the transaction to the intended amount. In addition, it allows the transfer to happen every tmpl_period rounds for tmpl_dur rounds by checking that the Txn.first_valid() is divisible by tmpl_period. The last condition that is checked is the lease parameter to limit replay rate. The lease parameter is a 32-byte string and, combined with the sender field, makes the transaction unique. Which means that only one of these transactions can hold this lease between the first and last valid rounds.

def periodic_payment(tmpl_fee=tmpl_fee,
                     tmpl_period=tmpl_period,
                     tmpl_dur=tmpl_dur,
                     tmpl_lease=tmpl_lease,
                     tmpl_amt=tmpl_amt,
                     tmpl_rcv=tmpl_rcv):

    periodic_pay_core = And(Txn.type_enum() == Int(1),
                            Txn.fee() <= tmpl_fee)

    periodic_pay_transfer = And(Txn.close_remainder_to() ==  Global.zero_address(),
                                Txn.receiver() == tmpl_rcv,
                                Txn.amount() == tmpl_amt,
                                Txn.first_valid() % tmpl_period == Int(0),
                                Txn.last_valid() == tmpl_dur + Txn.first_valid(),
                                Txn.lease() == tmpl_lease)

    return And(periodic_pay_core, periodic_pay_transfer)

This code will be piped into another python file that will allow us to sign the logic and use in a transaction. If you want to see the compiled TEAL output add this line to the end of the PyTeal file.

print(periodic_payment().teal())

The complete code for the periodic_payment_teal file should contain the following:

#!/usr/bin/env python3

from pyteal import *

#template variables
tmpl_fee = Int(1000)
tmpl_period = Int(1000)
tmpl_dur = Int(1000)
tmpl_lease = Bytes("base64", "y9OJ5MRLCHQj8GqbikAUKMBI7hom+SOj8dlopNdNHXI=")
tmpl_amt = Int(200000)
tmpl_rcv = Addr("ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM")

def periodic_payment(tmpl_fee=tmpl_fee,
                     tmpl_period=tmpl_period,
                     tmpl_dur=tmpl_dur,
                     tmpl_lease=tmpl_lease,
                     tmpl_amt=tmpl_amt,
                     tmpl_rcv=tmpl_rcv):

    periodic_pay_core = And(Txn.type_enum() == Int(1),
                            Txn.fee() <= tmpl_fee)

    periodic_pay_transfer = And(Txn.close_remainder_to() ==  Global.zero_address(),
                                Txn.receiver() == tmpl_rcv,
                                Txn.amount() == tmpl_amt,
                                Txn.first_valid() % tmpl_period == Int(0),
                                Txn.last_valid() == tmpl_dur + Txn.first_valid(),
                                Txn.lease() == tmpl_lease)

    return And(periodic_pay_core, periodic_pay_transfer)

print(periodic_payment().teal())

3. Deploy the Smart Contracts Constructed with PyTeal using the Python SDK

In the same directory as your periodic_payment.py create a file called periodic_payment_deploy.py. Also, make sure you have installed goal. Begin setting up your deploy python file, which will create the TEAL defined in steps 1 and 2 and compile it with the goal command line. The resulting program is then used to create a logic signature:

#!/usr/bin/env python3

from pyteal import *
import uuid, base64
from algosdk import algod, transaction, account, mnemonic
from periodic_payment import periodic_payment, tmpl_amt

#--------- compile & send transaction using Goal and Python SDK ----------

teal_source = periodic_payment().teal() 

# 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()
lsig = transaction.LogicSig(teal_bytes)


Learn More

4. Create Algod Clients

Next we create a connection to a running Algorand node by modifying periodic_payment_deploy.py with the following code.

# create algod clients
algod_address = "http://<your-algod-node>:<your-algod-port>"
algod_token = "<your-api-token>"
acl = algod.AlgodClient(algod_token, algod_address)

5. Sign the Logic Signature

We can now modify periodic_payment_deploy.py to recover an example account to sign the logic we created in steps 1 and 2.

#Recover the account that is wanting to delegate signature
passphrase = <your-25-word-mnemonic>
sk = mnemonic.to_private_key(passphrase)
addr = account.address_from_private_key(sk)
print("Dispense at least 201000 microAlgo to {}".format(addr))
input("Make sure you did that. Press Enter to continue...")

# sign the logic signature with an account sk
lsig.sign(sk)

This will also pause the example to allow you to use the dispenser to make sure the account is funded. This account represents the account that will delegate authority for others to remove funds every tmpl_period rounds. This sign operation signs the logic, which alows delegating our signature under the constraints of the logic.


Learn More
- Add Funds using Dispenser
- Logic Signatures

6. Define and Create the Transaction

We can now modify periodic_payment_deploy.py to create a transaction using this delegated signature to withdraw funds from the account that signed the logic.

# get suggested parameters
params = acl.suggested_params()
gen = params["genesisID"]
gh = params["genesishashb64"]
startRound = params["lastRound"] - (params["lastRound"] % 1000)
endRound = startRound + 1000
fee = 1000
amount = 200000
receiver = "ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM"
lease = base64.b64decode("y9OJ5MRLCHQj8GqbikAUKMBI7hom+SOj8dlopNdNHXI=")

# create a transaction
txn = transaction.PaymentTxn(addr, fee, startRound, endRound, gh, receiver, amount, flat_fee=True, lease=lease)

7. Create LogicSig Transaction

This transaction can now be created with the logic signature instantiated earlier. This can be done with the following code added to periodic_payment_deploy.py.

# Create the LogicSigTransaction with contract account LogicSig
lstx = transaction.LogicSigTransaction(txn, lsig)


Learn More

8. Send LogicSig Transaction to the Network

This transaction can now be sent to the network.

# send raw LogicSigTransaction to network
txid = acl.send_transaction(lstx)
print("Transaction ID: " + txid )

9. Complete PyTeal Example

#!/usr/bin/env python3

from pyteal import *

#template variables
tmpl_fee = Int(1000)
tmpl_period = Int(1000)
tmpl_dur = Int(1000)
tmpl_x = Bytes("base64", <32-byte string>)
tmpl_amt = Int(2000)
tmpl_rcv = Addr(<receiver address>)
tmpl_timeout = Int(30000)

periodic_pay_core = And(Txn.type_enum() == Int(1),
                        Txn.fee() < tmpl_fee)

periodic_pay_transfer = And(Txn.close_remainder_to() ==  Global.zero_address(),
                            Txn.receiver() == tmpl_rcv,
                            Txn.amount() == tmpl_amt,
                            Txn.first_valid() % tmpl_period == Int(0),
                            Txn.last_valid() == tmpl_dur + Txn.first_valid(),
                            Txn.lease() == tmpl_x)

periodic_pay_close = And(Txn.close_remainder_to() == tmpl_rcv,
                         Txn.receiver() == Global.zero_address(),
                         Txn.first_valid() > tmpl_timeout,
                         Txn.amount() == Int(0))

periodic_pay_escrow = And(periodic_pay_core, Or(periodic_pay_transfer, periodic_pay_close))
print(periodic_pay_escrow.teal())

10. Complete Python SDK Example

Below is the complete periodic_payment_deploy.py file.

#!/usr/bin/env python3

from pyteal import *
import uuid, base64
from algosdk import algod, transaction, account, mnemonic
from periodic_payment import periodic_payment, tmpl_amt

#--------- compile & send transaction using Goal and Python SDK ----------

teal_source = periodic_payment().teal() 

# 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()
lsig = transaction.LogicSig(teal_bytes)

# create algod clients
algod_address = "http://<your-algod-node>:<your-algod-port>"
algod_token = "<your-api-token>"
acl = algod.AlgodClient(algod_token, algod_address)

#Recover the account that is wanting to delegate signature
passphrase = "patrol crawl rule faculty enemy sick reveal embody trumpet win shy zero ill draw swim excuse tongue under exact baby moral kite spring absent double"
sk = mnemonic.to_private_key(passphrase)
addr = account.address_from_private_key(sk)
print("Dispense at least 201000 microAlgo to {}".format(addr))
input("Make sure you did that. Press Enter to continue...")

# sign the logic signature with an account sk
lsig.sign(sk)

# get suggested parameters
params = acl.suggested_params()
gen = params["genesisID"]
gh = params["genesishashb64"]
startRound = params["lastRound"] - (params["lastRound"] % 1000)
endRound = startRound + 1000
fee = 1000
amount = 200000
receiver = "ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM"
lease = base64.b64decode("y9OJ5MRLCHQj8GqbikAUKMBI7hom+SOj8dlopNdNHXI=")

# create a transaction
txn = transaction.PaymentTxn(addr, fee, startRound, endRound, gh, receiver, amount, flat_fee=True, lease=lease)

# Create the LogicSigTransaction with contract account LogicSig
lstx = transaction.LogicSigTransaction(txn, lsig)

# send raw LogicSigTransaction to network
txid = acl.send_transaction(lstx)
print("Transaction ID: " + txid )

# except Exception as e:
#     print(e)    

pyteal

smart contracts

periodic payment

delegated signature

v1 api

teal

April 13, 2020