HTLC SDK usage

This page is intended to help you call a Hash Time Locked Contract from within the SDKs. Templates are prebuilt TEAL programs that allow parameters to be injected into them from the SDKs. This will create a fully-realized TEAL program that can then be used with Algorand transactions. If you want to learn more about TEAL see the Algorand Smart Contract page, which contains references to more detailed information on the TEAL programming language and the TEAL stack engine.

See Hash Time Locked Contract for details on the TEAL code and a description of how the template functions.

Below you will find examples for creating an HTLC template, injecting template parameters, creating transactions that use the template, testing the template and sending the transactions to the network in each of the available SDKs. The example code is separated into snippets categorized by these core functions.

For information on installing Go, Java, JavaScript and Python SDKs see Using the SDKs and REST APIs.

  1. Setup: Imports, Global Variables, Helper Functions, etc. (Go, Java, JavaScript, Python)
  2. Create the template (Go, Java, JavaScript, Python) - This instantiates the template and injects template parameters.
  3. Create a logicSig (Go, Java, JavaScript, Python) - This section covers creating a transaction that is from the htlc contract account.
  4. Create and Sign Transaction (Go, Java, JavaScript, Python) - Create and sign the transaction with the contract logicSignature.
  5. Save and test the Signed Transaction (Go, Java, JavaScript, Python) - Save the signed transaction to a file and use goal for a line by line display of how the contract is processed.
  6. Send transactions to the network (Go, Java, JavaScript, Python) - Send the transaction to the network.

HTLC Template in Go

Setup: Imports, Global Variables, Helper Functions, etc.

Define imports, algod constants, setup connection and get the currently suggested parameters for a request from the Algorand network.


package main

import (
	"encoding/base64"
	"fmt"
    "os"
	"golang.org/x/crypto/ed25519"

	"github.com/algorand/go-algorand-sdk/client/algod"
	"github.com/algorand/go-algorand-sdk/crypto"
	"github.com/algorand/go-algorand-sdk/templates"
	"github.com/algorand/go-algorand-sdk/transaction"
)
// This example creates a contract account
func main() {
	// CHANGE ME
	const algodAddress = "http://localhost:8080"
	const algodToken = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70"
	//Blank values as we are creating an escrow account
	var sk ed25519.PrivateKey
	var ma crypto.MultisigAccount
	// Create an algod client
	algodClient, err := algod.MakeClient(algodAddress, algodToken)
	if err != nil {
		fmt.Printf("failed to make algod client: %s\n", err)
		return
	}
	// Get suggested params for the transaction
	txParams, err := algodClient.SuggestedParams()
    if err != nil {
            fmt.Printf("error getting suggested tx params: %s\n", err)
            return
    }	

Create the Template

This instantiates the HTLC template with a set of parameters for configuration. These parameters should not be confused with Transaction parameters to be passed into the contract when using the HTLC. We can then get the program bytes. At this point, you could save the program off to be used in another application or for later usage.


	// Inputs 
	owner := "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM"
	receiver := "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE"
	hashFn := "sha256"
	hashImg := "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc="
	expiryRound := txParams.LastRound + uint64(10000)
	maxFee := uint64(2000)
	//Instaniate the Template
	c, err := templates.MakeHTLC(owner, receiver, hashFn, hashImg, expiryRound, maxFee)
	program := c.GetProgram()

Create the logicSig

Take the program that is created by the template and set the args for the contract to be used with a transaction. Using these parameters a logicSignature can be created to sign a transaction later. The logicSignature is a replacement for signing the transaction with a spending key. If you do not want to pass in parameters yet you can still create an lsig without the args. Later you can create a new lsig with the program and a set of args to be used to sign a transaction. Also note that we print the contract account address out. At this point the account can be funded.


	// 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 := make([][]byte, 1)
	args[0] = []byte("hero wisdom green split loop element vote belt")
	
	// Next we create a LogicSig using blank private key
	// and blank multisig account because we are creating
	// a escrow contract account
	lsig, err := crypto.MakeLogicSig(program, args, sk, ma)
	addr := crypto.LogicSigAddress(lsig).String()
	fmt.Printf("Escrow Address: %s\n" , addr )

Create the Transaction

A normal transaction can now be created and signed with the logicSignature for the contract account. Note that the receiver address is set to the Zero address and the close remainder to is used to empty the contract account funds into a specific account with an HTLC.


	//Setup a transaction as normal
	const fee = 1000
	const amount = 0
	var note []byte
	// Make transaction
	genID := txParams.GenesisID
	genHash := txParams.GenesisHash
	tx, err := transaction.MakePaymentTxnWithFlatFee(
		addr, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ", fee, amount, txParams.LastRound, txParams.LastRound + 1000,
		note, receiver, genID, genHash)

	// Sign transaction with logicSig 
	txid, stx, err := crypto.SignLogicsigTransaction(lsig, tx)
	if err != nil {
		fmt.Printf("Signing failed with %v", err)
		return
	}
	fmt.Printf("Signed tx: %v\n", txid)

Save and test the Signed Transaction

Once you have a signed transaction you can save it to a file if you wish to debug it. See Using goal clerk dryrun for more details on how to use the saved signed transaction.


	//Write out the signed transaction to test with goal clerk dryrun -t filename
    f, err := os.Create("test.txn")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    if _, err := f.Write(stx); err != nil {
        panic(err)
    }
    if err := f.Sync(); err != nil {
        panic(err)
    }

Send transactions to the network

Finally, the saved transaction can be sent to the network.


	// Submit the raw transaction as normal
	transactionID, err := algodClient.SendRawTransaction(stx)
	if err != nil {
		fmt.Printf("Sending failed with %v\n", err)
	}
	fmt.Printf("Transaction ID: %v\n", transactionID)

}

