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.

Solution Thumbnail

Integrate AlgoSigner to JavaScript Application on Algorand

Overview

Algorand is a versatile blockchain where your app design and implementation possibilities are endless. From decentralized escrow and exchange apps to building apps that utilize the immutability of Algorand’s decentralized ledger, there are many use cases and ways to build the decentralized application (dApp) you want.

This solution contains several examples of different ways to use the Algorand JavaScript SDK to accomplish your goals. We’ll also go over how to integrate AlgoSigner with your dApp. As a developer, you can also use the associated Github project to get started developing dApps.

In this article, we’ll introduce the most common functions and features we’re going to use when building a dApp, such as:

  • Integration with AlgoSigner
  • Creating and sending ALGO and Algorand Standard Asset (ASA) transactions
  • Creating and sending atomic transfers
  • Interacting with Algorand Smart Contracts

Prerequisites

Before you begin, you should have some basic knowledge of Node.js, as well as some basic HTML and client-side JavaScript familiarity. This solution uses promises and async/await, so you should be familiar with those as well.

What you don’t need: any frontend frameworks. This solution does not use any frontend frameworks, like Vue or React – it’s built using pure HTML and vanilla JavaScript, with the Bulma CSS library installed to make everything look pretty. You can, of course, use the framework and CSS library you’re comfortable with. Please note that this demo has not been audited for security and should not be used in a production environment without modifications to enhance the security.

Your environment should also be setup in the following way:

  • You should have Node.js installed on your machine. The demo runs v14.16.1.
  • You should have both Google Chrome and the AlgoSigner extension for Google Chrome installed.
  • You should be signed up for PureStake and have a PureStake API key you can use to make calls to the Algorand API
  • You should download or clone the Algorand HTML / JS Demo to an empty folder on your machine. If you use an IDE, Visual Studio Code is recommended but not required.
  • It’s also recommended to make a couple of Algorand addresses on TestNet and give them ALGOs using the TestNet ALGO Faucet. You’ll need them to use the demo’s features.

Setting up the Demo

  1. Get the demo here. You can either clone it using Git or download it as a .zip file.
  2. Once you have the demo cloned or unpacked, replace the example API key in .env-EXAMPLE with your PureStake API key.
  3. Rename .env-EXAMPLE to .env in order to use your API key in Node.
  4. Open a terminal or command prompt in the folder you unzipped or cloned to.
  5. Run npm install to install dependencies.
  6. Run node app.js to run the application.
  7. Navigate to the server address in Google Chrome to use the demo.

Integrating AlgoSigner

In order for your dApp to be usable, you’ll need a way for the users of your app to sign transactions themselves. This is what AlgoSigner is built to do. If you have the AlgoSigner Google Chrome extension installed, it injects an instance of itself into the current webpage that can be referenced by JavaScript code.

So, how do we integrate AlgoSigner into our app? All we have to do is perform the following check before attempting to reference AlgoSigner:

if(typeof AlgoSigner !== 'undefined') {
    // AlgoSigner exists, we can call it
}

The demo warns you if you don’t have AlgoSigner installed. Next, we should check for accounts.

