Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial Thumbnail
Intermediate · 30 minutes

Hash Time Lock Contract Template With Java

This tutorial is intended to help you call a Hash Time Locked Contract using Java. Templates are prebuilt TEAL programs that allow parameters to be injected into them from the SDKs that configure the contract. In this example, we are going to instantiate the HTLC Template and show how it can be used with a transaction.

Requirements

Background

Algorand provides many templates for Smart Contract implementation in the SDKs. 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.

Steps

1. Create Template

The HTLC template can be instantiated with a set of predefined parameters that configure the HTLC contract. These parameters should not be confused with Transaction parameters that are passed into the contract when using the HTLC. These parameters configure how the HTLC will function:

  • 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

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.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://<your-host-and-port>";
        final String ALGOD_API_TOKEN = "<your-api-token>";
        // 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 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();
            firstRound = params.getLastRound();
            genId = params.getGenesisID();
            genesisHash = new Digest(params.getGenesishashb64());
            firstRound = params.getLastRound();
            expiryRound = params.getLastRound().add(BigInteger.valueOf(10000));

        } 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);        

    }
}

2. Create the Logic Signature

Before the account can be used, it must be funded. The HTLC address represents the account’s address which we will fund using the dispenser for the purpose of this tutorial.

To use the HTLC contract in a transaction, a Logic Signature must be created. This will be used later to sign the transaction. The Logic Signature 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. In this example, we are passing the one transaction parameter (password) as we create the Logic Signature. Logic Signatures are further documented on the developer site.

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.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://<your-host-and-port>";
        final String ALGOD_API_TOKEN = "<your-api-token>";
        // 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 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();
            firstRound = params.getLastRound();
            genId = params.getGenesisID();
            genesisHash = new Digest(params.getGenesishashb64());
            firstRound = params.getLastRound();
            expiryRound = params.getLastRound().add(BigInteger.valueOf(10000));

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

        // Inputs to the Template
        int maxFee = 2000;
        Address owner = new Address("726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM");
        Address receiver = new Address("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));

        ArrayList<byte[]> pargs = new ArrayList<byte[]>();
        byte[] arg1 = "hero wisdom green split loop element vote belt".getBytes();
        // byte[] arg2 = {4, 5, 6};
        pargs.add(arg1);
        System.out.println("program: " + result.program.toString());
        LogicsigSignature lsig = new LogicsigSignature(result.program, pargs);
        // Print out the contract address
        // if you are debugging this complete example
        // be sure to add a break point after you have the contract address
        // before submitting the transaction, so you can fund it using the dispenser
        System.out.println("Contract address: " + lsig.toAddress().toString());

    }
}


Learn More
- Add Funds using Dispenser
- Smart Contracts - Logic Signatures

3. Create and Sign the Transaction

A transaction can now be created that requests the funds from the HTLC Contract account. The amount should be set to 0 as the contract will close out all funds at once. The sender address should be set to the contract’s address. After the transaction is created, it can be signed with the Logic Signature as shown in the highlighted code below. Note that the receiver field is set to the Zero address as the contract automatically closes out to the receiver that was configured in the template creation. If this field is not set to the zero address the transaction will fail.

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.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://<your-host-and-port>";
        final String ALGOD_API_TOKEN = "<your-api-token>";
        // 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 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();
            firstRound = params.getLastRound();
            genId = params.getGenesisID();
            genesisHash = new Digest(params.getGenesishashb64());
            firstRound = params.getLastRound();
            expiryRound = params.getLastRound().add(BigInteger.valueOf(10000));

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

        // Inputs to the Template
        int maxFee = 2000;
        Address owner = new Address("726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM");
        Address receiver = new Address("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));

        ArrayList<byte[]> pargs = new ArrayList<byte[]>();
        byte[] arg1 = "hero wisdom green split loop element vote belt".getBytes();
        // byte[] arg2 = {4, 5, 6};
        pargs.add(arg1);
        System.out.println("program: " + result.program.toString());
        LogicsigSignature lsig = new LogicsigSignature(result.program, pargs);
        // Print out the contract address
        // if you are debugging this complete example
        // be sure to add a break point after you have the contract address
        // before submitting the transaction, so you can fund it using the dispenser
        System.out.println("Contract address: " + lsig.toAddress().toString());

        // Create a Transaction
        Address zeroAddress = new Address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ");
        Transaction tx = Transaction.PaymentTransactionBuilder()
                .sender(lsig.toAddress())
                .closeRemainderTo(receiver)
                .receiver(zeroAddress)
                .flatFee(1000)
                .amount(0)
                .firstValid(firstRound)
                .lastValid(lastRound)
                .genesisHash(genesisHash)
                .build(); 
        try {
            // Create a signed transaction with logicSig
            SignedTransaction stx = Account.signLogicsigTransaction(lsig, tx);

        } catch (Exception e) {
            System.err.println(e);
        }

    }
}

4. Send the Transaction to the Network

The final step is to send the transaction to the network. If the contract is funded, the transaction should succeed.

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.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://<your-host-and-port>";
        final String ALGOD_API_TOKEN = "<your-api-token>";
        // 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 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();
            firstRound = params.getLastRound();
            genId = params.getGenesisID();
            genesisHash = new Digest(params.getGenesishashb64());
            firstRound = params.getLastRound();
            expiryRound = params.getLastRound().add(BigInteger.valueOf(10000));

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

        // Inputs to the Template
        int maxFee = 2000;
        Address owner = new Address("726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM");
        Address receiver = new Address("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));

        ArrayList<byte[]> pargs = new ArrayList<byte[]>();
        byte[] arg1 = "hero wisdom green split loop element vote belt".getBytes();
        // byte[] arg2 = {4, 5, 6};
        pargs.add(arg1);
        System.out.println("program: " + result.program.toString());
        LogicsigSignature lsig = new LogicsigSignature(result.program, pargs);
        // Print out the contract address
        // if you are debugging this complete example
        // be sure to add a break point after you have the contract address
        // before submitting the transaction, so you can fund it using the dispenser
        System.out.println("Contract address: " + lsig.toAddress().toString());

        // Create a Transaction
        Address zeroAddress = new Address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ");
        Transaction tx = Transaction.PaymentTransactionBuilder()
                .sender(lsig.toAddress())
                .closeRemainderTo(receiver)
                .receiver(zeroAddress)
                .flatFee(1000)
                .amount(0)
                .firstValid(firstRound)
                .lastValid(lastRound)
                .genesisHash(genesisHash)
                .build(); 
        try {
            // Create a signed transaction with logicSig
            SignedTransaction stx = Account.signLogicsigTransaction(lsig, tx);

            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());
        }
    }
}