The full example code is presented below.


package main

import (
	"encoding/base64"
	"fmt"
    "os"
	"golang.org/x/crypto/ed25519"

	"github.com/algorand/go-algorand-sdk/client/algod"
	"github.com/algorand/go-algorand-sdk/crypto"
	"github.com/algorand/go-algorand-sdk/templates"
	"github.com/algorand/go-algorand-sdk/transaction"
)
// This example creates a contract account
func main() {
	// CHANGE ME
	const algodAddress = "http://localhost:8080"
	const algodToken = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70"
	//Blank values as we are creating an escrow account
	var sk ed25519.PrivateKey
	var ma crypto.MultisigAccount
	// Create an algod client
	algodClient, err := algod.MakeClient(algodAddress, algodToken)
	if err != nil {
		fmt.Printf("failed to make algod client: %s\n", err)
		return
	}
	// Get suggested params for the transaction
	txParams, err := algodClient.SuggestedParams()
    if err != nil {
            fmt.Printf("error getting suggested tx params: %s\n", err)
            return
    }	
	// Inputs 
	owner := "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM"
	receiver := "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE"
	hashFn := "sha256"
	hashImg := "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc="
	expiryRound := txParams.LastRound + uint64(10000)
	maxFee := uint64(2000)
	//Instaniate the Template
	c, err := templates.MakeHTLC(owner, receiver, hashFn, hashImg, expiryRound, maxFee)
	program := c.GetProgram()
	
	// 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 := make([][]byte, 1)
	args[0] = []byte("hero wisdom green split loop element vote belt")
	
	// Next we create a LogicSig using blank private key
	// and blank multisig account because we are creating
	// a escrow contract account
	lsig, err := crypto.MakeLogicSig(program, args, sk, ma)
	addr := crypto.LogicSigAddress(lsig).String()
	fmt.Printf("Escrow Address: %s\n" , addr )
	
	//Setup a transaction as normal
	const fee = 1000
	const amount = 0
	var note []byte
	// Make transaction
	genID := txParams.GenesisID
	genHash := txParams.GenesisHash
	tx, err := transaction.MakePaymentTxnWithFlatFee(
		addr, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ", fee, amount, txParams.LastRound, txParams.LastRound + 1000,
		note, receiver, genID, genHash)

	// Sign transaction with logicSig 
	txid, stx, err := crypto.SignLogicsigTransaction(lsig, tx)
	if err != nil {
		fmt.Printf("Signing failed with %v", err)
		return
	}
	fmt.Printf("Signed tx: %v\n", txid)

	//Write out the signed transaction to test with goal clerk dryrun -t filename
    f, err := os.Create("test.txn")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    if _, err := f.Write(stx); err != nil {
        panic(err)
    }
    if err := f.Sync(); err != nil {
        panic(err)
    }

	// Submit the raw transaction as normal
	transactionID, err := algodClient.SendRawTransaction(stx)
	if err != nil {
		fmt.Printf("Sending failed with %v\n", err)
	}
	fmt.Printf("Transaction ID: %v\n", transactionID)

}