if(typeof AlgoSigner !== 'undefined') {
    // connects to the browser AlgoSigner instance
    AlgoSigner.connect()
    // finds the TestNet accounts currently in AlgoSigner
    .then(() => AlgoSigner.accounts({
        ledger: 'TestNet'
    }))
    .then((accountData) => {
        // the accountData object should contain the Algorand addresses from TestNet that AlgoSigner currently knows about
        console.log(accountData);
    })
    .catch((e) => {
        // handle errors and perform error cleanup here
        console.error(e);
    }
}

If you’ve integrated AlgoSigner correctly, you should see the accounts you added to AlgoSigner in the console when you run the above code.

Sending ALGO Transactions

Next, let’s send some ALGOs. In the Algorand SDK, we do this by making a call to a library called algod. We do the same steps as before, we just make different calls. Let’s start by asking the user to sign the transaction we’re going to send.

let from = '{address of the account to send ALGOs from}';
let to = '{address of the account receiving the ALGOs}';
let amount = '{the amount of ALGOs}';
let note = 'An optional note';

AlgoSigner.connect()
    // get the current parameters, these are needed in order to sign the transaction
    .then(() => AlgoSigner.algod({
        ledger: 'TestNet',
        path: '/v2/transactions/params'
    }))
    // sign the transaction
    .then((txParams) => AlgoSigner.sign({
        from: from,
        to: to,
        amount: +amount,
        note: note,
        // 'pay' is an ALGO payment transaction
        type: 'pay',
        fee: txParams['min-fee'], // the minimum fee to send the transaction
        firstRound: txParams['last-round'], // the first round the transaction is valid for
        lastRound: txParams['last-round'] + 1000, // the last round the transaction is valid for
        genesisID: txParams['genesis-id'], // required
        genesisHash: txParams['genesis-hash'], // required 
        flatFee: true // levy a flat fee instead of a variable fee
    }))
    .catch((e) => { 
        // handle errors and perform error cleanup here 
        console.error(e); 
    });

If you run the above code at this point, an AlgoSigner window should pop up asking for the passphrase you set when setting up. After you enter the passphrase, you should be given the details of the transaction you’re about to sign, including the accounts involved, the amount to be sent, and the fees.

At this point, pressing “Sign” won’t do anything but sign the transaction on your local machine. We still need to send the transaction.

let from = '{address of the account to send ALGOs from}'; 
let to = '{address of the account receiving the ALGOs}'; 
let amount = '{the amount of ALGOs}'; 
let note = 'An optional note'; 

AlgoSigner.connect()
    .then(() => AlgoSigner.algod({ 
        ledger: 'TestNet', 
        path: '/v2/transactions/params' 
    }))
    .then((txParams) => AlgoSigner.sign({ 
        from: from, 
        to: to, 
        amount: +amount, 
        note: note,
        type: 'pay',
        fee: txParams['min-fee'],
        firstRound: txParams['last-round'],
        lastRound: txParams['last-round'] + 1000,
        genesisID: txParams['genesis-id'],
        genesisHash: txParams['genesis-hash'],
        flatFee: true
    }))
    // send the transaction
    .then((signedTx) => AlgoSigner.send({
        ledger: 'TestNet',
        tx: signedTx.blob // the unique blob representing the signed transaction
    }))
    .catch((e) => { 
        // handle errors and perform error cleanup here
        console.error(e); 
    });

Checking Transaction Status

In order to know that our transaction has been successfully committed to the blockchain, we need to check its status.

AlgoSigner doesn’t have a built-in way to do this yet (as of August 2021), so I wrote a helper function that should allow you to check based on Ryan Fox’s excellent transaction confirmation code. Please refer to algosignerutils.js in the demo’s source code if you want to know more.

Otherwise, we can just call that function as-is. Let’s call it after the send transaction step. After we do this, our transaction should be posted to TestNet. Below is the complete code for this entire process.

let from = '{address of the account to send ALGOs from}'; 
let to = '{address of the account receiving the ALGOs}'; 
let amount = '{the amount of ALGOs}'; 
let note = 'An optional note'; 

AlgoSigner.connect()
    .then(() => AlgoSigner.algod({ 
        ledger: 'TestNet', 
        path: '/v2/transactions/params'
    }))
    .then((txParams) => AlgoSigner.sign({ 
        from: from,
        to: to,
        amount: +amount,
        note: note,
        type: 'pay',
        fee: txParams['min-fee'],
        firstRound: txParams['last-round'],
        lastRound: txParams['last-round'] + 1000,
        genesisID: txParams['genesis-id'],
        genesisHash: txParams['genesis-hash'],
        flatFee: true
    })) 
    .then((signedTx) => AlgoSigner.send({ 
        ledger: 'TestNet', 
        tx: signedTx.blob 
    }))
    // wait for confirmation from the blockchain
    .then((tx) => waitForAlgosignerConfirmation(tx)) // see algosignerutils.js
    .then((tx) => {
        // our transaction was successful, we can now view it on the blockchain 
    }
    .catch((e) => 
    { 
        // handle errors and perform error cleanup here
        console.error(e); 
    });

Creating and Sending Algorand Standard Asset (ASA) Transactions

So, we can send ALGOs, but what about other tokens on the blockchain? Not only can we send other tokens, we can also create them. It just takes a little bit of time and know-how. Let’s use our ALGO transaction code we wrote before as a template. Instead of sending a payment transaction, though, we’re going to send an “asset creation” (acfg) transaction.

let from = '{address of the account to issue the command from}'; 
let assetName = '{the name of the asset}'; 
let assetUnitName = '{the Unit Name of the asset}';
let assetTotal = '{the number of tokens in circulation}';
let assetDecimals = '{the number of decimals in the token}';
let note = 'An optional note'; 

AlgoSigner.connect()
    .then(() => AlgoSigner.algod({ 
        ledger: 'TestNet', 
        path: '/v2/transactions/params'
    }))
    // sign new transaction
      .then((txParams)  =>  AlgoSigner.sign({
      from:  from,
      assetName:  assetName, // name of the asset
      assetUnitName:  assetUnit, // "unit" name of the asset
      assetTotal:  +assetTotal, // total in circulation. use assetTotal = 1 to make an NFT
      assetDecimals:  +assetDecimals, // if assetDecimals > 0, then the asset can be subdivided into "portions" of a token
      note:  assetNote,
      type:  'acfg',  // ASA Configuration (acfg)
      fee:  txParams['min-fee'],
      firstRound:  txParams['last-round'],
      lastRound:  txParams['last-round'] + 1000,
      genesisID:  txParams['genesis-id'],
      genesisHash:  txParams['genesis-hash'],
      flatFee:  true
    }))
    .then((signedTx) => AlgoSigner.send({ 
        ledger: 'TestNet', 
        tx: signedTx.blob 
    }))
    // wait for confirmation from the blockchain
    .then((tx) => waitForAlgosignerConfirmation(tx)) // see algosignerutils.js
    .then((tx) => {
        // our transaction was successful, we can now view it on the blockchain 
    }
    .catch((e) => 
    { 
        // handle errors and perform error cleanup here
        console.error(e); 
    });

Once we send and confirm the above transaction, we can get the ID of the asset and use it in subsequent transactions. One of the first transactions we should send if we’re sending the ASA to another wallet is the “opt-in” transaction. Opt-in transactions are a security mechanism built into the ASA system preventing a wallet from receiving any asset they haven’t consented to receiving. If we didn’t opt-in first, the API would return the following error:

asset {your asset ID here} missing from {the wallet ID you tried to send the asset to}

So, in order to opt-in to this asset, we’re going to send a transaction where we transfer zero units of the asset to ourselves.

let assetId = '{ID of the asset we want to opt-in}'
let optInAccount = '{address of the account to opt-in}'; 
let note = 'An optional note'; 

AlgoSigner.connect()
    .then(() => AlgoSigner.algod({ 
        ledger: 'TestNet', 
        path: '/v2/transactions/params'
    }))
    // sign new opt-in transaction
    .then((txParams) => AlgoSigner.sign({
      assetIndex: assetId,
        from: optInAccount,
        to: optInAccount,
        amount: 0,
        note: note,
        type: 'axfer',
        fee: txParams['min-fee'],
        firstRound: txParams['last-round'],
        lastRound: txParams['last-round'] + 1000,
        genesisID: txParams['genesis-id'],
        genesisHash: txParams['genesis-hash'],
        flatFee: true
    })) 
    .then((signedTx) => AlgoSigner.send({ 
        ledger: 'TestNet', 
        tx: signedTx.blob 
    }))
    // wait for confirmation from the blockchain
    .then((tx) => waitForAlgosignerConfirmation(tx)) // see algosignerutils.js
    .then((tx) => {
        // our transaction was successful, we can now view it on the blockchain 
    }
    .catch((e) => 
    { 
        // handle errors and perform error cleanup here
        console.error(e); 
    });

Once we’ve opted into the asset, we can send the “payment” transaction we’re familiar with. The only differences here are that we need to provide an Asset ID, and we need to specify a transaction type of axfer instead of pay.

let assetId = '{ID of the asset we want to transfer}'
let from = '{address of the account to send ALGOs from}'; 
let to = '{address of the account receiving the ALGOs}'; 
let amount = '{the amount of ALGOs}'; 
let note = 'An optional note'; 

AlgoSigner.connect()
    .then(() => AlgoSigner.algod({ 
        ledger: 'TestNet', 
        path: '/v2/transactions/params'
    }))
    .then((txParams) => AlgoSigner.sign({
      assetIndex: assetId,
        from: from,
        to: to,
        amount: +amount,
        note: note,
        type: 'axfer',
        fee: txParams['min-fee'],
        firstRound: txParams['last-round'],
        lastRound: txParams['last-round'] + 1000,
        genesisID: txParams['genesis-id'],
        genesisHash: txParams['genesis-hash'],
        flatFee: true
    })) 
    .then((signedTx) => AlgoSigner.send({ 
        ledger: 'TestNet', 
        tx: signedTx.blob 
    }))
    // wait for confirmation from the blockchain
    .then((tx) => waitForAlgosignerConfirmation(tx)) // see algosignerutils.js
    .then((tx) => {
        // our transaction was successful, we can now view it on the blockchain 
    }
    .catch((e) => 
    { 
        // handle errors and perform error cleanup here
        console.error(e); 
    });

Atomic Transfers

Now we can send ALGOs, and we can also create and send ASAs. But how can we trust, if we exchange ALGOs or ASAs with a stranger, that they’ll hold up their end of the deal? We create what’s called an “atomic transfer”. Here, atomic means “either all tasks complete, or no tasks complete at all.” In the case of our atomic transfer, both transactions have to post to the blockchain or neither will.

This is a fairly involved process, but it can be distilled down to the following steps:

  1. Create two transactions (we’ll refer to these as the original transactions)
  2. Assign these two transactions a “group ID”. In the JavaScript SDK, a Group object will be returned containing copies of the transactions that were grouped together
  3. Encode the “group” fields of the original transactions (not the ones in the Group object) to Base64, using the group fields in the Group object
  4. Sign both original transactions using AlgoSigner; signed transactions will be returned
  5. Decompose both signatures into their binary representations. In the JS SDK, we’ll decompose each signature into a Uint8Array of bytes.
  6. Append the second binary array onto the end of the first
  7. Convert the combined binary array back to Base64
  8. Send the grouped transaction to the blockchain.

Here’s an example. Say I want to atomic swap an asset named Bubblegum (asset ID 15431290 on TestNet) for an asset named PizzaCoin (asset ID 15413447 on TestNet), and I’ve told the person I’m trading with that I’m willing to trade 1 Bubblegum for 50 PizzaCoin. Here’s what that looks like in a JavaScript client. Note: in order for this to work, the wallets receiving the assets need to be opted in to that asset!

// setup needed variables
let tx1 = {};
let tx2 = {};
let signedTx1 = {};
let signedTx2 = {};
let txGroup = [];

AlgoSigner.connect()
    .then(() => AlgoSigner.algod({ 
        ledger: 'TestNet', 
        path: '/v2/transactions/params'
    }))
    .then((txParams) => {
        let pizzaCoinRecipient = '{wallet address #1}';
        let bubblegumRecipient = '{wallet address #2}';

        tx1 = {
            assetIndex: '15431290', // Bubblegum
            from: pizzaCoinRecipient,
            amount: 1,
            to: bubblegumRecipient,
            type: 'axfer',
            fee: txParams['min-fee'],
            firstRound: txParams['last-round'],
            lastRound: txParams['last-round'] + 1000,
            genesisID: txParams['genesis-id'],
            genesisHash: txParams['genesis-hash'],
            flatFee: true
        };

        tx2 = {
            assetIndex: '15413447', // PizzaCoin
            from: bubblegumRecipient,
            amount: 50,
            to: pizzaCoinRecipient,
            type: 'axfer',
            fee: txParams['min-fee'],
            firstRound: txParams['last-round'],
            lastRound: txParams['last-round'] + 1000,
            genesisID: txParams['genesis-id'],
            genesisHash: txParams['genesis-hash'],
            flatFee: true
        };

        // assigns a group id to the transaction set
        return algosdk.assignGroupID([tx1, tx2]);
    })
    .then((txGroup) => {
        // Modify the group fields in original transactions to be base64 encoded strings
        tx1.group = txGroup[0].group.toString('base64');
        tx2.group = txGroup[1].group.toString('base64');
    })
    // sign transaction 1
    .then(() => AlgoSigner.sign(tx1))
    .then((d) => signedTx1 = d)
    // sign transaction 2
    .then(() => AlgoSigner.sign(tx2))
    .then((d) => signedTx2 = d)
    .then(() => {
        // Get the decoded binary Uint8Array values from the blobs
        const decoded_1 = new Uint8Array(atob(signedTx1.blob).split("").map(x => x.charCodeAt(0)));
        const decoded_2 = new Uint8Array(atob(signedTx2.blob).split("").map(x => x.charCodeAt(0)));

        // Use their combined length to create a 3rd array
        let combined_decoded_txns = new Uint8Array(decoded_1.byteLength + decoded_2.byteLength);

        // Starting at the 0 position, fill in the binary for the first object
        combined_decoded_txns.set(new Uint8Array(decoded_1), 0);

        // Starting at the first object byte length, fill in the 2nd binary value
        combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength);

        // Modify our combined array values back to an encoded 64bit string
        const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns));

        return AlgoSigner.send({
            ledger: 'TestNet',
            tx: grouped_txns
        });
    })
    // wait for confirmation from the blockchain
    .then((tx) => waitForAlgosignerConfirmation(tx)) // see algosignerutils.js
    .then((tx) => {
        // our transaction was successful, we can now view it on the blockchain 
    }
    .catch((e) => 
    { 
        // handle errors and perform error cleanup here
        console.error(e); 
    });

Interacting with Smart Contracts

Smart Contracts are small programs that serve various functions on the blockchain. This demo covers three such programs: the limit order contract, the hash time lock contract, and the split contract.

Two important things to understand about Smart Contracts in general:

  1. A Smart Contract is itself a destination on the blockchain. You send ALGOs and ASAs to it like you would any other wallet.
  2. A Smart Contract exists on the blockchain from the moment you call the method in the SDK creating it - you don’t need to send a transaction creating the contract. After all, how can you send a transaction to an address that doesn’t exist? Because of this, you’re able to reference the contract address even before you create your first transaction sending assets to the contract.

Note: this part of the demo is quite a bit different from the last part. The smart contracts in this demo are run server-side using Node.js, not client-side, and so each example contract has code on the client making a call to a local server to process the contract.

In each of these implementations, the contract logic is split into a “create” step and an “execute” step, so that we can leave room for a client, like AlgoSigner, to sign certain key parts of the contract. You can call each step using AJAX on the client side as soon as you have the necessary inputs and values. Look over the demo’s source code to see how the server and client interact with each other.

Limit Order

A limit order contract is a smart contract that exchanges ALGO for an Algorand Standard Asset (ASA), which guarantees the exchange rate between ALGO and the asset.

This particular demo revolves around a fungible ASA called Bubblegum (BUBBLE). In the demo, you pay 1,000,000 microAlgos (1 ALGO) to receive 1 BUBBLE. Think of it as putting an ALGO “token” in a dispenser and receiving a “gumball” in return.

How it Works

  1. First, the contract is created with the defined exchange rate (1 ALGO to 1 BUBBLE). In the demo, it’s created using the createBubblegumLimitContract method in limitorder.js. This method returns the address of the created contract on the blockchain.
  2. Next, the owner of the BUBBLE asset needs to deposit enough microAlgos (hereafter referred to as µAlgos) into the account to cover both the minimum balance (see Assets Overview for more information) and the fees for the transactions they’re about to make. The owner also needs to send an opt-in transaction to the recipient of the BUBBLE asset in order for them to receive any BUBBLE.
  3. After that, the person who is receiving the BUBBLE needs to pay for it.
  4. When the person receiving the BUBBLE has paid for it, the server executes the contract by reading the contract program from storage and submitting a transaction that executes the contract.

Below is some Node.js illustrating how to create and execute this contract. In the demo, both of these steps are designed to be called from a client. So, you would call the “create” step, have it set up the contract, then send a transaction paying for the asset on the client side. Once the transaction that pays for the asset is confirmed by the blockchain, only then would you call the “execute” step. If all this was successful, the assets should be swapped; you should be able to confirm this by looking up the transaction that was returned on AlgoExplorer.

Create Step

// import the needed modules
const algosdk = require('algosdk');
const fs = require('fs').promises;
const limitTemplate = require("algosdk/src/logicTemplates/limitorder");

const token = {
    'X-API-Key': `YOUR PURESTAKE API KEY HERE`
}
const server = 'https://testnet-algorand.api.purestake.io/ps2';
const port = '';

// create the client and get the suggested params
let algodClient = new algosdk.Algodv2(token, server, port); // create the client
let txParams = await algodClient.getTransactionParams().do(); // get the params

// program inputs
let contractOwner = '{wallet that owns the contract}';
let ratn = parseInt(1); // trade 1 BUBBLE
let ratd = parseInt(1000000); // for 1 Algo
let assetID = 15431290; // ID of the BUBBLE asset
let minTrade = 999999; // minimum number of microAlgos to accept
let expiryRound = txParams.lastRound + parseInt(10000);
let maxFee = 2000; // we set the max fee to avoid account bleed from excessive fees

// create the limit contract template
let limit = new limitTemplate.LimitOrder(contractOwner, assetID, ratn, ratd, expiryRound, minTrade, maxFee);

// if you want to use the contract later, you should write it to a file on the server or to a database field, etc.
// here, we're going to write it to a file

try {
    await fs.access(`static/contracts/`);
} catch (e) {
    await fs.mkdir(`static/contracts/`);
}

await fs.writeFile(`static/contracts/${address}`, limit.getProgram());

// reconstitute the account that owns the BUBBLE asset so we can trade it out
let bubblegumAccountMnemonic =
  "soft cloth blanket account dwarf title initial sweet retreat kiwi " +
  "minor maximum jaguar athlete excess sound ridge slow palm bid tackle " +
  "honey analyst absent clarify";
let assetOwner = algosdk.mnemonicToSecretKey(bubblegumAccountMnemonic); 

// construct the transaction
let note = algosdk.encodeObj("Contract funding transaction"); // optional note
let fundingTx = algosdk.makePaymentTxnWithSuggestedParams(assetOwner.addr,
  address, 100000 + (1000 * 2), undefined, note, txParams);
let signedFundingTx = fundingTx.signTxn(assetOwner.sk);
let resultTx = (await algodClient.sendRawTransaction(signedFundingTx).do());

// at this point, you should wait for confirmation that the transaction has posted
// there are different ways to do this; refer to algoutils.js for an example.

return limit.getAddress();

Execute Step

// import the needed modules
const algosdk = require('algosdk');
const fs = require('fs').promises;
const limitTemplate = require("algosdk/src/logicTemplates/limitorder");

const token = {
    'X-API-Key': `YOUR PURESTAKE API KEY HERE`
}
const server = 'https://testnet-algorand.api.purestake.io/ps2';
const port = '';

// create the client and get the suggested params
let algodClient = new algosdk.Algodv2(token, server, port);
let txParams = await algodClient.getTransactionParams().do();

// read the TEAL program you saved earlier from local storage
const data = await fs.readFile(`static/contracts/${contractAddress}`);
let limitProgram = data;

// reconstitute the account that owns the BUBBLE asset so we can trade it out
let bubblegumAccountMnemonic =
  "soft cloth blanket account dwarf title initial sweet retreat kiwi " +
  "minor maximum jaguar athlete excess sound ridge slow palm bid tackle " +
  "honey analyst absent clarify";
let assetOwner = algosdk.mnemonicToSecretKey(bubblegumAccountMnemonic);
let secretKey = assetOwner.sk;

// swap 1 BUBBLE for 1,000,000 microAlgos (+fee)
let txnBytes = limitTemplate.getSwapAssetsTransaction(limitProgram, 1, 1000000, secretKey, txParams.fee, txParams.firstRound, txParams.lastRound, txParams.genesisHash);

// send the transaction
let tx = (await algodClient.sendRawTransaction(txnBytes).do());

// wait for confirmation here

// return the transaction ID
return tx.txId;

Hash Time Lock Contract

A hash time lock contract is a smart contract that stores an amount of ALGO to send to a specific recipient and only unlocks if the password (the preimage) is supplied. Think of it like a lock box.

How it Works

  1. First, the contract is created. At this time, the recipient is defined in the contract. In the demo, the contract is created using the createHashTimeLockContract method in hashtimelock.js. This method returns the preimage that was used.
  2. Next, the contract needs to be funded with some ALGOs to be held in escrow. In the demo, we also deposit some extra µAlgos to cover the fees for both the transaction we’re about to make, and the “unlock” transaction that will take place when the contract is executed.
  3. Once the payment transaction is confirmed, we can now execute the contract. When we execute the contract, it will either pass and unlock the funds to the recipient, or fail, and the funds will fail to unlock. If the contract expires, the funds are closed out to the original sender.

Below is some Node.js illustrating how to create and execute this contract.

Create Step

// import the needed modules
const algosdk = require('algosdk');
const fs = require('fs').promises;
const htlcTemplate = require("algosdk/src/logicTemplates/htlc");
const crypto = require('crypto');
var randomWords = require('random-words');

const token = {
    'X-API-Key': `YOUR PURESTAKE API KEY HERE`
}
const server = 'https://testnet-algorand.api.purestake.io/ps2';
const port = '';

// create the client and get the suggested params
let algodClient = new algosdk.Algodv2(token, server, port); // create the client
let txParams = await algodClient.getTransactionParams().do(); // get the params

// program inputs
let contractOwner = '{wallet that owns the contract}';
let contractReceiver = '{wallet that is meant to receive the contents of the contract, but only if the preimage is supplied}';
let expiryRound = txParams.lastRound + parseInt(10000);
let maxFee = 2000; // we set the max fee to avoid account bleed from excessive fees

// generate the preimage and image using the random-words module (random-words is NOT CRYPTOGRAPHICALLY SECURE)
let hashFn = "sha256";
let strRandomWords = randomWords(8).join(' ');
let hashImg = crypto.createHash(hashFn).update(strRandomWords).digest('base64');

console.log(`Passphrase: ${strRandomWords}`);
console.log(`SHA-256 hash (image): ${hashImg}`);

// create the HTLC template
let htlc = new htlcTemplate.HTLC(contractOwner, contractReceiver, hashFn, hashImg, expiryRound, maxFee);

// if you want to use the contract later, you should write it to a file on the server or to a database field, etc.
// here, we're going to write it to a file

try {
    await fs.access(`static/contracts/`);
} catch (e) {
    await fs.mkdir(`static/contracts/`);
}

await fs.writeFile(`static/contracts/${address}`, htlc.getProgram());

// normally, you wouldn't return the passphrase to the client like this 
// this is just for demonstration purposes
return [address, strRandomWords];  

Execute Step

// import the needed modules
const algosdk = require('algosdk');
const fs = require('fs').promises;
const limitTemplate = require("algosdk/src/logicTemplates/limitorder");

const token = {
    'X-API-Key': `YOUR PURESTAKE API KEY HERE`
}
const server = 'https://testnet-algorand.api.purestake.io/ps2';
const port = '';

// create the client and get the suggested params
let algodClient = new algosdk.Algodv2(token, server, port);
let txParams = await algodClient.getTransactionParams().do();

let contractAddress = "{the address of the HTLC created earlier}";
let preimageBase64 = Buffer.from('PASSPHRASE GOES HERE').toString('base64');

// read the TEAL program you saved earlier from local storage
const data = await fs.readFile(`static/contracts/${contractAddress}`);
let htlcProgram = data;

// create a transaction closing out the HTLC to the recipient
// this will fail if the preimage isn't correct
let txn = {
  "from": contractAddress,
  "to": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ", // zero account
  "fee": 1,
  "type": "pay",
  "amount": 0,
  "firstRound": txParams.firstRound,
  "lastRound": txParams.lastRound,
  "genesisID": txParams.genesisID,
  "genesisHash": txParams.genesisHash,
  "closeRemainderTo": "{the recipient wallet address}"
};

// Create and submit logic signed transaction
let args = [Buffer.from(preimageBase64, 'base64').toString('ascii')]; // convert to ASCII
let lsig = algosdk.makeLogicSig(htlcProgram, args); // create the logic signature
let rawSignedTxn = algosdk.signLogicSigTransaction(txn, lsig); // sign the transaction with the logic signature
let tx = (await algodClient.sendRawTransaction(rawSignedTxn.blob).do());

// wait for confirmation here

// return transaction ID if successful
return tx.txId;

Split Contract

A split contract is a smart contract that takes an amount of ALGO and splits it between two parties.

How it Works

  1. First, the contract is created with the defined recipients and ALGO proportions. In the demo, it’s created using the createSplitContract method in split.js. This method returns the address of the created contract on the blockchain.
  2. Next, the contract needs to be funded with an amount ALGOs to be split. In the demo, we also deposit some extra µAlgos to cover the fees for both the transaction we’re about to make, and the “split” transaction that will take place when the contract is executed.
  3. Once the payment transaction is confirmed, we can now execute the contract. When we execute the contract, it will split the total amount of ALGOs into two “piles”, one for each recipient, based on the ratio that was defined when the contract was created.

Below is some Node.js illustrating how to create and execute this contract. In this example, we’re splitting 40,000 µAlgos between two people: the first person is getting 60%, and the second person is getting 40%. Notice that we don’t provide the value “40,000 µAlgos” until the execute step. When we create the contract, it is just some code that says “I want to give 60% of whatever funds I receive to address A, and 40% of whatever funds I receive to address B.”

Create Step

// import the needed modules
const algosdk = require('algosdk');
const fs = require('fs').promises;
const splitTemplate = require("algosdk/src/logicTemplates/split");

const token = {
    'X-API-Key': `YOUR PURESTAKE API KEY HERE`
}
const server = 'https://testnet-algorand.api.purestake.io/ps2';
const port = '';

// create the client and get the suggested params
let algodClient = new algosdk.Algodv2(token, server, port); // create the client
let txParams = await algodClient.getTransactionParams().do(); // get the params

// INPUTS
let receivers = ['{wallet address #1}', '{wallet address #2}']; // parties splitting the funds
let ratn = parseInt(60); // first "pile"
let ratd = parseInt(40); // second "pile"
let expiryRound = txParams.lastRound + parseInt(10000);
let minPay =  3000; // minimum amount to split
let maxFee =  2000; // we set the max fee to avoid account bleed from excessive fees

// create the split contract template
let split =  new splitTemplate.Split(sender, receivers[0], receivers[1], ratn, ratd, expiryRound, minPay, maxFee);

// if you want to use the contract later, you should write it to a file on the server or to a database field, etc.
// here, we're going to write it to a file

try {
    await fs.access(`static/contracts/`);
} catch (e) {
    await fs.mkdir(`static/contracts/`);
}

await fs.writeFile(`static/contracts/${address}`, split.getProgram());

// return the split contract's address on the blockchain
return address;

Execute Step

// import the needed modules
const algosdk = require('algosdk');
const fs = require('fs').promises;
const limitTemplate = require("algosdk/src/logicTemplates/limitorder");

const token = {
    'X-API-Key': `YOUR PURESTAKE API KEY HERE`
}
const server = 'https://testnet-algorand.api.purestake.io/ps2';
const port = '';

// create the client and get the suggested params
let algodClient = new algosdk.Algodv2(token, server, port);
let txParams = await algodClient.getTransactionParams().do();

let contractAddress = "{the address of the split contract created earlier}";

// read the TEAL program you saved earlier from local storage
const data = await fs.readFile(`static/contracts/${contractAddress}`);
let splitProgram = data;

// split 40,000 microAlgos
let txnBytes = splitTemplate.getSplitFundsTransaction(splitProgram, 40000, txParams.firstRound, txParams.lastRound, txParams.fee, txParams.genesisHash);
let tx = (await algodClient.sendRawTransaction(txnBytes).do());

// wait for confirmation here

// return the transaction ID
return tx.txId;

Summary

This is a lot to take in, for sure. We covered how to send both ALGO and ASA transactions, atomic transfers, and three different types of smart contracts! This is just a glimpse into the extreme utility and capability of the Algorand blockchain. It takes a while to learn, but if you’re focused and diligent, you’ll be making great dApps with the best of them. Take care.