Tutorials
No Results
Tutorial Image

Intermediate · 30 minutes

Hash Time Lock Contract with PyTeal

This tutorial is intended to help you call a Hash Time Locked Contract using Python. This example uses PyTeal to reproduce the exact TEAL on the standard HTLC Template.

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 Hash Time Lock smart contract. This will be done in two python files, once which builds the TEAL contract and another python file showing how the contract can be deployed. The Hash Time Lock Contract is just one of the templates and is described in the reference documentation. Hash Time Lock Contracts are contract accounts that can disburse funds when the correct hash preimage (“password”) is passed as an argument. If the funds are not claimed with the password after a certain period of time, the original owner can reclaim them.

1. Define Template Variables

This tutorial creates a Hash Time Lock Contract using Pyteal. We also have a tutorial on doing the same operation but uses injected parameters. Using Pyteal gives you the opportunity to alter the logic to your needs. To create this template we first will create a python file that contains the parameters for the htlc and the logic needed to create the TEAL program. These parameters are defined as follows:

  • TMPL_RCV: the address to send funds to when the preimage is supplied
  • TMPL_HASHFN: the specific hash function (sha256 or keccak256) to use
  • TMPL_HASHIMG: the image of the hash function for which knowing the preimage under TMPL_HASHFN will release the funds
  • TMPL_TIMEOUT: the round after which funds may be closed out to TMPL_OWN
  • TMPL_OWN: the address to refund funds to on timeout
  • TMPL_FEE: maximum fee of any transactions approved by this contract

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

from pyteal import *

""" HTLC
"""
hash_function = "Sha256"
hash_img = Bytes("base64", "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=")
timeout =Int(5555)
max_fee=Int(2000)
tmpl_rcv = Addr("ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM")
owner=Addr("GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A")

These variables are used for testing the TEAL creation and will be replaced when the HTLC is created in a transaction.


Learn More
- Hash Time Lock Contract Template with Python

2. Define the Contract Logic

Add a function to the htlc.py file that implements the logic of contract. Additionally, you can add a function to print out the created TEAL for testing purposes.

def htlc(tmpl_hash_img=hash_img,
         tmpl_timeout=timeout,
         tmpl_owner=owner,
         tmpl_max_fee=max_fee,
         tmpl_hash_fn=Sha256,
         tmpl_rcv=tmpl_rcv):

    fee_cond = Txn.fee() <= tmpl_max_fee
    type_cond = Txn.type_enum() == Int(1)
    amount_cond = Txn.amount() == Int(0)
    r_cond = Txn.receiver() == Global.zero_address()
    recv_cond = ( Txn.close_remainder_to() == tmpl_rcv ).And(
                tmpl_hash_fn(Arg(0)) == tmpl_hash_img)
    esc_cond = (Txn.close_remainder_to()  == tmpl_owner).And(
                Txn.first_valid() > tmpl_timeout)

    htlc_core = fee_cond.And(type_cond).And(r_cond).And(amount_cond).And(recv_cond.Or(esc_cond))   
    return htlc_core

print(htlc().teal())

The fee_cond verifies that the fee is reasonable. The type_cond verifies that this is a payment transaction. The r_cond verifies that the receiver is set to the Zero Address as we are closing out the htlc to the receiver using the close out to parameter not the receiver parameter. The recv_cond verifies that the close out to parameter is used to send all funds in the account to the template receiver and that the hash image is also correct. The hash image is passed in as a parameter to the contract with the transaction. The esc_cond handles the contract timeout clause and verifies the close out to parmeter is the template owner and that the contract is actually expired.

You should be able to run this and get the following output.

$ python3 htlc.py
txn Fee
int 2000
<=
txn TypeEnum
int 1
==
&&
txn Receiver
global ZeroAddress
==
&&
txn Amount
int 0
==
&&
txn CloseRemainderTo
addr ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM
==
arg 0
sha256
byte base64 QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=
==
&&
txn CloseRemainderTo
addr GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A
==
txn FirstValid
int 5555
>
&&
||
&&

3. Use Python to Compile the TEAL

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 ‘htlc_deploy.py’. We first set the template variables and then call the htlc 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.

#!/usr/bin/env python3

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

#--------- compile & send transaction using Goal and Python SDK ----------
tmpl_hash_fn = Sha256
tmpl_hash_img = Bytes("base64", "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=")
tmpl_timeout =Int(5000000)
tmpl_max_fee=Int(2000)
tmpl_rcv = Addr("42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE")
tmpl_owner=Addr("726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM")
teal_source = htlc(tmpl_hash_img, tmpl_timeout, tmpl_owner, tmpl_max_fee, tmpl_hash_fn, tmpl_rcv).teal() 
#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()

4. Create the Logic Signature

We can now create a logic signature using the compiled TEAL. Add the following code to the htlc_deploy.py file.

# 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 'hero wisdom green split loop element vote belt'
# hashed with sha256 will produce our image hash
# This is the passcode for the HTLC 
args = [
    "hero wisdom green split loop element vote belt".encode()
]
# Add the program bytes and args to a LogicSig object
lsig = transaction.LogicSig(teal_bytes, args)
print( lsig.address() )

The logic signature is for a contract account, which means that for a valid transcation to be issued from the contract the hash of the program needs to be the sender of the transction and the logic will determine if the transaction is valid. Also the address should be funded at this point. You will also notice we are passing the passcode argument when we create the logic signature.