HTLC Template in Java

Setup: Imports, Global Variables, Helper Functions, etc.

Define imports, algod constants, setup connection and get the currently suggested parameters for a request from the Algorand network.


package com.algorand.algosdk.teal;

import com.algorand.algosdk.account.Account;
import com.algorand.algosdk.algod.client.AlgodClient;
import com.algorand.algosdk.algod.client.ApiException;
import com.algorand.algosdk.algod.client.api.AlgodApi;
import com.algorand.algosdk.algod.client.auth.ApiKeyAuth;
import com.algorand.algosdk.algod.client.model.*;
import com.algorand.algosdk.crypto.Address;
import com.algorand.algosdk.crypto.Digest;
import com.algorand.algosdk.crypto.LogicsigSignature;
import com.algorand.algosdk.templates.ContractTemplate;
import com.algorand.algosdk.templates.HTLC;
import com.algorand.algosdk.transaction.SignedTransaction;
import com.algorand.algosdk.transaction.Transaction;
import com.algorand.algosdk.util.Encoder;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.ArrayList;

/**
 * Sign and Submit a transaction example using HTLC
 *
 */
public class SubmitHtlcTransaction 
{
    public static void main(String args[]) throws Exception {
        // Setup conection parameters
        final String ALGOD_API_ADDR = "http://localhost:8080";
        final String ALGOD_API_TOKEN = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70";
        // Instaniate the algod wrapper
        AlgodClient client = (AlgodClient) new AlgodClient().setBasePath(ALGOD_API_ADDR);
        ApiKeyAuth api_key = (ApiKeyAuth) client.getAuthentication("api_key");
        api_key.setApiKey(ALGOD_API_TOKEN);
        AlgodApi algodApiInstance = new AlgodApi(client);

        // get last round and suggested tx fee
        BigInteger suggestedFeePerByte = BigInteger.valueOf(1);
        BigInteger firstRound = BigInteger.valueOf(301);
        String genId = null;
        Digest genesisHash = null;
        BigInteger expiryRound = BigInteger.valueOf(0);
        try {
            // Get suggested parameters from the node
            TransactionParams params = algodApiInstance.transactionParams();
            suggestedFeePerByte = params.getFee();
            firstRound = params.getLastRound();
            System.out.println("Suggested Fee: " + suggestedFeePerByte);
            genId = params.getGenesisID();
            genesisHash = new Digest(params.getGenesishashb64());
            firstRound = params.getLastRound();
            expiryRound = BigInteger.valueOf(5000000);

        } catch (ApiException e) {
            System.err.println("Exception when calling algod#transactionParams");
            e.printStackTrace();
        }

Create the Template

This instantiates the HTLC template with a set of parameters for configuration. These parameters should not be confused with Transaction parameters to be passed into the contract when using the HTLC. We can then get the program bytes. At this point, you could save the program off to be used in another application or for later usage.


        // Inputs to the Template
        int maxFee = 2000;
		String owner = "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM";
		String receiver = "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE";
		String hashFn = "sha256";
		String hashImg = "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=";
        // Instantiate the templates
		ContractTemplate result = HTLC.MakeHTLC(owner, receiver, hashFn, hashImg, expiryRound.intValue(), maxFee);        

Create the logicSig

Take the program that is created by the template and set the args for the contract to be used with a transaction. Using these parameters a logicSignature can be created to sign a transaction later. The logicSignature is a replacement for signing the transaction with a spending key. If you do not want to pass in parameters yet you can still create an lsig without the args. Later you can create a new lsig with the program and a set of args to be used to sign a transaction. Also note that we print the contract account address out. At this point the account can be funded.



        // contract args
        ArrayList pargs = new ArrayList();
        byte[] arg1 = "hero wisdom green split loop element vote belt".getBytes();
        // byte[] arg2 = {4, 5, 6};
        pargs.add(arg1);

        //lsig = new LogicsigSignature(program, args);
        LogicsigSignature lsig = new LogicsigSignature(result.program, pargs);
        // Print out the contract address
        System.out.println("Contract address: " + lsig.toAddress().toString());
[]>[]>

Create the Transaction

A normal transaction can now be created and signed with the logicSignature for the contract account. Note that the receiver address is set to the Zero address and the close remainder to is used to empty the contract account funds into a specific account with an HTLC.


        // Create a Transaction
        Transaction tx = new Transaction(lsig.toAddress(),BigInteger.valueOf(1000), firstRound, lastRound, null, genId, genesisHash,
        amount, new Address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"), new Address("42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE"));
        try {
            // Create a signed transaction with logicSig
            SignedTransaction stx = Account.signLogicsigTransaction(lsig, tx);

Save and test the Signed Transaction

Once you have a signed transaction you can save it to a file if you wish to debug it. See Using goal clerk dryrun for more details on how to use the saved signed transaction.


            byte[] outBytes = Encoder.encodeToMsgPack(stx);
            try {
                String FILEPATH = "./test.txn";
                File file = new File(FILEPATH);
                OutputStream os = new FileOutputStream(file);
                // Starts writing the bytes in it
                os.write(outBytes);
                // Close the file
                os.close();
            }
            catch (Exception e) {
                System.out.println("Exception: " + e);
            }

Send transactions to the network

Finally, the saved transaction can be sent to the network.


            // Msgpack encode the signed transaction and send to the network
            byte[] encodedTxBytes = Encoder.encodeToMsgPack(stx);
            TransactionID id = algodApiInstance.rawTransaction(encodedTxBytes);
            System.out.println("Successfully sent tx with id: " + id);
        } catch (ApiException e) {
            // This is generally expected, but should give us an informative error message.
            System.err.println("Exception when calling algod#rawTransaction: " + e.getResponseBody());
        }
    }
}

The full example code is presented below.


package com.algorand.algosdk.teal;

import com.algorand.algosdk.account.Account;
import com.algorand.algosdk.algod.client.AlgodClient;
import com.algorand.algosdk.algod.client.ApiException;
import com.algorand.algosdk.algod.client.api.AlgodApi;
import com.algorand.algosdk.algod.client.auth.ApiKeyAuth;
import com.algorand.algosdk.algod.client.model.*;
import com.algorand.algosdk.crypto.Address;
import com.algorand.algosdk.crypto.Digest;
import com.algorand.algosdk.crypto.LogicsigSignature;
import com.algorand.algosdk.templates.ContractTemplate;
import com.algorand.algosdk.templates.HTLC;
import com.algorand.algosdk.transaction.SignedTransaction;
import com.algorand.algosdk.transaction.Transaction;
import com.algorand.algosdk.util.Encoder;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.ArrayList;

/**
 * Sign and Submit a transaction example using HTLC
 *
 */
public class SubmitHtlcTransaction 
{
    public static void main(String args[]) throws Exception {
        // Setup conection parameters
        final String ALGOD_API_ADDR = "http://localhost:8080";
        final String ALGOD_API_TOKEN = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70";
        // Instaniate the algod wrapper
        AlgodClient client = (AlgodClient) new AlgodClient().setBasePath(ALGOD_API_ADDR);
        ApiKeyAuth api_key = (ApiKeyAuth) client.getAuthentication("api_key");
        api_key.setApiKey(ALGOD_API_TOKEN);
        AlgodApi algodApiInstance = new AlgodApi(client);

        // get last round and suggested tx fee
        BigInteger suggestedFeePerByte = BigInteger.valueOf(1);
        BigInteger firstRound = BigInteger.valueOf(301);
        String genId = null;
        Digest genesisHash = null;
        BigInteger expiryRound = BigInteger.valueOf(0);
        try {
            // Get suggested parameters from the node
            TransactionParams params = algodApiInstance.transactionParams();
            suggestedFeePerByte = params.getFee();
            firstRound = params.getLastRound();
            System.out.println("Suggested Fee: " + suggestedFeePerByte);
            genId = params.getGenesisID();
            genesisHash = new Digest(params.getGenesishashb64());
            firstRound = params.getLastRound();
            expiryRound = BigInteger.valueOf(5000000);

        } catch (ApiException e) {
            System.err.println("Exception when calling algod#transactionParams");
            e.printStackTrace();
        }

        // Inputs to the Template
        int maxFee = 2000;
		String owner = "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM";
		String receiver = "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE";
		String hashFn = "sha256";
		String hashImg = "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=";
        // Instantiate the templates
		ContractTemplate result = HTLC.MakeHTLC(owner, receiver, hashFn, hashImg, expiryRound.intValue(), maxFee);        

	    // 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  
        BigInteger amount = BigInteger.valueOf(0);
        BigInteger lastRound = firstRound.add(BigInteger.valueOf(1000)); // 1000 is the max tx window

        // contract args 
        ArrayList pargs = new ArrayList();
        byte[] arg1 = "hero wisdom green split loop element vote belt".getBytes();
        // byte[] arg2 = {4, 5, 6};
        pargs.add(arg1);

        //lsig = new LogicsigSignature(program, args);
        LogicsigSignature lsig = new LogicsigSignature(result.program, pargs);
        // Print out the contract address
        System.out.println("Contract address: " + lsig.toAddress().toString());
        
        // Create a Transaction
        Transaction tx = new Transaction(lsig.toAddress(),BigInteger.valueOf(1000), firstRound, lastRound, null, genId, genesisHash,
        amount, new Address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"), new Address("42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE"));
        try {
            // Create a signed transaction with logicSig
            SignedTransaction stx = Account.signLogicsigTransaction(lsig, tx);
            byte[] outBytes = Encoder.encodeToMsgPack(stx);
            try {
                String FILEPATH = "./test.txn";
                File file = new File(FILEPATH);
                OutputStream os = new FileOutputStream(file);
                // Starts writing the bytes in it
                os.write(outBytes);
                // Close the file
                os.close();
            }
            catch (Exception e) {
                System.out.println("Exception: " + e);
            }
            // Msgpack encode the signed transaction and send to the network
            byte[] encodedTxBytes = Encoder.encodeToMsgPack(stx);
            TransactionID id = algodApiInstance.rawTransaction(encodedTxBytes);
            System.out.println("Successfully sent tx with id: " + id);
        } catch (ApiException e) {
            // This is generally expected, but should give us an informative error message.
            System.err.println("Exception when calling algod#rawTransaction: " + e.getResponseBody());
        }
    }
}[]>[]>

HTLC Template in JavaScript

Setup: Imports, Global Variables, Helper Functions, etc.

Define imports, algod constants, setup connection and get the currently suggested parameters for a request from the Algorand network.


// Handle importing needed modules
const algosdk = require('algosdk');
const fs = require('fs');
const htlcTemplate = require("algosdk/src/logicTemplates/htlc");
// Retrieve the token, server and port values for your installation in the algod.net
// and algod.token files within the data directory
const token = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70";
const server = "http://127.0.0.1";
const port = 8080;
// Instantiate the algod wrapper
let algodclient = new algosdk.Algod(token, server, port);
(async() => {
    // Get the relevant params from the algod for the network
    let params = await algodclient.getTransactionParams();
    let endRound = params.lastRound + parseInt(1000);
    let fee = await algodclient.suggestedFee();

Create the Template

This instantiates the HTLC template with a set of parameters for configuration. These parameters should not be confused with Transaction parameters to be passed into the contract when using the HTLC. We can then get the program bytes. At this point, you could save the program off to be used in another application or for later usage. Also note that we print the contract account address out. At this point the account can be funded.


    // // Inputs
    let owner = "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM";
    let receiver = "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE";
    let hashFn = "sha256";
    let hashImg = "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=";
    let expiryRound = 5000000; //params.lastRound + 1000;
    let maxFee = 2000;
    // Instaniate the template
    let htlc = new htlcTemplate.HTLC(owner, receiver, hashFn, hashImg, expiryRound, maxFee);
    // Outputs
    let program = htlc.getProgram();
    console.log("htlc addr: " + htlc.getAddress());

Create the logicSig

Take the program that is created by the template and set the args for the contract to be used with a transaction. Using these parameters a logicSignature can be created to sign a transaction later. The logicSignature is a replacement for signing the transaction with a spending key. If you do not want to pass in parameters yet you can still create an lsig without the args. Later you can create a new lsig with the program and a set of args to be used to sign a transaction.


    // 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     
    let args = ["hero wisdom green split loop element vote belt"];
    let lsig = algosdk.makeLogicSig(program, args);

Create the Transaction

A normal transaction can now be created and signed with the logicSignature for the contract account. Note that the receiver address is set to the Zero address and the close remainder to is used to empty the contract account funds into a specific account with an HTLC.


    //create a transaction
    let txn = {
        "from": htlc.getAddress(),
        "to": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
        "fee": 1,
        "type": "pay",
        "amount": 0,
        "firstRound": params.lastRound,
        "lastRound": endRound,
        "genesisID": params.genesisID,
        "genesisHash": params.genesishashb64,
        "closeRemainderTo": "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE"
    };
    // create logic signed transaction.
    let rawSignedTxn = algosdk.signLogicSigTransaction(txn, lsig);

Save and test the Signed Transaction

Once you have a signed transaction you can save it to a file if you wish to debug it. See Using goal clerk dryrun for more details on how to use the saved signed transaction.


    // Save the signed transaction file for debugging purposes
    fs.writeFileSync("test.txn", rawSignedTxn.blob);

Send transactions to the network

Finally, the saved transaction can be sent to the network.


    //Submit the lsig signed transaction
    let tx = (await algodclient.sendRawTransaction(rawSignedTxn.blob));
    console.log("Transaction : " + tx.txId);
})().catch(e => {
    console.log(e);
});

The full example code is presented below.


// Handle importing needed modules
const algosdk = require('algosdk');
const fs = require('fs');
const htlcTemplate = require("algosdk/src/logicTemplates/htlc");
// Retrieve the token, server and port values for your installation in the algod.net
// and algod.token files within the data directory
const token = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70";
const server = "http://127.0.0.1";
const port = 8080;
// Instantiate the algod wrapper
let algodclient = new algosdk.Algod(token, server, port);
(async() => {
    // Get the relevant params from the algod for the network
    let params = await algodclient.getTransactionParams();
    let endRound = params.lastRound + parseInt(1000);
    let fee = await algodclient.suggestedFee();
    // // Inputs
    let owner = "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM";
    let receiver = "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE";
    let hashFn = "sha256";
    let hashImg = "QzYhq9JlYbn2QdOMrhyxVlNtNjeyvyJc/I8d8VAGfGc=";
    let expiryRound = 5000000; //params.lastRound + 1000;
    let maxFee = 2000;
    // Instaniate the template
    let htlc = new htlcTemplate.HTLC(owner, receiver, hashFn, hashImg, expiryRound, maxFee);
    // Outputs
    let program = htlc.getProgram();
    console.log("htlc addr: " + htlc.getAddress());

    // 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     
    let args = ["hero wisdom green split loop element vote belt"];
    let lsig = algosdk.makeLogicSig(program, args);
    //create a transaction
    let txn = {
        "from": htlc.getAddress(),
        "to": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
        "fee": 1,
        "type": "pay",
        "amount": 0,
        "firstRound": params.lastRound,
        "lastRound": endRound,
        "genesisID": params.genesisID,
        "genesisHash": params.genesishashb64,
        "closeRemainderTo": "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE"
    };
    // create logic signed transaction.
    let rawSignedTxn = algosdk.signLogicSigTransaction(txn, lsig);

    // Save the signed transaction file for debugging purposes
    fs.writeFileSync("test.txn", rawSignedTxn.blob);

    //Submit the lsig signed transaction
    let tx = (await algodclient.sendRawTransaction(rawSignedTxn.blob));
    console.log("Transaction : " + tx.txId);
})().catch(e => {
    console.log(e);
});

HTLC Template in Python

Setup: Imports, Global Variables, Helper Functions, etc.

Define imports, algod constants, setup connection and get the currently suggested parameters for a request from the Algorand network.


# Create an algod client
algod_token = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70"
algod_address = "http://localhost:8080"
client = algod.AlgodClient(algod_token, algod_address)
# Get suggested parameters from the network
tx_params = client.suggested_params()

Create the Template

This instantiates the HTLC template with a set of parameters for configuration. These parameters should not be confused with Transaction parameters to be passed into the contract when using the HTLC. We can then get the program bytes. At this point, you could save the program off to be used in another application or for later usage.


# Specify TLHC-related template params.
template_data = {
	"owner": "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM",
	"receiver": "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE",
	"hash_function": "sha256",
	"hash_image": "9S+9MrKzuG/4jvbEkGKChfSCrxXdyylUH5S89Saj9sc=",
	"expiry_round": 5000000,
	"max_fee": 2000
}
## Inject template data into HTLC template
c = template.HTLC(**template_data)
# Retrieve the program bytes
program = c.get_program()

Create the logicSig

Take the program that is created by the template and set the args for the contract to be used with a transaction. Using these parameters a logicSignature can be created to sign a transaction later. The logicSignature is a replacement for signing the transaction with a spending key. Also note that we print the contract account address out. At this point the account can be funded. If you do not want to pass in parameters yet you can still create an lsig without the args. Later you can create a new lsig with the program and a set of args to be used to sign a transaction.


# 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 a
lsig = transaction.LogicSig(program, args)
# Get the address for the escrow account associated with the logic
addr = lsig.address()
print("Contract Address: {}\n".format(addr))

Create the Transaction

A normal transaction can now be created and signed with the logicSignature for the contract account. Note that the receiver address is set to the Zero address and the close remainder to is used to empty the contract account funds into a specific account with an HTLC.


# 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": addr,
    "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)

Save and test the Signed Transaction

Once you have a signed transaction you can save it to a file if you wish to debug it. See Using goal clerk dryrun for more details on how to use the saved signed transaction.


# Write the transaction to a file and test with `goal clerk dryrun -t test.txn`
transaction.write_to_file([logicsig_txn], "test.txn")

Send transactions to the network

Finally, the saved transaction can be sent to the network.


# Send the transaction to the network.
txid = client.send_transaction(logicsig_txn, headers={'content-type': 'application/x-binary'})
print("Transaction ID: {}".format(txid))

The full example code is presented below.

 


from algosdk import algod, transaction, template
# Create an algod client
algod_token = "61bddc5e84ed1ea3f8e9143a70da8fbe3478ae5e06caa52064332b09d158bb70"
algod_address = "http://localhost:8080"
client = algod.AlgodClient(algod_token, algod_address)
# Get suggested parameters from the network
tx_params = client.suggested_params()
# Specify TLHC-related template params.
template_data = {
	"owner": "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM",
	"receiver": "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE",
	"hash_function": "sha256",
	"hash_image": "9S+9MrKzuG/4jvbEkGKChfSCrxXdyylUH5S89Saj9sc=",
	"expiry_round": 5000000,
	"max_fee": 2000
}
## Inject template data into HTLC template
c = template.HTLC(**template_data)
# Retrieve the program bytes
program = c.get_program()
# 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 a
lsig = transaction.LogicSig(program, arg)
# Get the address for the escrow account associated with the logic
addr = lsig.address()
print("Escrow Address: {}\n".format(addr))
# 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": addr,
    "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)
# Write the transaction to a file and test with `goal clerk dryrun -t test.txn`
transaction.write_to_file([logicsig_txn], "test.txn")
# Send the transaction to the network.
txid = client.send_transaction(logicsig_txn, headers={'content-type': 'application/x-binary'})
print("Transaction ID: {}".format(txid))

Using goal clerk dryrun

Once you save a signed transaction to a file you can use the goal clerk dryrun -t signed-transaction.file. To get a printout of how the TEAL is processed. This will look something similar to the following.


$ goal clerk dryrun -t test.txn
tx[0] cost=40 trace:
  1 intcblock => 
 11 bytecblock => 
112 txn => 1000 0x3e8
114 intc_0 => 2000 0x7d0
115 <= => 1 0x1
116 txn => 1 0x1
118 intc_1 => 1 0x1
119 == => 1 0x1
120 && => 1 0x1
121 txn => 0000000000000000000000000000000000000000000000000000000000000000
123 global => 0000000000000000000000000000000000000000000000000000000000000000
125 == => 1 0x1
126 && => 1 0x1
127 txn => 0 0x0
129 intc_2 => 0 0x0
130 == => 1 0x1
131 && => 1 0x1
132 txn => e69a961e6f2d5f7ac86607926552be98235ed33363ca88918bb9c938a915b6f9
134 bytec_0 => e69a961e6f2d5f7ac86607926552be98235ed33363ca88918bb9c938a915b6f9
135 == => 1 0x1
136 arg_0 => 68756e74657232
137 sha256 => f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
138 bytec_1 => f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
139 == => 1 0x1
140 && => 1 0x1
141 txn => e69a961e6f2d5f7ac86607926552be98235ed33363ca88918bb9c938a915b6f9
143 bytec_2 => febca0bb144a5a4ea7b438a4681ac80e6ca105bcb607fc565325c95695f6a213
144 == => 0 0x0
145 txn => 634006 0x9ac96
147 intc_3 => 5000000 0x4c4b40
148 > => 0 0x0
149 && => 0 0x0
150 || => 1 0x1
151 && => 1 0x1

 - pass -