Learn More
- Logic Signatures
- Contract Account

5. Use the Logic Signature in a Transaction

Next we can modify htlc_deploy to create an example transaction and submit it to the network. You will see that we see the receiver to the Zero Address (AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ). We create a payment transaction where the sender is the address of the contract and then use the LogicSigTransaction function to associate the payment transaction with the logic signature. We then submit the transaction to the network. This will close out the contract address, so if you run the example more than once, the contract account will need to be funded again.

# Create an algod client
algod_token = "<your_api_token>"
algod_address = "http://<your-algod-node>:<your-algod-port>"
client = algod.AlgodClient(algod_token, algod_address)
# Get suggested parameters from the network
tx_params = client.suggested_params()

# Before submitting this transaction, you must first make sure the escrow 
# account is sufficiently funded. Once it is funded, create and submit
# transactions from the escrow account like the transaction below.
# Transaction data
tx_data = {
    "sender": lsig.address(),
    "receiver": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
    "amt": 0,
    "close_remainder_to": "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE",
    "fee": 1000,
    "flat_fee": True,
    "first": tx_params.get('lastRound'),
    "last": tx_params.get('lastRound') + 1000,
    "gen": tx_params.get('genesisID'),
    "gh": tx_params.get('genesishashb64')
}
# Instantiate a payment transaction type
txn = transaction.PaymentTxn(**tx_data)
# Instantiate a LogicSigTransaction with the payment txn and the logicsig
logicsig_txn = transaction.LogicSigTransaction(txn, lsig)
# Send the transaction to the network.
txid = client.send_transaction(logicsig_txn, headers={'content-type': 'application/x-binary'})
print("Transaction ID: {}".format(txid))

6. Complete Examples

The complete example for htlc.py is listed below.

#!/usr/bin/env python3

from pyteal import *

""" HTLC
"""
hash_function = "Sha256"
hash_img = Bytes("base64", "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=")
timeout =Int(5555)
max_fee=Int(2000)
tmpl_rcv = Addr("ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM")
owner=Addr("GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A")

def htlc(tmpl_hash_img=hash_img,
         tmpl_timeout=timeout,
         tmpl_owner=owner,
         tmpl_max_fee=max_fee,
         tmpl_hash_fn=Sha256,
         tmpl_rcv=tmpl_rcv):

    fee_cond = Txn.fee() <= tmpl_max_fee
    type_cond = Txn.type_enum() == Int(1)
    amount_cond = Txn.amount() == Int(0)
    r_cond = Txn.receiver() == Global.zero_address()
    recv_cond = ( Txn.close_remainder_to() == tmpl_rcv ).And(
                tmpl_hash_fn(Arg(0)) == tmpl_hash_img)
    esc_cond = (Txn.close_remainder_to()  == tmpl_owner).And(
                Txn.first_valid() > tmpl_timeout)

    htlc_core = fee_cond.And(type_cond).And(r_cond).And(amount_cond).And(recv_cond.Or(esc_cond))   
    return htlc_core

print(htlc().teal())

The complete example for htlc_deploy.py is listed below.

#!/usr/bin/env python3

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

#--------- compile & send transaction using Goal and Python SDK ----------
tmpl_hash_fn = Sha256
tmpl_hash_img = Bytes("base64", "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=")
tmpl_timeout =Int(5000000)
tmpl_max_fee=Int(2000)
tmpl_rcv = Addr("42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE")
tmpl_owner=Addr("726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM")
teal_source = htlc(tmpl_hash_img, tmpl_timeout, tmpl_owner, tmpl_max_fee, tmpl_hash_fn, tmpl_rcv).teal() 
#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()

# 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 'hero wisdom green split loop element vote belt'
# hashed with sha256 will produce our image hash
# This is the passcode for the HTLC 
args = [
    "hero wisdom green split loop element vote belt".encode()
]
# Add the program bytes and args to a LogicSig object
lsig = transaction.LogicSig(teal_bytes, args)
print( lsig.address() )
# Create an algod client
algod_token = "<your_api_token>"
algod_address = "http://<your-algod-node>:<your-algod-port>"
client = algod.AlgodClient(algod_token, algod_address)
# Get suggested parameters from the network
tx_params = client.suggested_params()

# Before submitting this transaction, you must first make sure the escrow 
# account is sufficiently funded. Once it is funded, create and submit
# transactions from the escrow account like the transaction below.
# Transaction data
tx_data = {
    "sender": lsig.address(),
    "receiver": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
    "amt": 0,
    "close_remainder_to": "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE",
    "fee": 1000,
    "flat_fee": True,
    "first": tx_params.get('lastRound'),
    "last": tx_params.get('lastRound') + 1000,
    "gen": tx_params.get('genesisID'),
    "gh": tx_params.get('genesishashb64')
}
# Instantiate a payment transaction type
txn = transaction.PaymentTxn(**tx_data)
# Instantiate a LogicSigTransaction with the payment txn and the logicsig
logicsig_txn = transaction.LogicSigTransaction(txn, lsig)
# Send the transaction to the network.
txid = client.send_transaction(logicsig_txn, headers={'content-type': 'application/x-binary'})
print("Transaction ID: {}".format(txid))

htlc

logicsignature

pyteal

logic signature

v1 api

teal

May 05, 2020