Repository: algorand/js-algorand-sdk
Pull request #413 (2021-08-03 18:58:31)

DO NOT MERGE - Release/v1.11.0
# 1.11.0

## Added

- Signing support for rekeying to LogicSig/MultiSig account


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..8800316
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,30 @@
+---
+name: "\U0001F41C Bug report"
+about: Report a reproducible bug.
+title: ''
+labels: new-bug
+assignees: ''
+
+---
+
+### Subject of the issue
+
+<!-- Describe your issue here. -->
+
+### Your environment
+
+<!--
+* Software version: `algod -v`
+* Node status if applicable: `goal node status`
+* Operating System details. 
+* In many cases log files and cadaver files are also useful to include. Since these files may be large, an Algorand developer may request them later. These files may include public addresses that you're participating with. If that is a concern please be sure to scrub that data.
+-->
+
+### Steps to reproduce
+
+1.
+2.
+
+### Expected behaviour
+
+### Actual behaviour
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..7a16b83
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: "\U0001F514 Feature Request"
+about: Suggestions for how we can improve the algorand platform.
+title: ''
+labels: new-feature-request
+assignees: ''
+---
+
+## Problem
+
+<!-- What is the problem that we’re trying to solve? -->
+
+## Solution
+
+<!-- Do you have a potential/suggested solution? Document more than one if possible. -->
+
+## Dependencies
+
+<!-- Does the solution have any team or design dependencies? -->
+
+## Urgency
+
+<!-- What is the urgency here and why? -->
diff --git a/.prettierignore b/.prettierignore
index ab32208..008c450 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -2,3 +2,4 @@ dist
 tests/cucumber/features
 tests/cucumber/browser/build
 tests/browser/bundle.*
+.github
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3246331..3239fb4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 1.11.0
+
+## Added
+
+- Signing support for rekeying to LogicSig/MultiSig account
+
 # 1.10.1
 
 ## Added
diff --git a/package-lock.json b/package-lock.json
index 332f76c..490bef8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "algosdk",
-  "version": "1.10.1",
+  "version": "1.11.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "algosdk",
-      "version": "1.10.1",
+      "version": "1.11.0",
       "license": "MIT",
       "dependencies": {
         "algo-msgpack-with-bigint": "^2.1.1",
diff --git a/package.json b/package.json
index 7826468..0ab82b4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "algosdk",
-  "version": "1.10.1",
+  "version": "1.11.0",
   "description": "The official JavaScript SDK for Algorand",
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
diff --git a/src/logic/logic.ts b/src/logic/logic.ts
index 35bda56..9d8d60e 100644
--- a/src/logic/logic.ts
+++ b/src/logic/logic.ts
@@ -261,7 +261,7 @@ export function readProgram(
  * @throws
  * @returns true if success
  */
-export function checkProgram(program: Uint8Array, args: Uint8Array[]) {
+export function checkProgram(program: Uint8Array, args?: Uint8Array[]) {
   const [, , success] = readProgram(program, args);
   return success;
 }
diff --git a/src/logicsig.ts b/src/logicsig.ts
index 9436be8..c91b56a 100644
--- a/src/logicsig.ts
+++ b/src/logicsig.ts
@@ -7,8 +7,9 @@ import * as utils from './utils/utils';
 import * as txnBuilder from './transaction';
 import {
   EncodedLogicSig,
+  EncodedLogicSigAccount,
   EncodedMultisig,
-  EncodedTransaction,
+  EncodedSignedTransaction,
 } from './types/transactions/encoded';
 import { MultisigMetadata } from './types/multisig';
 
@@ -32,30 +33,30 @@ export class LogicSig implements LogicSigStorageStructure {
 
   constructor(
     program: Uint8Array,
-    bufferOrUint8ArrArgs: Array<Uint8Array | Buffer> | undefined
+    programArgs?: Array<Uint8Array | Buffer> | null
   ) {
+    if (
+      programArgs &&
+      (!Array.isArray(programArgs) ||
+        !programArgs.every(
+          (arg) => arg.constructor === Uint8Array || Buffer.isBuffer(arg)
+        ))
+    ) {
+      throw new TypeError('Invalid arguments');
+    }
+
     let args: Uint8Array[] | undefined;
-    if (typeof bufferOrUint8ArrArgs !== 'undefined')
-      args = bufferOrUint8ArrArgs.map((arg) => new Uint8Array(arg));
+    if (programArgs != null)
+      args = programArgs.map((arg) => new Uint8Array(arg));
 
     if (!logic.checkProgram(program, args)) {
       throw new Error('Invalid program');
     }
 
-    function checkType(arg: any) {
-      return arg.constructor === Uint8Array || Buffer.isBuffer(arg);
-    }
-
-    if (args && (!Array.isArray(args) || !args.every(checkType))) {
-      throw new TypeError('Invalid arguments');
-    }
-
-    Object.assign(this, {
-      logic: program,
-      args,
-      sig: undefined,
-      msig: undefined,
-    });
+    this.logic = program;
+    this.args = args;
+    this.sig = undefined;
+    this.msig = undefined;
   }
 
   // eslint-disable-next-line camelcase
@@ -126,8 +127,8 @@ export class LogicSig implements LogicSigStorageStructure {
    * @param secretKey - Secret key to sign with
    * @param msig - Multisig account as \{version, threshold, addrs\}
    */
-  sign(secretKey: Uint8Array, msig: MultisigMetadata) {
-    if (msig === undefined) {
+  sign(secretKey: Uint8Array, msig?: MultisigMetadata) {
+    if (msig == null) {
       this.sig = this.signProgram(secretKey);
     } else {
       const subsigs = msig.addrs.map((addr) => ({
@@ -193,9 +194,162 @@ export class LogicSig implements LogicSigStorageStructure {
   }
 }
 
+/**
+ * Represents an account that can sign with a LogicSig program.
+ */
+export class LogicSigAccount {
+  lsig: LogicSig;
+  sigkey?: Uint8Array;
+
+  /**
+   * Create a new LogicSigAccount. By default this will create an escrow
+   * LogicSig account. Call `sign` or `signMultisig` on the newly created
+   * LogicSigAccount to make it a delegated account.
+   *
+   * @param program - The compiled TEAL program which contains the logic for
+   *   this LogicSig.
+   * @param args - An optional array of arguments for the program.
+   */
+  constructor(program: Uint8Array, args?: Array<Uint8Array | Buffer> | null) {
+    this.lsig = new LogicSig(program, args);
+    this.sigkey = undefined;
+  }
+
+  // eslint-disable-next-line camelcase
+  get_obj_for_encoding() {
+    const obj: EncodedLogicSigAccount = {
+      lsig: this.lsig.get_obj_for_encoding(),
+    };
+    if (this.sigkey) {
+      obj.sigkey = this.sigkey;
+    }
+    return obj;
+  }
+
+  // eslint-disable-next-line camelcase
+  static from_obj_for_encoding(encoded: EncodedLogicSigAccount) {
+    const lsigAccount = new LogicSigAccount(encoded.lsig.l, encoded.lsig.arg);
+    lsigAccount.lsig = LogicSig.from_obj_for_encoding(encoded.lsig);
+    lsigAccount.sigkey = encoded.sigkey;
+    return lsigAccount;
+  }
+
+  /**
+   * Encode this object into msgpack.
+   */
+  toByte() {
+    return encoding.encode(this.get_obj_for_encoding());
+  }
+
+  /**
+   * Decode a msgpack object into a LogicSigAccount.
+   * @param encoded - The encoded LogicSigAccount.
+   */
+  static fromByte(encoded: ArrayLike<any>) {
+    const decodedObj = encoding.decode(encoded) as EncodedLogicSigAccount;
+    return LogicSigAccount.from_obj_for_encoding(decodedObj);
+  }
+
+  /**
+   * Check if this LogicSigAccount has been delegated to another account with a
+   * signature.
+   *
+   * Note this function only checks for the presence of a delegation signature.
+   * To verify the delegation signature, use `verify`.
+   */
+  isDelegated() {
+    return !!(this.lsig.sig || this.lsig.msig);
+  }
+
+  /**
+   * Verifies this LogicSig's program and signatures.
+   * @returns true if and only if the LogicSig program and signatures are valid.
+   */
+  verify() {
+    const addr = this.address();
+    return this.lsig.verify(address.decodeAddress(addr).publicKey);
+  }
+
+  /**
+   * Get the address of this LogicSigAccount.
+   *
+   * If the LogicSig is delegated to another account, this will return the
+   * address of that account.
+   *
+   * If the LogicSig is not delegated to another account, this will return an
+   *  escrow address that is the hash of the LogicSig's program code.
+   */
+  address() {
+    if (this.lsig.sig && this.lsig.msig) {
+      throw new Error(
+        'LogicSig has too many signatures. At most one of sig or msig may be present'
+      );
+    }
+
+    if (this.lsig.sig) {
+      if (!this.sigkey) {
+        throw new Error('Signing key for delegated account is missing');
+      }
+      return address.encodeAddress(this.sigkey);
+    }
+
+    if (this.lsig.msig) {
+      const msigMetadata = {
+        version: this.lsig.msig.v,
+        threshold: this.lsig.msig.thr,
+        pks: this.lsig.msig.subsig.map((subsig) => subsig.pk),
+      };
+      return address.encodeAddress(address.fromMultisigPreImg(msigMetadata));
+    }
+
+    return this.lsig.address();
+  }
+
+  /**
+   * Turns this LogicSigAccount into a delegated LogicSig. This type of LogicSig
+   * has the authority to sign transactions on behalf of another account, called
+   * the delegating account. Use this function if the delegating account is a
+   * multisig account.
+   *
+   * @param msig - The multisig delegating account
+   * @param secretKey - The secret key of one of the members of the delegating
+   *   multisig account. Use `appendToMultisig` to add additional signatures
+   *   from other members.
+   */
+  signMultisig(msig: MultisigMetadata, secretKey: Uint8Array) {
+    this.lsig.sign(secretKey, msig);
+  }
+
+  /**
+   * Adds an additional signature from a member of the delegating multisig
+   * account.
+   *
+   * @param secretKey - The secret key of one of the members of the delegating
+   *   multisig account.
+   */
+  appendToMultisig(secretKey: Uint8Array) {
+    this.lsig.appendToMultisig(secretKey);
+  }
+
+  /**
+   * Turns this LogicSigAccount into a delegated LogicSig. This type of LogicSig
+   * has the authority to sign transactions on behalf of another account, called
+   * the delegating account. If the delegating account is a multisig account,
+   * use `signMultisig` instead.
+   *
+   * @param secretKey - The secret key of the delegating account.
+   */
+  sign(secretKey: Uint8Array) {
+    this.lsig.sign(secretKey);
+    this.sigkey = nacl.keyPairFromSecretKey(secretKey).publicKey;
+  }
+}
+
 /**
  * makeLogicSig creates LogicSig object from program and arguments
  *
+ * @deprecated Use new LogicSigAccount(...) instead
+ *
  * @param program - Program to make LogicSig from
  * @param args - Arguments as array of Uint8Array
  * @returns LogicSig object
@@ -204,61 +358,91 @@ export function makeLogicSig(program: Uint8Array, args?: Uint8Array[]) {
   return new LogicSig(program, args);
 }
 
-/**
- * signLogicSigTransactionObject takes transaction.Transaction and a LogicSig object and returns a logicsig
- * transaction which is a blob representing a transaction and logicsig object.
- * @param txn - transaction.Transaction
- * @param lsig - logicsig object
- * @returns Object containing txID and blob representing signed transaction.
- */
-export function signLogicSigTransactionObject(
+function signLogicSigTransactionWithAddress(
   txn: txnBuilder.Transaction,
-  lsig: LogicSig
+  lsig: LogicSig,
+  lsigAddress: Uint8Array
 ) {
-  const lstx: {
-    lsig: EncodedLogicSig;
-    txn: EncodedTransaction;
-    sgnr?: Buffer;
-  } = {
+  if (!lsig.verify(lsigAddress)) {
+    throw new Error(
+      'Logic signature verification failed. Ensure the program and signature are valid.'
+    );
+  }
+
+  const signedTxn: EncodedSignedTransaction = {
     lsig: lsig.get_obj_for_encoding(),
     txn: txn.get_obj_for_encoding(),
   };
 
-  const isDelegated = lsig.sig || lsig.msig;
-  if (isDelegated) {
-    if (!lsig.verify(txn.from.publicKey)) {
-      throw new Error(
-        "Logic signature verification failed. Ensure the program is valid and the transaction sender is the program's delegated address."
-      );
-    }
-  } else {
-    // add AuthAddr if signing with a different program than From indicates for non-delegated LogicSig
-    const programAddr = lsig.address();
-    if (programAddr !== address.encodeAddress(txn.from.publicKey)) {
-      lstx.sgnr = Buffer.from(address.decodeAddress(programAddr).publicKey);
-    }
+  if (!nacl.bytesEqual(lsigAddress, txn.from.publicKey)) {
+    signedTxn.sgnr = Buffer.from(lsigAddress);
   }
 
   return {
     txID: txn.txID().toString(),
-    blob: encoding.encode(lstx),
+    blob: encoding.encode(signedTxn),
   };
 }
 
 /**
- * signLogicSigTransaction takes a raw transaction and a LogicSig object and returns a logicsig
- * transaction which is a blob representing a transaction and logicsig object.
- * @param txn - containing constructor arguments for a transaction
- * @param lsig -  logicsig object
+ * signLogicSigTransactionObject takes a transaction and a LogicSig object and
+ * returns a signed transaction.
+ *
+ * @param txn - The transaction to sign.
+ * @param lsigObject - The LogicSig object that will sign the transaction.
+ *
+ * @returns Object containing txID and blob representing signed transaction.
+ */
+export function signLogicSigTransactionObject(
+  txn: txnBuilder.Transaction,
+  lsigObject: LogicSig | LogicSigAccount
+) {
+  let lsig: LogicSig;
+  let lsigAddress: Uint8Array;
+
+  if (lsigObject instanceof LogicSigAccount) {
+    lsig = lsigObject.lsig;
+    lsigAddress = address.decodeAddress(lsigObject.address()).publicKey;
+  } else {
+    lsig = lsigObject;
+
+    if (lsig.sig) {
+      // For a LogicSig with a non-multisig delegating account, we cannot derive
+      // the address of that account from only its signature, so assume the
+      // delegating account is the sender. If that's not the case, the signing
+      // will fail.
+      lsigAddress = txn.from.publicKey;
+    } else if (lsig.msig) {
+      const msigMetadata = {
+        version: lsig.msig.v,
+        threshold: lsig.msig.thr,
+        pks: lsig.msig.subsig.map((subsig) => subsig.pk),
+      };
+      lsigAddress = address.fromMultisigPreImg(msigMetadata);
+    } else {
+      lsigAddress = address.decodeAddress(lsig.address()).publicKey;
+    }
+  }
+
+  return signLogicSigTransactionWithAddress(txn, lsig, lsigAddress);
+}
+
+/**
+ * signLogicSigTransaction takes a transaction and a LogicSig object and returns
+ * a signed transaction.
+ *
+ * @param txn - The transaction to sign.
+ * @param lsigObject - The LogicSig object that will sign the transaction.
+ *
  * @returns Object containing txID and blob representing signed transaction.
  * @throws error on failure
  */
 export function signLogicSigTransaction(
   txn: txnBuilder.TransactionLike,
-  lsig: LogicSig
+  lsigObject: LogicSig | LogicSigAccount
 ) {
   const algoTxn = txnBuilder.instantiateTxnIfNeeded(txn);
-  return signLogicSigTransactionObject(algoTxn, lsig);
+  return signLogicSigTransactionObject(algoTxn, lsigObject);
 }
 
 /**
@@ -303,9 +487,7 @@ export function tealSignFromProgram(
   data: Uint8Array | Buffer,
   program: Uint8Array
 ) {
-  const lsig = makeLogicSig(program);
+  const lsig = new LogicSig(program);
   const contractAddress = lsig.address();
   return tealSign(sk, data, contractAddress);
 }
-
-export default LogicSig;
diff --git a/src/main.ts b/src/main.ts
index 5442799..c338c1d 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -7,7 +7,7 @@ import * as LogicTemplatesCommonJSExport from './logicTemplates';
 import Bid, { BidOptions } from './bid';
 import * as convert from './convert';
 import * as utils from './utils/utils';
-import AnyTransaction from './types/transactions';
+import AnyTransaction, { EncodedSignedTransaction } from './types/transactions';
 import { MultisigMetadata } from './types/multisig';
 
 const SIGN_BYTES_PREFIX = Buffer.from([77, 88]); // "MX"
@@ -44,8 +44,8 @@ export function signTransaction(
   const algoTxn = txnBuilder.instantiateTxnIfNeeded(txn);
 
   return {
-    txID: (algoTxn as txnBuilder.Transaction).txID().toString(),
-    blob: (algoTxn as txnBuilder.Transaction).signTxn(sk),
+    txID: algoTxn.txID().toString(),
+    blob: algoTxn.signTxn(sk),
   };
 }
 
@@ -117,16 +117,7 @@ export function signMultisigTransaction(
     threshold,
     addrs,
   });
-  if (Object.prototype.hasOwnProperty.call(txn, 'from')) {
-    const actualSender =
-      typeof txn.from === 'string'
-        ? txn.from
-        : address.encodeAddress(txn.from.publicKey);
-
-    if (actualSender !== expectedFromRaw) {
-      throw new Error(MULTISIG_BAD_SENDER_ERROR_MSG);
-    }
-  } else {
+  if (!Object.prototype.hasOwnProperty.call(txn, 'from')) {
     // eslint-disable-next-line no-param-reassign
     txn.from = expectedFromRaw;
   }
@@ -180,7 +171,9 @@ export function appendSignMultisigTransaction(
 ) {
   const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey);
   // obtain underlying txn, sign it, and merge it
-  const multisigTxObj: any = encoding.decode(multisigTxnBlob);
+  const multisigTxObj = encoding.decode(
+    multisigTxnBlob
+  ) as EncodedSignedTransaction;
   const msigTxn = multisig.MultisigTransaction.from_obj_for_encoding(
     multisigTxObj.txn
   );
@@ -260,6 +253,7 @@ export {
 } from './convert';
 export { computeGroupID, assignGroupID } from './group';
 export {
+  LogicSigAccount,
   makeLogicSig,
   signLogicSigTransaction,
   signLogicSigTransactionObject,
diff --git a/src/multisig.ts b/src/multisig.ts
index fb0f8b5..6a4dace 100644
--- a/src/multisig.ts
+++ b/src/multisig.ts
@@ -7,7 +7,7 @@ import { EncodedTransaction } from './types/transactions';
 import { MultisigMetadata } from './types/multisig';
 import {
   EncodedMultisig,
-  EncodedMultisigBlob,
+  EncodedSignedTransaction,
 } from './types/transactions/encoded';
 
 /**
@@ -18,12 +18,12 @@ export const MULTISIG_MERGE_LESSTHANTWO_ERROR_MSG =
   'Not enough multisig transactions to merge. Need at least two';
 export const MULTISIG_MERGE_MISMATCH_ERROR_MSG =
   'Cannot merge txs. txIDs differ';
+export const MULTISIG_MERGE_MISMATCH_AUTH_ADDR_MSG =
+  'Cannot merge txs. Auth addrs differ';
 export const MULTISIG_MERGE_WRONG_PREIMAGE_ERROR_MSG =
   'Cannot merge txs. Multisig preimages differ';
 export const MULTISIG_MERGE_SIG_MISMATCH_ERROR_MSG =
   'Cannot merge txs. subsigs are mismatched.';
-const MULTISIG_BAD_FROM_FIELD_ERROR_MSG =
-  'The transaction from field and multisig preimage do not match.';
 const MULTISIG_KEY_NOT_EXIST_ERROR_MSG = 'Key does not exist';
 export const MULTISIG_NO_MUTATE_ERROR_MSG =
   'Cannot mutate a multisig field as it would invalidate all existing signatures.';
@@ -69,16 +69,32 @@ function createMultisigTransaction(
   if (keyExist === false) {
     throw new Error(MULTISIG_KEY_NOT_EXIST_ERROR_MSG);
   }
+
   const msig: EncodedMultisig = {
     v: version,
     thr: threshold,
     subsig: subsigs,
   };
-  const sTxn: EncodedMultisigBlob = {
+  const signedTxn: EncodedSignedTransaction = {
     msig,
     txn: txnForEncoding,
   };
-  return new Uint8Array(encoding.encode(sTxn));
+
+  // if the address of this multisig is different from the transaction sender,
+  // we need to add the auth-addr field
+  const msigAddr = address.fromMultisigPreImg({
+    version,
+    threshold,
+    pks,
+  });
+  if (
+    address.encodeAddress(txnForEncoding.snd) !==
+    address.encodeAddress(msigAddr)
+  ) {
+    signedTxn.sgnr = Buffer.from(msigAddr);
+  }
+
+  return new Uint8Array(encoding.encode(signedTxn));
 }
 
 /**
@@ -122,18 +138,6 @@ export class MultisigTransaction extends txnBuilder.Transaction {
     { version, threshold, pks }: MultisigMetadataWithPks,
     sk: Uint8Array
   ) {
-    const expectedFromRaw = address.fromMultisigPreImg({
-      version,
-      threshold,
-      pks,
-    });
-    if (
-      address.encodeAddress(this.from.publicKey) !==
-      address.encodeAddress(expectedFromRaw)
-    ) {
-      throw new Error(MULTISIG_BAD_FROM_FIELD_ERROR_MSG);
-    }
-
     // get signature verifier
     const myPk = nacl.keyPairFromSecretKey(sk).publicKey;
     return createMultisigTransaction(
@@ -160,18 +164,42 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) {
   if (multisigTxnBlobs.length < 2) {
     throw new Error(MULTISIG_MERGE_LESSTHANTWO_ERROR_MSG);
   }
-  const refSigTx = encoding.decode(multisigTxnBlobs[0]) as EncodedMultisigBlob;
-  const refSigAlgoTx = MultisigTransaction.from_obj_for_encoding(refSigTx.txn);
-  const refTxIDStr = refSigAlgoTx.txID().toString();
-  const from = address.encodeAddress(refSigTx.txn.snd);
+  const refSigTx = encoding.decode(
+    multisigTxnBlobs[0]
+  ) as EncodedSignedTransaction;
+  const refTxID = MultisigTransaction.from_obj_for_encoding(
+    refSigTx.txn
+  ).txID();
+  const refAuthAddr = refSigTx.sgnr
+    ? address.encodeAddress(refSigTx.sgnr)
+    : undefined;
+  const refPreImage = {
+    version: refSigTx.msig.v,
+    threshold: refSigTx.msig.thr,
+    pks: refSigTx.msig.subsig.map((subsig) => subsig.pk),
+  };
+  const refMsigAddr = address.encodeAddress(
+    address.fromMultisigPreImg(refPreImage)
+  );
 
   let newSubsigs = refSigTx.msig.subsig;
   for (let i = 0; i < multisigTxnBlobs.length; i++) {
-    const unisig = encoding.decode(multisigTxnBlobs[i]) as EncodedMultisigBlob;
+    const unisig = encoding.decode(
+      multisigTxnBlobs[i]
+    ) as EncodedSignedTransaction;
+
     const unisigAlgoTxn = MultisigTransaction.from_obj_for_encoding(unisig.txn);
-    if (unisigAlgoTxn.txID().toString() !== refTxIDStr) {
+    if (unisigAlgoTxn.txID() !== refTxID) {
       throw new Error(MULTISIG_MERGE_MISMATCH_ERROR_MSG);
     }
+
+    const authAddr = unisig.sgnr
+      ? address.encodeAddress(unisig.sgnr)
+      : undefined;
+    if (refAuthAddr !== authAddr) {
+      throw new Error(MULTISIG_MERGE_MISMATCH_AUTH_ADDR_MSG);
+    }
+
     // check multisig has same preimage as reference
     if (unisig.msig.subsig.length !== refSigTx.msig.subsig.length) {
       throw new Error(MULTISIG_MERGE_WRONG_PREIMAGE_ERROR_MSG);
@@ -181,9 +209,11 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) {
       threshold: unisig.msig.thr,
       pks: unisig.msig.subsig.map((subsig) => subsig.pk),
     };
-    if (from !== address.encodeAddress(address.fromMultisigPreImg(preimg))) {
+    const msgigAddr = address.encodeAddress(address.fromMultisigPreImg(preimg));
+    if (refMsigAddr !== msgigAddr) {
       throw new Error(MULTISIG_MERGE_WRONG_PREIMAGE_ERROR_MSG);
     }
+
     // now, we can merge
     newSubsigs = unisig.msig.subsig.map((uniSubsig, index) => {
       const current = refSigTx.msig.subsig[index];
@@ -218,11 +248,14 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) {
     thr: refSigTx.msig.thr,
     subsig: newSubsigs,
   };
-  const sTxn: EncodedMultisigBlob = {
+  const signedTxn: EncodedSignedTransaction = {
     msig,
     txn: refSigTx.txn,
   };
-  return new Uint8Array(encoding.encode(sTxn));
+  if (typeof refAuthAddr !== 'undefined') {
+    signedTxn.sgnr = Buffer.from(address.decodeAddress(refAuthAddr).publicKey);
+  }
+  return new Uint8Array(encoding.encode(signedTxn));
 }
 
 export function verifyMultisig(
diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts
index e762a64..80438ca 100644
--- a/src/types/transactions/encoded.ts
+++ b/src/types/transactions/encoded.ts
@@ -294,7 +294,14 @@ export interface EncodedTransaction {
 }
 
 export interface EncodedSubsig {
+  /**
+   *  The public key
+   */
   pk: Uint8Array;
+
+  /**
+   * The signature provided by the public key, if any
+   */
   s?: Uint8Array;
 }
 
@@ -321,6 +328,8 @@ export interface EncodedMultisig {
 
 /**
  * An encoded multisig blob that contains the encoded multisig as well as a copy of the encoded transaction
+ *
+ * @deprecated Use EncodedSignedTransaction instead.
  */
 export interface EncodedMultisigBlob {
   /**
@@ -341,6 +350,11 @@ export interface EncodedLogicSig {
   msig?: EncodedMultisig;
 }
 
+export interface EncodedLogicSigAccount {
+  lsig: EncodedLogicSig;
+  sigkey?: Uint8Array;
+}
+
 /**
  * A structure for an encoded signed transaction object
  */


Test patch:
diff --git a/tests/6.Multisig.ts b/tests/6.Multisig.ts
index b5624c7..d4b0e24 100644
--- a/tests/6.Multisig.ts
+++ b/tests/6.Multisig.ts
@@ -47,8 +47,8 @@ describe('Sample Multisig Info', () => {
 });
 
 describe('Multisig Functionality', () => {
-  describe('should generate correct partial signature', () => {
-    it('first partial sig should match golden main repo result', () => {
+  describe('signMultisigTransaction', () => {
+    it('should match golden main repo result', () => {
       const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
         from: sampleMultisigAddr,
         to: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI',
@@ -83,14 +83,9 @@ describe('Multisig Functionality', () => {
       assert.deepStrictEqual(Buffer.from(blob), expectedSignedTxn);
     });
 
-    it('second partial sig should match golden main repo result', () => {
-      const oneSigTxn = Buffer.from(
-        'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==',
-        'base64'
-      );
-
+    it('should correctly handle a different sender', () => {
       const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
-        from: sampleMultisigAddr,
+        from: 'EHGMQCXBIFBE364DEKWQVVNCTCTVCGQL3BR2Q5I7CFTRXWIVTF4SYA3GHU',
         to: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI',
         amount: 1000,
         note: new Uint8Array(Buffer.from('RSYiABhShvs=', 'base64')),
@@ -109,6 +104,31 @@ describe('Multisig Functionality', () => {
       const { txID, blob } = algosdk.signMultisigTransaction(
         txn,
         sampleMultisigParams,
+        sampleAccount1.sk
+      );
+
+      const expectedTxID =
+        'YQOXQNNO56WXQSU3IDUM2C4J7IZI6WMSCMKN5UCP5ZK6GWA6BXKQ';
+      assert.strictEqual(txID, expectedTxID);
+
+      const expectedSignedTxn = Buffer.from(
+        'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaRzZ25yxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCAhzMgK4UFCTfuDIq0K1aKYp1EaC9hjqHUfEWcb2RWZeaR0eXBlo3BheQ==',
+        'base64'
+      );
+      assert.deepStrictEqual(Buffer.from(blob), expectedSignedTxn);
+    });
+  });
+
+  describe('appendSignMultisigTransaction', () => {
+    it('should match golden main repo result', () => {
+      const oneSigTxn = Buffer.from(
+        'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==',
+        'base64'
+      );
+
+      const { txID, blob } = algosdk.appendSignMultisigTransaction(
+        oneSigTxn,
+        sampleMultisigParams,
         sampleAccount2.sk
       );
 
@@ -117,18 +137,33 @@ describe('Multisig Functionality', () => {
       assert.strictEqual(txID, expectedTxID);
 
       const expectedBlob = Buffer.from(
-        'gqRtc2lng6ZzdWJzaWeTgaJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXiConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEAQIbskY7Dq5x6d7P8SPojCoi+0D7IfIiWdtmDiST9GhVMHnTK/musH3QV5NTnn8qwDdcKifa6ujvg61ldXx9AEgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==',
+        'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQBAhuyRjsOrnHp3s/xI+iMKiL7QPsh8iJZ22YOJJP0aFUwedMr+a6wfdBXk1OefyrAN1wqJ9rq6O+DrWV1fH0ASBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5',
         'base64'
       );
       assert.deepStrictEqual(Buffer.from(blob), expectedBlob);
+    });
 
-      const mergedBlob = algosdk.mergeMultisigTransactions([blob, oneSigTxn]);
+    it('should correctly handle a different sender', () => {
+      const oneSigTxn = Buffer.from(
+        'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaRzZ25yxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCAhzMgK4UFCTfuDIq0K1aKYp1EaC9hjqHUfEWcb2RWZeaR0eXBlo3BheQ==',
+        'base64'
+      );
 
-      const expectedMergedBlob = Buffer.from(
-        'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQBAhuyRjsOrnHp3s/xI+iMKiL7QPsh8iJZ22YOJJP0aFUwedMr+a6wfdBXk1OefyrAN1wqJ9rq6O+DrWV1fH0ASBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5',
+      const { txID, blob } = algosdk.appendSignMultisigTransaction(
+        oneSigTxn,
+        sampleMultisigParams,
+        sampleAccount2.sk
+      );
+
+      const expectedTxID =
+        'YQOXQNNO56WXQSU3IDUM2C4J7IZI6WMSCMKN5UCP5ZK6GWA6BXKQ';
+      assert.strictEqual(txID, expectedTxID);
+
+      const expectedBlob = Buffer.from(
+        'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQPuH2WlM2x1tflA6LGMhKXBiuO7dsRzjXYPgcfjvDth9ZsCazyOKHqQmtYjD+pXLM8fJbrRhoUGTkyLxINiT9wCBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEICHMyArhQUJN+4MirQrVopinURoL2GOodR8RZxvZFZl5pHR5cGWjcGF5',
         'base64'
       );
-      assert.deepStrictEqual(Buffer.from(mergedBlob), expectedMergedBlob);
+      assert.deepStrictEqual(Buffer.from(blob), expectedBlob);
     });
   });
 
@@ -188,8 +223,10 @@ describe('Multisig Functionality', () => {
 
       assert.deepStrictEqual(Buffer.from(finMsigBlob), twoSigTxBlob);
     });
+  });
 
-    it('merging should be symmetric and match golden main repo result', () => {
+  describe('mergeMultisigTransactions', () => {
+    it('should be symmetric and match golden main repo result', () => {
       // prettier-ignore
       const oneAndThreeBlob = Buffer.from([130, 164, 109, 115, 105, 103, 131, 166, 115, 117, 98, 115, 105, 103, 147, 130, 162, 112, 107, 196, 32, 27, 126, 192, 176, 75, 234, 97, 183, 150, 144, 151, 230, 203, 244, 7, 225, 8, 167, 5, 53, 29, 11, 201, 138, 190, 177, 34, 9, 168, 171, 129, 120, 161, 115, 196, 64, 113, 61, 44, 215, 188, 9, 110, 249, 243, 107, 227, 105, 200, 124, 12, 209, 21, 155, 67, 225, 240, 42, 107, 19, 212, 242, 236, 251, 14, 157, 232, 202, 93, 76, 125, 237, 173, 175, 178, 41, 145, 52, 43, 74, 132, 202, 20, 132, 237, 142, 122, 248, 31, 104, 105, 253, 168, 172, 70, 227, 115, 249, 227, 13, 129, 162, 112, 107, 196, 32, 9, 99, 50, 9, 83, 115, 137, 240, 117, 103, 17, 119, 57, 145, 199, 208, 62, 27, 115, 200, 196, 245, 43, 246, 175, 240, 26, 162, 92, 249, 194, 113, 130, 162, 112, 107, 196, 32, 231, 240, 248, 77, 6, 129, 29, 249, 243, 28, 141, 135, 139, 17, 85, 244, 103, 29, 81, 161, 133, 194, 0, 144, 134, 103, 244, 73, 88, 112, 104, 161, 161, 115, 196, 64, 125, 39, 23, 127, 48, 75, 104, 78, 54, 53, 177, 125, 33, 29, 42, 49, 173, 205, 5, 9, 225, 99, 169, 16, 177, 95, 186, 134, 218, 129, 179, 15, 219, 90, 103, 54, 188, 25, 90, 235, 144, 137, 45, 35, 66, 53, 45, 183, 232, 27, 20, 50, 91, 219, 194, 187, 55, 146, 113, 126, 233, 186, 161, 15, 163, 116, 104, 114, 2, 161, 118, 1, 163, 116, 120, 110, 140, 163, 102, 101, 101, 206, 0, 3, 200, 192, 162, 102, 118, 206, 0, 14, 249, 218, 163, 103, 101, 110, 172, 100, 101, 118, 110, 101, 116, 45, 118, 51, 56, 46, 48, 162, 103, 104, 196, 32, 254, 179, 108, 57, 16, 20, 57, 0, 195, 218, 85, 66, 202, 24, 54, 176, 15, 210, 248, 25, 89, 18, 87, 205, 35, 246, 4, 47, 152, 200, 54, 157, 162, 108, 118, 206, 0, 14, 253, 194, 166, 115, 101, 108, 107, 101, 121, 196, 32, 50, 18, 43, 43, 214, 61, 220, 83, 49, 150, 23, 165, 170, 83, 196, 177, 194, 111, 227, 220, 202, 242, 141, 54, 34, 181, 105, 119, 161, 64, 92, 134, 163, 115, 110, 100, 196, 32, 141, 146, 180, 137, 144, 1, 115, 160, 77, 250, 67, 89, 163, 102, 106, 106, 252, 234, 44, 66, 160, 93, 217, 193, 247, 62, 235, 165, 71, 128, 55, 233, 164, 116, 121, 112, 101, 166, 107, 101, 121, 114, 101, 103, 167, 118, 111, 116, 101, 102, 115, 116, 206, 0, 13, 187, 160, 166, 118, 111, 116, 101, 107, 100, 205, 39, 16, 167, 118, 111, 116, 101, 107, 101, 121, 196, 32, 112, 27, 215, 251, 145, 43, 7, 179, 8, 17, 255, 40, 29, 159, 238, 149, 99, 229, 128, 46, 32, 38, 137, 35, 25, 37, 143, 119, 250, 147, 30, 136, 167, 118, 111, 116, 101, 108, 115, 116, 206, 0, 15, 66, 64]);
       // prettier-ignore
@@ -208,6 +245,32 @@ describe('Multisig Functionality', () => {
       assert.deepStrictEqual(Buffer.from(finMsigBlob), allThreeBlob);
       assert.deepStrictEqual(Buffer.from(finMsigBlobTwo), allThreeBlob);
     });
+
+    it('should correctly handle a different sender', () => {
+      const oneAndThreeBlob = Buffer.from(
+        'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgqJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGhc8RAQ3t8lAipybLbWaRa3EDKl0mLGwUpYzl0iBCHSflhJM6zboJiT10FnWtXqW0dUBzpj9yeqtSuSJyKb8Ml3YJkCqN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEICHMyArhQUJN+4MirQrVopinURoL2GOodR8RZxvZFZl5pHR5cGWjcGF5',
+        'base64'
+      );
+      const twoAndThreeBlob = Buffer.from(
+        'g6Rtc2lng6ZzdWJzaWeTgaJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXiConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxED7h9lpTNsdbX5QOixjISlwYrju3bEc412D4HH47w7YfWbAms8jih6kJrWIw/qVyzPHyW60YaFBk5Mi8SDYk/cAgqJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGhc8RAQ3t8lAipybLbWaRa3EDKl0mLGwUpYzl0iBCHSflhJM6zboJiT10FnWtXqW0dUBzpj9yeqtSuSJyKb8Ml3YJkCqN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEICHMyArhQUJN+4MirQrVopinURoL2GOodR8RZxvZFZl5pHR5cGWjcGF5',
+        'base64'
+      );
+      const allThreeBlob = Buffer.from(
+        'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQPuH2WlM2x1tflA6LGMhKXBiuO7dsRzjXYPgcfjvDth9ZsCazyOKHqQmtYjD+pXLM8fJbrRhoUGTkyLxINiT9wCConBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaFzxEBDe3yUCKnJsttZpFrcQMqXSYsbBSljOXSIEIdJ+WEkzrNugmJPXQWda1epbR1QHOmP3J6q1K5InIpvwyXdgmQKo3RocgKhdgGkc2ducsQgjZK0iZABc6BN+kNZo2ZqavzqLEKgXdnB9z7rpUeAN+mjdHhui6NhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0D6KJmds3zFaNnZW6sZGV2bmV0LXYzOC4womdoxCD+s2w5EBQ5AMPaVULKGDawD9L4GVkSV80j9gQvmMg2naJsds32/aRub3RlxAhFJiIAGFKG+6NyY3bEIHts4k/rW6zAsWTinCIsV/X2PcOH1DkEglhBHF/hD3wCo3NuZMQgIczICuFBQk37gyKtCtWimKdRGgvYY6h1HxFnG9kVmXmkdHlwZaNwYXk=',
+        'base64'
+      );
+
+      const finMsigBlob = algosdk.mergeMultisigTransactions([
+        new Uint8Array(twoAndThreeBlob),
+        new Uint8Array(oneAndThreeBlob),
+      ]);
+      const finMsigBlobTwo = algosdk.mergeMultisigTransactions([
+        new Uint8Array(oneAndThreeBlob),
+        new Uint8Array(twoAndThreeBlob),
+      ]);
+      assert.deepStrictEqual(Buffer.from(finMsigBlob), allThreeBlob);
+      assert.deepStrictEqual(Buffer.from(finMsigBlobTwo), allThreeBlob);
+    });
   });
 
   describe('read-only transaction methods should work as expected on multisig transactions', () => {
diff --git a/tests/8.LogicSig.js b/tests/8.LogicSig.js
deleted file mode 100644
index e34ae23..0000000
--- a/tests/8.LogicSig.js
+++ /dev/null
@@ -1,939 +0,0 @@
-const assert = require('assert');
-const algosdk = require('../index');
-const logic = require('../src/logic/logic');
-const utils = require('../src/utils/utils');
-
-describe('LogicSig functionality', () => {
-  describe('Basic logic sig', () => {
-    it('should work on valid program', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34]);
-      const programHash =
-        '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY';
-      const pk = algosdk.decodeAddress(programHash).publicKey;
-      let lsig = algosdk.makeLogicSig(program);
-      assert.equal(lsig.logic, program);
-      assert.equal(lsig.args, undefined);
-      assert.equal(lsig.sig, undefined);
-      assert.equal(lsig.msig, undefined);
-      assert.equal(lsig.address(), programHash);
-
-      let verified = lsig.verify(pk);
-      assert.equal(verified, true);
-
-      const args = [Uint8Array.from([1, 2, 3]), Uint8Array.from([4, 5, 6])];
-      lsig = algosdk.makeLogicSig(program, args);
-      assert.equal(lsig.logic, program);
-      assert.deepEqual(lsig.args, args);
-      assert.equal(lsig.sig, undefined);
-      assert.equal(lsig.msig, undefined);
-
-      verified = lsig.verify(pk);
-      assert.equal(verified, true);
-
-      // check serialization
-      const encoded = lsig.toByte();
-      const decoded = algosdk.logicSigFromByte(encoded);
-      assert.deepStrictEqual(decoded, lsig);
-    });
-    it('should fail on tampered program', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34]);
-      const programHash =
-        '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY';
-      const pk = algosdk.decodeAddress(programHash).publicKey;
-
-      program[3] = 2;
-      const lsig = algosdk.makeLogicSig(program);
-      const verified = lsig.verify(pk);
-      assert.equal(verified, false);
-    });
-    it('should fail on invalid program', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34]);
-      program[0] = 128;
-      assert.throws(() => algosdk.makeLogicSig(program));
-    });
-  });
-
-  describe('Signatures', () => {
-    it('should sign a basic transaction', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34]);
-      const lsig = algosdk.makeLogicSig(program);
-
-      const from = lsig.address();
-      const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM';
-      const fee = 10;
-      const amount = 847;
-      const firstRound = 51;
-      const lastRound = 61;
-      const note = new Uint8Array([123, 12, 200]);
-      const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=';
-      const genesisID = '';
-      const rekeyTo =
-        'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM';
-      let closeRemainderTo;
-      const txn = {
-        from,
-        to,
-        fee,
-        amount,
-        closeRemainderTo,
-        firstRound,
-        lastRound,
-        note,
-        genesisHash,
-        genesisID,
-        reKeyTo: rekeyTo,
-      };
-
-      const actual = algosdk.signLogicSigTransaction(txn, lsig);
-      const expected = {
-        txID: 'D7H6THOHOCEWJYNWMKHVOR2W36KAJXSGG6DMNTHTBWONBCG4XATA',
-        blob: new Uint8Array(
-          Buffer.from(
-            'gqRsc2lngaFsxAUBIAEBIqN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQg9nYtrHWxmX1sLJYYBoBQdJDXlREv/n+3YLJzivnH8a2kdHlwZaNwYXk=',
-            'base64'
-          )
-        ),
-      };
-
-      assert.deepStrictEqual(actual, expected);
-    });
-
-    it('should sign an already built transaction', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34]);
-      const lsig = algosdk.makeLogicSig(program);
-
-      const from = lsig.address();
-      const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM';
-      const fee = 10;
-      const amount = 847;
-      const firstRound = 51;
-      const lastRound = 61;
-      const note = new Uint8Array([123, 12, 200]);
-      const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=';
-      const genesisID = '';
-      const rekeyTo =
-        'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM';
-      let closeRemainderTo;
-      const suggestedParams = {
-        flatFee: false,
-        fee,
-        firstRound,
-        lastRound,
-        genesisHash,
-        genesisID,
-      };
-      const txnObject = {
-        from,
-        to,
-        amount,
-        suggestedParams,
-        closeRemainderTo,
-        note,
-        rekeyTo,
-      };
-      const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject(
-        txnObject
-      );
-
-      const actual = algosdk.signLogicSigTransaction(txn, lsig);
-      const expected = {
-        txID: 'D7H6THOHOCEWJYNWMKHVOR2W36KAJXSGG6DMNTHTBWONBCG4XATA',
-        blob: new Uint8Array(
-          Buffer.from(
-            'gqRsc2lngaFsxAUBIAEBIqN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQg9nYtrHWxmX1sLJYYBoBQdJDXlREv/n+3YLJzivnH8a2kdHlwZaNwYXk=',
-            'base64'
-          )
-        ),
-      };
-
-      assert.deepStrictEqual(actual, expected);
-    });
-
-    it('should sign a transaction with a different AuthAddr', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34]);
-      const lsig = algosdk.makeLogicSig(program);
-
-      const from = 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU';
-      const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM';
-      const fee = 10;
-      const amount = 847;
-      const firstRound = 51;
-      const lastRound = 61;
-      const note = new Uint8Array([123, 12, 200]);
-      const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=';
-      const genesisID = '';
-      const rekeyTo =
-        'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM';
-      let closeRemainderTo;
-      const txn = {
-        from,
-        to,
-        fee,
-        amount,
-        closeRemainderTo,
-        firstRound,
-        lastRound,
-        note,
-        genesisHash,
-        genesisID,
-        reKeyTo: rekeyTo,
-      };
-
-      const actual = algosdk.signLogicSigTransaction(txn, lsig);
-      const expected = {
-        txID: 'A6G4CMEV7QHLTMWDGU6BRYYVG3IXSSFTVDISEPALUHKIP4DNHQ4A',
-        blob: new Uint8Array(
-          Buffer.from(
-            'g6Rsc2lngaFsxAUBIAEBIqRzZ25yxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQguw62NBVKGAtqJ03XdSlcNtO6eq5rXbDMEMVGLbDzMN+kdHlwZaNwYXk=',
-            'base64'
-          )
-        ),
-      };
-
-      assert.deepStrictEqual(actual, expected);
-    });
-  });
-});
-
-describe('Logic validation', () => {
-  describe('Varint', () => {
-    it('should parse binary data correctly', () => {
-      let data = Uint8Array.from([1]);
-      let [value, length] = logic.parseUvarint(data);
-      assert.equal(length, 1);
-      assert.equal(value, 1);
-
-      data = Uint8Array.from([123]);
-      [value, length] = logic.parseUvarint(data);
-      assert.equal(length, 1);
-      assert.equal(value, 123);
-
-      data = Uint8Array.from([200, 3]);
-      [value, length] = logic.parseUvarint(data);
-      assert.equal(length, 2);
-      assert.equal(value, 456);
-    });
-  });
-  describe('Const blocks', () => {
-    it('should parse int const block correctly', () => {
-      const data = Uint8Array.from([32, 5, 0, 1, 200, 3, 123, 2]);
-      const size = logic.checkIntConstBlock(data, 0);
-      assert.equal(size, data.length);
-    });
-    it('should parse bytes const block correctly', () => {
-      const data = Uint8Array.from([
-        38,
-        2,
-        13,
-        49,
-        50,
-        51,
-        52,
-        53,
-        54,
-        55,
-        56,
-        57,
-        48,
-        49,
-        50,
-        51,
-        2,
-        1,
-        2,
-      ]);
-      const size = logic.checkByteConstBlock(data, 0);
-      assert.equal(size, data.length);
-    });
-    it('should parse int push op correctly', () => {
-      const data = Uint8Array.from([0x81, 0x80, 0x80, 0x04]);
-      const size = logic.checkPushIntOp(data, 0);
-      assert.strictEqual(size, data.length);
-    });
-    it('should parse byte push op correctly', () => {
-      const data = Uint8Array.from([
-        0x80,
-        0x0b,
-        0x68,
-        0x65,
-        0x6c,
-        0x6c,
-        0x6f,
-        0x20,
-        0x77,
-        0x6f,
-        0x72,
-        0x6c,
-        0x64,
-      ]);
-      const size = logic.checkPushByteOp(data, 0);
-      assert.strictEqual(size, data.length);
-    });
-  });
-  describe('Program checker', () => {
-    it('should assess correct programs right', () => {
-      let program = Uint8Array.from([1, 32, 1, 1, 34]);
-      let result = logic.checkProgram(program);
-      assert.equal(result, true);
-
-      result = logic.checkProgram(program, [Uint8Array.from('a' * 10)]);
-      assert.equal(result, true);
-
-      program = utils.concatArrays(program, Uint8Array.from('\x22' * 10));
-      result = logic.checkProgram(program, [Uint8Array.from('a' * 10)]);
-      assert.equal(result, true);
-    });
-    it('should fail on long input', () => {
-      assert.throws(() => logic.checkProgram(), new Error('empty program'));
-      let program = Uint8Array.from([1, 32, 1, 1, 34]);
-      assert.throws(
-        () => logic.checkProgram(program, [new Uint8Array(1000).fill(55)]),
-        new Error('program too long')
-      );
-
-      program = utils.concatArrays(program, new Uint8Array(1000).fill(34));
-      assert.throws(
-        () => logic.checkProgram(program),
-        new Error('program too long')
-      );
-    });
-    it('should fail on invalid program', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34, 255]);
-      assert.throws(
-        () => logic.checkProgram(program),
-        new Error('invalid instruction')
-      );
-    });
-    it('should fail on invalid args', () => {
-      const program = Uint8Array.from([1, 32, 1, 1, 34]);
-      assert.throws(
-        () => logic.checkProgram(program, '123'),
-        new Error('invalid arguments')
-      );
-    });
-    it('should fail on costly program', () => {
-      let program = Uint8Array.from([1, 38, 1, 1, 1, 40, 2]); // byte 0x01 + keccak256
-      let result = logic.checkProgram(program);
-      assert.equal(result, true);
-
-      // 10x keccak256 more is fine
-      program = utils.concatArrays(program, new Uint8Array(10).fill(2));
-      result = logic.checkProgram(program);
-      assert.equal(result, true);
-
-      // 800x keccak256 more is too costly
-      program = utils.concatArrays(program, new Uint8Array(800).fill(2));
-      //  old versions
-      const oldVersions = [0x1, 0x2, 0x3];
-      let i;
-      for (i = 0; i < oldVersions.length; i++) {
-        program[0] = oldVersions[i];
-        assert.throws(
-          () => logic.checkProgram(program),
-          new Error(
-            'program too costly for Teal version < 4. consider using v4.'
-          )
-        );
-      }
-      //  new versions
-      const newVersions = [0x4];
-      for (i = 0; i < newVersions.length; i++) {
-        program[0] = newVersions[i];
-        assert.ok(logic.checkProgram(program));
-      }
-    });
-    it('should support TEAL v2 opcodes', () => {
-      assert.ok(logic.langspecEvalMaxVersion >= 2);
-      assert.ok(logic.langspecLogicSigVersion >= 2);
-
-      // balance
-      let program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x60]); // int 0; balance
-      let result = logic.checkProgram(program);
-      assert.equal(result, true);
-
-      // app_opted_in
-      program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x61]); // int 0; int 0; app_opted_in
-      result = logic.checkProgram(program);
-      assert.equal(result, true);
-
-      // 800x keccak256 more is to costly
-      // prettier-ignore
-      program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x70, 0x00 ]); // int 0; int 0; asset_holding_get Balance
-      result = logic.checkProgram(program);
-      assert.equal(result, true);
-    });
-    it('should support TEAL v3 opcodes', () => {
-      assert.ok(logic.langspecEvalMaxVersion >= 3);
-      assert.ok(logic.langspecLogicSigVersion >= 3);
-
-      // min_balance
-      let program = Uint8Array.from([0x03, 0x20, 0x01, 0x00, 0x22, 0x78]); // int 0; min_balance
-      assert.ok(logic.checkProgram(program));
-
-      // pushbytes
-      program = Uint8Array.from([
-        0x03,
-        0x20,
-        0x01,
-        0x00,
-        0x22,
-        0x80,
-        0x02,
-        0x68,
-        0x69,
-        0x48,
-      ]); // int 0; pushbytes "hi"; pop
-      assert.ok(logic.checkProgram(program));
-
-      // pushint
-      program = Uint8Array.from([
-        0x03,
-        0x20,
-        0x01,
-        0x00,
-        0x22,
-        0x81,
-        0x01,
-        0x48,
-      ]); // int 0; pushint 1; pop
-      assert.ok(logic.checkProgram(program));
-
-      // swap
-      program = Uint8Array.from([
-        0x03,
-        0x20,
-        0x02,
-        0x00,
-        0x01,
-        0x22,
-        0x23,
-        0x4c,
-        0x48,
-      ]); // int 0; int 1; swap; pop
-      assert.ok(logic.checkProgram(program));
-    });
-    it('should support TEAL v4 opcodes', () => {
-      assert.ok(logic.langspecEvalMaxVersion >= 4);
-
-      // divmodw
-      let program = Uint8Array.from([
-        0x04,
-        0x20,
-        0x03,
-        0x01,
-        0x00,
-        0x02,
-        0x22,
-        0x81,
-        0xd0,
-        0x0f,
-        0x23,
-        0x24,
-        0x1f,
-      ]); // int 1; pushint 2000; int 0; int 2; divmodw
-      assert.ok(logic.checkProgram(program));
-
-      // gloads i
-      program = Uint8Array.from([0x04, 0x20, 0x01, 0x00, 0x22, 0x3b, 0x00]); // int 0; gloads 0
-      assert.ok(logic.checkProgram(program));
-
-      // callsub
-      program = Uint8Array.from([
-        0x04,
-        0x20,
-        0x02,
-        0x01,
-        0x02,
-        0x22,
-        0x88,
-        0x00,
-        0x02,
-        0x23,
-        0x12,
-        0x49,
-      ]); // int 1; callsub double; int 2; ==; double: dup;
-      assert.ok(logic.checkProgram(program));
-
-      // b>=
-      program = Uint8Array.from([
-        0x04,
-        0x26,
-        0x02,
-        0x01,
-        0x11,
-        0x01,
-        0x10,
-        0x28,
-        0x29,
-        0xa7,
-      ]); // byte 0x11; byte 0x10; b>=
-      assert.ok(logic.checkProgram(program));
-
-      // b^
-      program = Uint8Array.from([
-        0x04,
-        0x26,
-        0x03,
-        0x01,
-        0x11,
-        0x01,
-        0x10,
-        0x01,
-        0x01,
-        0x28,
-        0x29,
-        0xad,
-        0x2a,
-        0x12,
-      ]); // byte 0x11; byte 0x10; b>=
-      assert.ok(logic.checkProgram(program));
-
-      // callsub, retsub
-      program = Uint8Array.from([
-        0x04,
-        0x20,
-        0x02,
-        0x01,
-        0x02,
-        0x22,
-        0x88,
-        0x00,
-        0x03,
-        0x23,
-        0x12,
-        0x43,
-        0x49,
-        0x08,
-        0x89,
-      ]); // int 1; callsub double; int 2; ==; return; double: dup; +; retsub;
-      assert.ok(logic.checkProgram(program));
-
-      // loop
-      program = Uint8Array.from([
-        0x04,
-        0x20,
-        0x04,
-        0x01,
-        0x02,
-        0x0a,
-        0x10,
-        0x22,
-        0x23,
-        0x0b,
-        0x49,
-        0x24,
-        0x0c,
-        0x40,
-        0xff,
-        0xf8,
-        0x25,
-        0x12,
-      ]); // int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; ==
-      assert.ok(logic.checkProgram(program));
-    });
-  });
-});
-
-describe('Template logic validation', () => {
-  describe('Split', () => {
-    it('should match the goldens', () => {
-      // Inputs
-      const owner =
-        'WO3QIJ6T4DZHBX5PWJH26JLHFSRT7W7M2DJOULPXDTUS6TUX7ZRIO4KDFY';
-      const receivers = [
-        'W6UUUSEAOGLBHT7VFT4H2SDATKKSG6ZBUIJXTZMSLW36YS44FRP5NVAU7U',
-        'XCIBIN7RT4ZXGBMVAMU3QS6L5EKB7XGROC5EPCNHHYXUIBAA5Q6C5Y7NEU',
-      ];
-      const rat1 = 100;
-      const rat2 = 30;
-      const expiryRound = 123456;
-      const minPay = 10000;
-      const maxFee = 5000000;
-      const split = new algosdk.LogicTemplates.Split(
-        owner,
-        receivers[0],
-        receivers[1],
-        rat1,
-        rat2,
-        expiryRound,
-        minPay,
-        maxFee
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAIAcCWsQICAMDEBx5kkE4mAyCztwQn0+DycN+vsk+vJWcsoz/b7NDS6i33HOkvTpf+YiC3qUpIgHGWE8/1LPh9SGCalSN7IaITeeWSXbfsS5wsXyC4kBQ38Z8zcwWVAym4S8vpFB/c0XC6R4mnPi9EBADsPDEQIhIxASMMEDIEJBJAABkxCSgSMQcyAxIQMQglEhAxAiEEDRAiQAAuMwAAMwEAEjEJMgMSEDMABykSEDMBByoSEDMACCEFCzMBCCEGCxIQMwAIIQcPEBA=';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = split.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        'KPYGWKTV7CKMPMTLQRNGMEQRSYTYDHUOFNV4UDSBDLC44CLIJPQWRTCPBU';
-      assert.deepStrictEqual(goldenAddress, split.getAddress());
-    });
-  });
-  describe('HTLC', () => {
-    it('sha256 should match the goldens', () => {
-      // Inputs
-      const owner =
-        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
-      const receiver =
-        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
-      const hashFn = 'sha256';
-      const hashImg = 'EHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8=';
-      const expiryRound = 600000;
-      const maxFee = 1000;
-      const htlc = new algosdk.LogicTemplates.HTLC(
-        owner,
-        receiver,
-        hashFn,
-        hashImg,
-        expiryRound,
-        maxFee
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IBB2YRNPIfx8AiI9UKues2ALw//DcSQjoeR7sfmp2/VfIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQEpEhAxCSoSMQIlDRAREA==';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = htlc.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        'FBZIR3RWVT2BTGVOG25H3VAOLVD54RTCRNRLQCCJJO6SVSCT5IVDYKNCSU';
-      assert.deepStrictEqual(goldenAddress, htlc.getAddress());
-      const goldenLtxn =
-        'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgEHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8g/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItASkSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEIChyiO42rPQZmq42un3UDl1H3kZii2K4CElLvSrIU+oqpHR5cGWjcGF5';
-      const o = {
-        from: goldenAddress,
-        to: receiver,
-        fee: 0,
-        amount: 0,
-        closeRemainderTo: receiver,
-        firstRound: 1,
-        lastRound: 100,
-        genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=',
-        type: 'pay',
-      };
-      const preImageAsBase64 = 'cHJlaW1hZ2U=';
-      const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock(
-        htlc.getProgram(),
-        o,
-        preImageAsBase64
-      );
-      assert.deepEqual(Buffer.from(goldenLtxn, 'base64'), actualTxn.blob);
-    });
-    it('keccak256 should match the goldens', () => {
-      // Inputs
-      const owner =
-        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
-      const receiver =
-        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
-      const hashFn = 'keccak256';
-      const hashImg = 'D7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAc=';
-      const expiryRound = 600000;
-      const maxFee = 1000;
-      const htlc = new algosdk.LogicTemplates.HTLC(
-        owner,
-        receiver,
-        hashFn,
-        hashImg,
-        expiryRound,
-        maxFee
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IA+3eDK7wazksjUplLNJM7nLifv/Q2y5AOjjjqHMsKAHIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQIpEhAxCSoSMQIlDRAREA==';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = htlc.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        '3MJ6JY3P6AU4R6I2RASYSAOPNI3QMWPZ7HYXJRNRGBIAXCHAY7QZRBH5PQ';
-      assert.deepStrictEqual(goldenAddress, htlc.getAddress());
-      const goldenLtxn =
-        'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgD7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAcg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItAikSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEINsT5ONv8CnI+RqIJYkBz2o3Bln5+fF0xbEwUAuI4MfhpHR5cGWjcGF5';
-      const o = {
-        from: goldenAddress,
-        to: receiver,
-        fee: 0,
-        amount: 0,
-        closeRemainderTo: receiver,
-        firstRound: 1,
-        lastRound: 100,
-        genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=',
-        type: 'pay',
-      };
-      const preImageAsBase64 = 'cHJlaW1hZ2U=';
-      const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock(
-        htlc.getProgram(),
-        o,
-        preImageAsBase64
-      );
-      assert.deepEqual(Buffer.from(goldenLtxn, 'base64'), actualTxn.blob);
-    });
-    it('other hash function should fail', () => {
-      // Inputs
-      const owner =
-        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
-      const receiver =
-        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
-      const hashFn = 'made-up-hash-fn';
-      const hashImg = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
-      const expiryRound = 600000;
-      const maxFee = 1000;
-      assert.throws(
-        () =>
-          new algosdk.LogicTemplates.HTLC(
-            owner,
-            receiver,
-            hashFn,
-            hashImg,
-            expiryRound,
-            maxFee
-          )
-      );
-    });
-  });
-  describe('Limit Order', () => {
-    it('should match the goldens', () => {
-      // Inputs
-      const owner =
-        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
-      const assetid = 12345;
-      const ratn = 30;
-      const ratd = 100;
-      const expiryRound = 123456;
-      const minTrade = 10000;
-      const maxFee = 5000000;
-      const limitOrder = new algosdk.LogicTemplates.LimitOrder(
-        owner,
-        assetid,
-        ratn,
-        ratd,
-        expiryRound,
-        minTrade,
-        maxFee
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = limitOrder.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I';
-      assert.deepStrictEqual(goldenAddress, limitOrder.getAddress());
-      const secretKey = Buffer.from(
-        'DTKVj7KMON3GSWBwMX9McQHtaDDi8SDEBi0bt4rOxlHNRahLa0zVG+25BDIaHB1dSoIHIsUQ8FFcdnCdKoG+Bg==',
-        'base64'
-      );
-      const actualBlob = algosdk.LogicTemplates.getSwapAssetsTransaction(
-        actualBytes,
-        3000,
-        10000,
-        secretKey,
-        10,
-        1234,
-        2234,
-        'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='
-      );
-      const expectedTxn1 = Buffer.from(
-        'gqRsc2lngaFsxLcBIAoAAcCWsQICkE4EuWBkHsDEByYBIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMRYiEjEQIxIQMQEkDhAyBCMSQABVMgQlEjEIIQQNEDEJMgMSEDMBECEFEhAzAREhBhIQMwEUKBIQMwETMgMSEDMBEiEHHTUCNQExCCEIHTUENQM0ATQDDUAAJDQBNAMSNAI0BA8QQAAWADEJKBIxAiEJDRAxBzIDEhAxCCISEBCjdHhuiaNhbXTNJxCjZmVlzQisomZ2zQTSomdoxCB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaaNncnDEIKz368WOGpdE/Ww0L8wUu5Ly2u2bpG3ZSMKCJvcvGApTomx2zQi6o3JjdsQgzUWoS2tM1RvtuQQyGhwdXUqCByLFEPBRXHZwnSqBvgajc25kxCBd4Wnq60VaWXeVmuPt0x1XgMQSyP4nUnlh5G2Rhyo+taR0eXBlo3BheQ==',
-        'base64'
-      );
-      const expectedTxn2 = Buffer.from(
-        'gqNzaWfEQKXv8Z6OUDNmiZ5phpoQJHmfKyBal4gBZLPYsByYnlXCAlXMBeVFG5CLP1k5L6BPyEG2/XIbjbyM0CGG55CxxAKjdHhuiqRhYW10zQu4pGFyY3bEIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITo2ZlZc0JJKJmds0E0qJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCs9+vFjhqXRP1sNC/MFLuS8trtm6Rt2UjCgib3LxgKU6Jsds0IuqNzbmTEIM1FqEtrTNUb7bkEMhocHV1KggcixRDwUVx2cJ0qgb4GpHR5cGWlYXhmZXKkeGFpZM0wOQ==',
-        'base64'
-      );
-      const expectedBlob = Buffer.concat([expectedTxn1, expectedTxn2]);
-      assert.deepEqual(expectedBlob, actualBlob);
-    });
-  });
-  describe('Periodic payment', () => {
-    it('should match the goldens', () => {
-      // Inputs
-      const receiver =
-        'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I';
-      const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=';
-      const amount = 500000;
-      const withdrawalWindow = 95;
-      const period = 100;
-      const expiryRound = 2445756;
-      const maxFee = 1000;
-      const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment(
-        receiver,
-        amount,
-        withdrawalWindow,
-        period,
-        expiryRound,
-        maxFee,
-        leaseb64
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = periodicPayment.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY';
-      assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress());
-      const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
-      const goldenStx =
-        'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk=';
-      const goldenStxBlob = Buffer.from(goldenStx, 'base64');
-      const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction(
-        actualBytes,
-        0,
-        1200,
-        goldenGenesisHash
-      );
-      const expectedDict = algosdk.decodeObj(goldenStxBlob);
-      const actualDict = algosdk.decodeObj(stx.blob);
-      assert.deepEqual(expectedDict, actualDict);
-    });
-  });
-  describe('Limit Order', () => {
-    it('should match the goldens', () => {
-      // Inputs
-      const owner =
-        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
-      const assetid = 12345;
-      const ratn = 30;
-      const ratd = 100;
-      const expiryRound = 123456;
-      const minTrade = 10000;
-      const maxFee = 5000000;
-      const limitOrder = new algosdk.LogicTemplates.LimitOrder(
-        owner,
-        assetid,
-        ratn,
-        ratd,
-        expiryRound,
-        minTrade,
-        maxFee
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = limitOrder.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I';
-      assert.deepStrictEqual(goldenAddress, limitOrder.getAddress());
-    });
-  });
-  describe('Periodic payment', () => {
-    it('should match the goldens', () => {
-      // Inputs
-      const receiver =
-        'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I';
-      const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=';
-      const amount = 500000;
-      const withdrawalWindow = 95;
-      const period = 100;
-      const expiryRound = 2445756;
-      const maxFee = 1000;
-      const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment(
-        receiver,
-        amount,
-        withdrawalWindow,
-        period,
-        expiryRound,
-        maxFee,
-        leaseb64
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = periodicPayment.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY';
-      assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress());
-      const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
-      const goldenStx =
-        'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk=';
-      const goldenStxBlob = Buffer.from(goldenStx, 'base64');
-      const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction(
-        actualBytes,
-        0,
-        1200,
-        goldenGenesisHash
-      );
-      const expectedDict = algosdk.decodeObj(goldenStxBlob);
-      const actualDict = algosdk.decodeObj(stx.blob);
-      assert.deepEqual(expectedDict, actualDict);
-    });
-  });
-  describe('Dynamic Fee', () => {
-    it('should match the goldens', () => {
-      // Inputs
-      const receiver =
-        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
-      const amount = 5000;
-      const firstValid = 12345;
-      const lastValid = 12346;
-      const closeRemainder =
-        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
-      const artificialLease = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
-      const leaseBytes = new Uint8Array(Buffer.from(artificialLease, 'base64'));
-      const dynamicFee = new algosdk.LogicTemplates.DynamicFee(
-        receiver,
-        amount,
-        firstValid,
-        lastValid,
-        closeRemainder,
-        leaseBytes
-      );
-      // Outputs
-      const goldenProgram =
-        'ASAFAgGIJ7lgumAmAyD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEyDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+SB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaTIEIhIzABAjEhAzAAcxABIQMwAIMQESEDEWIxIQMRAjEhAxBygSEDEJKRIQMQgkEhAxAiUSEDEEIQQSEDEGKhIQ';
-      const goldenBytes = Buffer.from(goldenProgram, 'base64');
-      const actualBytes = dynamicFee.getProgram();
-      assert.deepStrictEqual(goldenBytes, actualBytes);
-      const goldenAddress =
-        'GCI4WWDIWUFATVPOQ372OZYG52EULPUZKI7Y34MXK3ZJKIBZXHD2H5C5TI';
-      assert.deepStrictEqual(goldenAddress, dynamicFee.getAddress());
-      const privateKeyOneB64 =
-        'cv8E0Ln24FSkwDgGeuXKStOTGcze5u8yldpXxgrBxumFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT7w==';
-      const privateKeyOne = Buffer.from(privateKeyOneB64, 'base64');
-      const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
-      const txnAndLsig = algosdk.LogicTemplates.signDynamicFee(
-        actualBytes,
-        privateKeyOne,
-        goldenGenesisHash
-      );
-      const txnDict = txnAndLsig.txn;
-      const txnObj = new algosdk.Transaction(txnDict);
-      const txnBytes = txnObj.toByte();
-      const goldenTxn =
-        'iqNhbXTNE4ilY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmibHbNMDqibHjEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpo3JjdsQg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohOjc25kxCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76R0eXBlo3BheQ==';
-      assert.deepStrictEqual(
-        new Uint8Array(Buffer.from(goldenTxn, 'base64')),
-        txnBytes
-      );
-      const { lsig } = txnAndLsig;
-      const lsigBytes = lsig.toByte();
-      const goldenLsig =
-        'gqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYH';
-      assert.deepStrictEqual(
-        new Uint8Array(Buffer.from(goldenLsig, 'base64')),
-        lsigBytes
-      );
-      const privateKeyTwoB64 =
-        '2qjz96Vj9M6YOqtNlfJUOKac13EHCXyDty94ozCjuwwriI+jzFgStFx9E6kEk1l4+lFsW4Te2PY1KV8kNcccRg==';
-      const privateKeyTwo = Buffer.from(privateKeyTwoB64, 'base64');
-      const stxns = algosdk.LogicTemplates.getDynamicFeeTransactions(
-        txnDict,
-        lsig,
-        privateKeyTwo,
-        1234,
-        firstValid,
-        lastValid
-      );
-      const goldenStxns =
-        'gqNzaWfEQJBNVry9qdpnco+uQzwFicUWHteYUIxwDkdHqY5Qw2Q8Fc2StrQUgN+2k8q4rC0LKrTMJQnE+mLWhZgMMJvq3QCjdHhuiqNhbXTOAAWq6qNmZWXOAATzvqJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76NzbmTEICuIj6PMWBK0XH0TqQSTWXj6UWxbhN7Y9jUpXyQ1xxxGpHR5cGWjcGF5gqRsc2lngqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYHo3R4boujYW10zROIpWNsb3NlxCDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+aNmZWXOAAWq6qJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiE6NzbmTEIIU9h0wnKapwajF0N7K4zy3orGLF+rQ8kLIk/vW6FhPvpHR5cGWjcGF5';
-      const goldenStxnBytes = Buffer.from(goldenStxns, 'base64');
-      assert.deepStrictEqual(new Uint8Array(goldenStxnBytes), stxns);
-    });
-  });
-});
diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts
new file mode 100644
index 0000000..b774aea
--- /dev/null
+++ b/tests/8.LogicSig.ts
@@ -0,0 +1,1487 @@
+import assert from 'assert';
+import algosdk from '../index';
+import * as logic from '../src/logic/logic';
+import * as utils from '../src/utils/utils';
+
+const sampleAccount1 = algosdk.mnemonicToSecretKey(
+  'auction inquiry lava second expand liberty glass involve ginger illness length room item discover ahead table doctor term tackle cement bonus profit right above catch'
+);
+const sampleAccount2 = algosdk.mnemonicToSecretKey(
+  'since during average anxiety protect cherry club long lawsuit loan expand embark forum theory winter park twenty ball kangaroo cram burst board host ability left'
+);
+const sampleAccount3 = algosdk.mnemonicToSecretKey(
+  'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor'
+);
+
+// Multisig Golden Params
+const sampleMultisigParams: algosdk.MultisigMetadata = {
+  version: 1,
+  threshold: 2,
+  addrs: [sampleAccount1.addr, sampleAccount2.addr, sampleAccount3.addr],
+};
+
+const sampleMultisigAddr = algosdk.multisigAddress(sampleMultisigParams);
+
+describe('LogicSig', () => {
+  describe('makeLogicSig', () => {
+    it('should work on valid program', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      const programHash =
+        '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY';
+      const pk = algosdk.decodeAddress(programHash).publicKey;
+      let lsig = algosdk.makeLogicSig(program);
+      assert.strictEqual(lsig.logic, program);
+      assert.strictEqual(lsig.args, undefined);
+      assert.strictEqual(lsig.sig, undefined);
+      assert.strictEqual(lsig.msig, undefined);
+      assert.strictEqual(lsig.address(), programHash);
+
+      let verified = lsig.verify(pk);
+      assert.strictEqual(verified, true);
+
+      const args = [Uint8Array.from([1, 2, 3]), Uint8Array.from([4, 5, 6])];
+      lsig = algosdk.makeLogicSig(program, args);
+      assert.strictEqual(lsig.logic, program);
+      assert.deepStrictEqual(lsig.args, args);
+      assert.strictEqual(lsig.sig, undefined);
+      assert.strictEqual(lsig.msig, undefined);
+
+      verified = lsig.verify(pk);
+      assert.strictEqual(verified, true);
+
+      // check serialization
+      const encoded = lsig.toByte();
+      const decoded = algosdk.logicSigFromByte(encoded);
+      assert.deepStrictEqual(decoded, lsig);
+    });
+    it('should fail on tampered program', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      const programHash =
+        '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY';
+      const pk = algosdk.decodeAddress(programHash).publicKey;
+
+      program[3] = 2;
+      const lsig = algosdk.makeLogicSig(program);
+      const verified = lsig.verify(pk);
+      assert.strictEqual(verified, false);
+    });
+    it('should fail on invalid program', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      program[0] = 128;
+      assert.throws(() => algosdk.makeLogicSig(program));
+    });
+  });
+
+  describe('address', () => {
+    it('should produce the correct address', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      const programHash =
+        '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY';
+
+      const lsig = algosdk.makeLogicSig(program);
+      const address = lsig.address();
+
+      assert.deepStrictEqual(address, programHash);
+    });
+  });
+});
+
+describe('LogicSigAccount', () => {
+  describe('constructor', () => {
+    it('should work on valid program without args', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+
+      const lsigAccount = new algosdk.LogicSigAccount(program);
+      assert.deepStrictEqual(lsigAccount.lsig.logic, program);
+      assert.strictEqual(lsigAccount.lsig.args, undefined);
+      assert.strictEqual(lsigAccount.lsig.sig, undefined);
+      assert.strictEqual(lsigAccount.lsig.msig, undefined);
+      assert.strictEqual(lsigAccount.sigkey, undefined);
+
+      // check serialization
+      const encoded = lsigAccount.toByte();
+      const expectedEncoded = new Uint8Array(
+        Buffer.from('gaRsc2lngaFsxAUBIAEBIg==', 'base64')
+      );
+      assert.deepStrictEqual(encoded, expectedEncoded);
+
+      const decoded = algosdk.LogicSigAccount.fromByte(encoded);
+      assert.deepStrictEqual(decoded, lsigAccount);
+    });
+    it('should work on valid program with args', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])];
+
+      const lsigAccount = new algosdk.LogicSigAccount(program, args);
+      assert.deepStrictEqual(lsigAccount.lsig.logic, program);
+      assert.deepStrictEqual(lsigAccount.lsig.args, args);
+      assert.strictEqual(lsigAccount.lsig.sig, undefined);
+      assert.strictEqual(lsigAccount.lsig.msig, undefined);
+      assert.strictEqual(lsigAccount.sigkey, undefined);
+
+      // check serialization
+      const encoded = lsigAccount.toByte();
+      const expectedEncoded = new Uint8Array(
+        Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64')
+      );
+      assert.deepStrictEqual(encoded, expectedEncoded);
+
+      const decoded = algosdk.LogicSigAccount.fromByte(encoded);
+      assert.deepStrictEqual(decoded, lsigAccount);
+    });
+    it('should fail on invalid program', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      program[0] = 128;
+      assert.throws(() => new algosdk.LogicSigAccount(program));
+    });
+  });
+
+  describe('sign', () => {
+    it('should properly sign the program', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])];
+
+      const lsigAccount = new algosdk.LogicSigAccount(program, args);
+      lsigAccount.sign(sampleAccount1.sk);
+
+      const expectedSig = new Uint8Array(
+        Buffer.from(
+          'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==',
+          'base64'
+        )
+      );
+      const expectedSigKey = algosdk.decodeAddress(sampleAccount1.addr)
+        .publicKey;
+
+      assert.deepStrictEqual(lsigAccount.lsig.logic, program);
+      assert.deepStrictEqual(lsigAccount.lsig.args, args);
+      assert.deepStrictEqual(lsigAccount.lsig.sig, expectedSig);
+      assert.strictEqual(lsigAccount.lsig.msig, undefined);
+      assert.deepStrictEqual(lsigAccount.sigkey, expectedSigKey);
+
+      // check serialization
+      const encoded = lsigAccount.toByte();
+      const expectedEncoded = new Uint8Array(
+        Buffer.from(
+          'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==',
+          'base64'
+        )
+      );
+      assert.deepStrictEqual(encoded, expectedEncoded);
+
+      const decoded = algosdk.LogicSigAccount.fromByte(encoded);
+      assert.deepStrictEqual(decoded, lsigAccount);
+    });
+  });
+
+  describe('signMultisig', () => {
+    it('should properly sign the program', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])];
+
+      const lsigAccount = new algosdk.LogicSigAccount(program, args);
+      lsigAccount.signMultisig(sampleMultisigParams, sampleAccount1.sk);
+
+      const expectedSig = new Uint8Array(
+        Buffer.from(
+          'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==',
+          'base64'
+        )
+      );
+      const expectedMsig: algosdk.EncodedMultisig = {
+        v: sampleMultisigParams.version,
+        thr: sampleMultisigParams.threshold,
+        subsig: sampleMultisigParams.addrs.map((addr) => ({
+          pk: algosdk.decodeAddress(addr).publicKey,
+        })),
+      };
+      expectedMsig.subsig[0].s = expectedSig;
+
+      assert.deepStrictEqual(lsigAccount.lsig.logic, program);
+      assert.deepStrictEqual(lsigAccount.lsig.args, args);
+      assert.strictEqual(lsigAccount.lsig.sig, undefined);
+      assert.deepStrictEqual(lsigAccount.lsig.msig, expectedMsig);
+      assert.strictEqual(lsigAccount.sigkey, undefined);
+
+      // check serialization
+      const encoded = lsigAccount.toByte();
+      const expectedEncoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==',
+          'base64'
+        )
+      );
+      assert.deepStrictEqual(encoded, expectedEncoded);
+
+      const decoded = algosdk.LogicSigAccount.fromByte(encoded);
+      assert.deepStrictEqual(decoded, lsigAccount);
+    });
+  });
+
+  describe('appendToMultisig', () => {
+    it('should properly append a signature', () => {
+      const msig1of3Encoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(msig1of3Encoded);
+
+      lsigAccount.appendToMultisig(sampleAccount2.sk);
+
+      const expectedSig1 = new Uint8Array(
+        Buffer.from(
+          'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==',
+          'base64'
+        )
+      );
+      const expectedSig2 = new Uint8Array(
+        Buffer.from(
+          'ZLxV2+2RokHUKrZg9+FKuZmaUrOxcVjO/D9P58siQRStqT1ehAUCChemaYMDIk6Go4tqNsVUviBQ/9PuqLMECQ==',
+          'base64'
+        )
+      );
+      const expectedMsig: algosdk.EncodedMultisig = {
+        v: sampleMultisigParams.version,
+        thr: sampleMultisigParams.threshold,
+        subsig: sampleMultisigParams.addrs.map((addr) => ({
+          pk: algosdk.decodeAddress(addr).publicKey,
+        })),
+      };
+      expectedMsig.subsig[0].s = expectedSig1;
+      expectedMsig.subsig[1].s = expectedSig2;
+
+      assert.strictEqual(lsigAccount.lsig.sig, undefined);
+      assert.deepStrictEqual(lsigAccount.lsig.msig, expectedMsig);
+      assert.strictEqual(lsigAccount.sigkey, undefined);
+
+      // check serialization
+      const encoded = lsigAccount.toByte();
+      const expectedEncoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB',
+          'base64'
+        )
+      );
+      assert.deepStrictEqual(encoded, expectedEncoded);
+
+      const decoded = algosdk.LogicSigAccount.fromByte(encoded);
+      assert.deepStrictEqual(decoded, lsigAccount);
+    });
+  });
+
+  describe('verify', () => {
+    it('should verify valid escrow', () => {
+      const escrowEncoded = new Uint8Array(
+        Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64')
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(escrowEncoded);
+
+      assert.strictEqual(lsigAccount.verify(), true);
+    });
+
+    it('should verify valid single sig', () => {
+      const sigEncoded = new Uint8Array(
+        Buffer.from(
+          'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded);
+
+      assert.strictEqual(lsigAccount.verify(), true);
+    });
+
+    it('should fail single sig with wrong sig', () => {
+      const sigEncoded = new Uint8Array(
+        Buffer.from(
+          'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded);
+
+      // modify signature
+      lsigAccount.lsig.sig![0] = 0;
+
+      assert.strictEqual(lsigAccount.verify(), false);
+    });
+
+    it('should verify valid multisig', () => {
+      const msigEncoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded);
+
+      assert.strictEqual(lsigAccount.verify(), true);
+    });
+
+    it('should fail multisig with wrong sig', () => {
+      const msigEncoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded);
+
+      // modify signature
+      lsigAccount.lsig.msig!.subsig[0].s[0] = 0;
+
+      assert.strictEqual(lsigAccount.verify(), false);
+    });
+
+    it('should fail multisig that does not meet threshold', () => {
+      const msigBelowThresholdEncoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(
+        msigBelowThresholdEncoded
+      );
+
+      assert.strictEqual(lsigAccount.verify(), false);
+    });
+  });
+
+  describe('isDelegated', () => {
+    it('should be correct for escrow', () => {
+      const escrowEncoded = new Uint8Array(
+        Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64')
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(escrowEncoded);
+
+      assert.strictEqual(lsigAccount.isDelegated(), false);
+    });
+
+    it('should be correct for single sig', () => {
+      const sigEncoded = new Uint8Array(
+        Buffer.from(
+          'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded);
+
+      assert.strictEqual(lsigAccount.isDelegated(), true);
+    });
+
+    it('should be correct for multisig', () => {
+      const msigEncoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded);
+
+      assert.strictEqual(lsigAccount.isDelegated(), true);
+    });
+  });
+
+  describe('address', () => {
+    it('should be correct for escrow', () => {
+      const escrowEncoded = new Uint8Array(
+        Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64')
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(escrowEncoded);
+
+      const addr = lsigAccount.address();
+
+      const expectedAddr =
+        '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY';
+
+      assert.strictEqual(addr, expectedAddr);
+    });
+
+    it('should be correct for single sig', () => {
+      const sigEncoded = new Uint8Array(
+        Buffer.from(
+          'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded);
+
+      const addr = lsigAccount.address();
+
+      const expectedAddr =
+        'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA';
+
+      assert.strictEqual(addr, expectedAddr);
+    });
+
+    it('should be correct for multisig', () => {
+      const msigEncoded = new Uint8Array(
+        Buffer.from(
+          'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB',
+          'base64'
+        )
+      );
+      const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded);
+
+      const addr = lsigAccount.address();
+
+      const expectedAddr =
+        'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM';
+
+      assert.strictEqual(addr, expectedAddr);
+    });
+  });
+});
+
+describe('signLogicSigTransaction', () => {
+  const program = Uint8Array.from([1, 32, 1, 1, 34]);
+  const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])];
+
+  const otherAddr =
+    'WTDCE2FEYM2VB5MKNXKLRSRDTSPR2EFTIGVH4GRW4PHGD6747GFJTBGT2A';
+
+  function testSign(
+    lsigObject:
+      | ReturnType<typeof algosdk.makeLogicSig>
+      | algosdk.LogicSigAccount,
+    sender: string,
+    expected: { txID: string; blob: Uint8Array }
+  ) {
+    const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
+      from: sender,
+      to: otherAddr,
+      amount: 5000,
+      suggestedParams: {
+        flatFee: true,
+        fee: 217000,
+        firstRound: 972508,
+        lastRound: 973508,
+        genesisID: 'testnet-v31.0',
+        genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=',
+      },
+      note: new Uint8Array([180, 81, 121, 57, 252, 250, 210, 113]),
+    });
+
+    const actual = algosdk.signLogicSigTransaction(txn, lsigObject);
+
+    assert.deepStrictEqual(actual, expected);
+  }
+
+  describe('with LogicSig', () => {
+    describe('escrow', () => {
+      const lsig = algosdk.makeLogicSig(program, args);
+
+      it('should match expected when sender is LogicSig address', () => {
+        const sender = lsig.address();
+        const expected = {
+          txID: 'SV3GD4AKRRX43F3V4V7GYYB6YCQEPULGUI6GKZO6GPJDKOO75NFA',
+          blob: new Uint8Array(
+            Buffer.from(
+              'gqRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraR0eXBlo3BheQ==',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsig, sender, expected);
+      });
+
+      it('should match expected when sender is not LogicSig address', () => {
+        const sender = otherAddr;
+        const expected = {
+          txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ',
+          blob: new Uint8Array(
+            Buffer.from(
+              'g6Rsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqRzZ25yxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqR0eXBlo3BheQ==',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsig, sender, expected);
+      });
+    });
+
+    describe('single sig', () => {
+      const account = algosdk.mnemonicToSecretKey(
+        'olympic cricket tower model share zone grid twist sponsor avoid eight apology patient party success claim famous rapid donor pledge bomb mystery security ability often'
+      );
+      const lsig = algosdk.makeLogicSig(program, args);
+      lsig.sign(account.sk);
+
+      it('should match expected when sender is LogicSig address', () => {
+        const sender = account.addr;
+        const expected = {
+          txID: 'EZB2N2TEFR5OOL76Z46ZMRUL3ZQQOYKRFIX6WSHQ5FWESHU4LZPA',
+          blob: new Uint8Array(
+            Buffer.from(
+              'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQD4FPTlN+xK8ZXmf6jGKe46iUYtVLIq+bNenZS3YsBh+IQUtuSRiiRblYXTNDxmsuWxFpCmRmREd5Hzk/BLszgKjdHhuiqNhbXTNE4ijZmVlzgADT6iiZnbOAA7W3KNnZW6tdGVzdG5ldC12MzEuMKJnaMQgJgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dKibHbOAA7axKRub3RlxAi0UXk5/PrScaNyY3bEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKo3NuZMQgXmdPHAru7DdxiY9hx2/10koZeT4skfoIUWJj44Vz6kKkdHlwZaNwYXk=',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsig, sender, expected);
+      });
+
+      it('should throw an error when sender is not LogicSig address', () => {
+        const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
+          from: otherAddr,
+          to: otherAddr,
+          amount: 5000,
+          suggestedParams: {
+            flatFee: true,
+            fee: 217000,
+            firstRound: 972508,
+            lastRound: 973508,
+            genesisID: 'testnet-v31.0',
+            genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=',
+          },
+          note: new Uint8Array([180, 81, 121, 57, 252, 250, 210, 113]),
+        });
+
+        assert.throws(
+          () => algosdk.signLogicSigTransaction(txn, lsig),
+          (err) =>
+            err.message ===
+            'Logic signature verification failed. Ensure the program and signature are valid.'
+        );
+      });
+    });
+
+    describe('multisig', () => {
+      const lsig = algosdk.makeLogicSig(program, args);
+      lsig.sign(sampleAccount1.sk, sampleMultisigParams);
+      lsig.appendToMultisig(sampleAccount2.sk);
+
+      it('should match expected when sender is LogicSig address', () => {
+        const sender = sampleMultisigAddr;
+        const expected = {
+          txID: 'UGGT5EZXG2OBPGWTEINC65UXIQ6UVAAOTNKRRCRAUCZH4FWJTVQQ',
+          blob: new Uint8Array(
+            Buffer.from(
+              'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsig, sender, expected);
+      });
+
+      it('should match expected when sender is not LogicSig address', () => {
+        const sender = otherAddr;
+        const expected = {
+          txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ',
+          blob: new Uint8Array(
+            Buffer.from(
+              'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKpHR5cGWjcGF5',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsig, sender, expected);
+      });
+    });
+  });
+
+  describe('with LogicSigAccount', () => {
+    describe('escrow', () => {
+      const lsigAccount = new algosdk.LogicSigAccount(program, args);
+
+      it('should match expected when sender is LogicSig address', () => {
+        const sender = lsigAccount.address();
+        const expected = {
+          txID: 'SV3GD4AKRRX43F3V4V7GYYB6YCQEPULGUI6GKZO6GPJDKOO75NFA',
+          blob: new Uint8Array(
+            Buffer.from(
+              'gqRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraR0eXBlo3BheQ==',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsigAccount, sender, expected);
+      });
+
+      it('should match expected when sender is not LogicSig address', () => {
+        const sender = otherAddr;
+        const expected = {
+          txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ',
+          blob: new Uint8Array(
+            Buffer.from(
+              'g6Rsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqRzZ25yxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqR0eXBlo3BheQ==',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsigAccount, sender, expected);
+      });
+    });
+
+    describe('single sig', () => {
+      const account = algosdk.mnemonicToSecretKey(
+        'olympic cricket tower model share zone grid twist sponsor avoid eight apology patient party success claim famous rapid donor pledge bomb mystery security ability often'
+      );
+      const lsigAccount = new algosdk.LogicSigAccount(program, args);
+      lsigAccount.sign(account.sk);
+
+      it('should match expected when sender is LogicSig address', () => {
+        const sender = account.addr;
+        const expected = {
+          txID: 'EZB2N2TEFR5OOL76Z46ZMRUL3ZQQOYKRFIX6WSHQ5FWESHU4LZPA',
+          blob: new Uint8Array(
+            Buffer.from(
+              'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQD4FPTlN+xK8ZXmf6jGKe46iUYtVLIq+bNenZS3YsBh+IQUtuSRiiRblYXTNDxmsuWxFpCmRmREd5Hzk/BLszgKjdHhuiqNhbXTNE4ijZmVlzgADT6iiZnbOAA7W3KNnZW6tdGVzdG5ldC12MzEuMKJnaMQgJgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dKibHbOAA7axKRub3RlxAi0UXk5/PrScaNyY3bEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKo3NuZMQgXmdPHAru7DdxiY9hx2/10koZeT4skfoIUWJj44Vz6kKkdHlwZaNwYXk=',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsigAccount, sender, expected);
+      });
+
+      it('should match expected when sender is not LogicSig address', () => {
+        const sender = otherAddr;
+        const expected = {
+          txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ',
+          blob: new Uint8Array(
+            Buffer.from(
+              'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQD4FPTlN+xK8ZXmf6jGKe46iUYtVLIq+bNenZS3YsBh+IQUtuSRiiRblYXTNDxmsuWxFpCmRmREd5Hzk/BLszgKkc2ducsQgXmdPHAru7DdxiY9hx2/10koZeT4skfoIUWJj44Vz6kKjdHhuiqNhbXTNE4ijZmVlzgADT6iiZnbOAA7W3KNnZW6tdGVzdG5ldC12MzEuMKJnaMQgJgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dKibHbOAA7axKRub3RlxAi0UXk5/PrScaNyY3bEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKo3NuZMQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+YqkdHlwZaNwYXk=',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsigAccount, sender, expected);
+      });
+    });
+
+    describe('multisig', () => {
+      const lsigAccount = new algosdk.LogicSigAccount(program, args);
+      lsigAccount.signMultisig(sampleMultisigParams, sampleAccount1.sk);
+      lsigAccount.appendToMultisig(sampleAccount2.sk);
+
+      it('should match expected when sender is LogicSig address', () => {
+        const sender = sampleMultisigAddr;
+        const expected = {
+          txID: 'UGGT5EZXG2OBPGWTEINC65UXIQ6UVAAOTNKRRCRAUCZH4FWJTVQQ',
+          blob: new Uint8Array(
+            Buffer.from(
+              'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsigAccount, sender, expected);
+      });
+
+      it('should match expected when sender is not LogicSig address', () => {
+        const sender = otherAddr;
+        const expected = {
+          txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ',
+          blob: new Uint8Array(
+            Buffer.from(
+              'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKpHR5cGWjcGF5',
+              'base64'
+            )
+          ),
+        };
+        testSign(lsigAccount, sender, expected);
+      });
+    });
+  });
+
+  it('should sign a raw transaction object', () => {
+    const lsig = algosdk.makeLogicSig(program);
+
+    const from = lsig.address();
+    const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM';
+    const fee = 10;
+    const amount = 847;
+    const firstRound = 51;
+    const lastRound = 61;
+    const note = new Uint8Array([123, 12, 200]);
+    const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=';
+    const genesisID = '';
+    const rekeyTo =
+      'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM';
+    let closeRemainderTo;
+    const txn = {
+      from,
+      to,
+      fee,
+      amount,
+      closeRemainderTo,
+      firstRound,
+      lastRound,
+      note,
+      genesisHash,
+      genesisID,
+      reKeyTo: rekeyTo,
+    };
+
+    const actual = algosdk.signLogicSigTransaction(txn, lsig);
+    const expected = {
+      txID: 'D7H6THOHOCEWJYNWMKHVOR2W36KAJXSGG6DMNTHTBWONBCG4XATA',
+      blob: new Uint8Array(
+        Buffer.from(
+          'gqRsc2lngaFsxAUBIAEBIqN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQg9nYtrHWxmX1sLJYYBoBQdJDXlREv/n+3YLJzivnH8a2kdHlwZaNwYXk=',
+          'base64'
+        )
+      ),
+    };
+
+    assert.deepStrictEqual(actual, expected);
+  });
+});
+
+describe('Program validation', () => {
+  describe('Varint', () => {
+    it('should parse binary data correctly', () => {
+      let data = Uint8Array.from([1]);
+      let [value, length] = logic.parseUvarint(data);
+      assert.strictEqual(length, 1);
+      assert.strictEqual(value, 1);
+
+      data = Uint8Array.from([123]);
+      [value, length] = logic.parseUvarint(data);
+      assert.strictEqual(length, 1);
+      assert.strictEqual(value, 123);
+
+      data = Uint8Array.from([200, 3]);
+      [value, length] = logic.parseUvarint(data);
+      assert.strictEqual(length, 2);
+      assert.strictEqual(value, 456);
+    });
+  });
+  describe('Const blocks', () => {
+    it('should parse int const block correctly', () => {
+      const data = Uint8Array.from([32, 5, 0, 1, 200, 3, 123, 2]);
+      const size = logic.checkIntConstBlock(data, 0);
+      assert.strictEqual(size, data.length);
+    });
+    it('should parse bytes const block correctly', () => {
+      const data = Uint8Array.from([
+        38,
+        2,
+        13,
+        49,
+        50,
+        51,
+        52,
+        53,
+        54,
+        55,
+        56,
+        57,
+        48,
+        49,
+        50,
+        51,
+        2,
+        1,
+        2,
+      ]);
+      const size = logic.checkByteConstBlock(data, 0);
+      assert.strictEqual(size, data.length);
+    });
+    it('should parse int push op correctly', () => {
+      const data = Uint8Array.from([0x81, 0x80, 0x80, 0x04]);
+      const size = logic.checkPushIntOp(data, 0);
+      assert.strictEqual(size, data.length);
+    });
+    it('should parse byte push op correctly', () => {
+      const data = Uint8Array.from([
+        0x80,
+        0x0b,
+        0x68,
+        0x65,
+        0x6c,
+        0x6c,
+        0x6f,
+        0x20,
+        0x77,
+        0x6f,
+        0x72,
+        0x6c,
+        0x64,
+      ]);
+      const size = logic.checkPushByteOp(data, 0);
+      assert.strictEqual(size, data.length);
+    });
+  });
+  describe('Program checker', () => {
+    it('should assess correct programs right', () => {
+      let program = Uint8Array.from([1, 32, 1, 1, 34]);
+      let result = logic.checkProgram(program);
+      assert.strictEqual(result, true);
+
+      const args = [Uint8Array.from([1, 2, 3])];
+
+      result = logic.checkProgram(program, args);
+      assert.strictEqual(result, true);
+
+      program = utils.concatArrays(program, new Array(10).fill(0x22));
+      result = logic.checkProgram(program, args);
+      assert.strictEqual(result, true);
+    });
+    it('should fail on long input', () => {
+      assert.throws(
+        () => (logic.checkProgram as any)(),
+        new Error('empty program')
+      );
+      let program = Uint8Array.from([1, 32, 1, 1, 34]);
+      assert.throws(
+        () => logic.checkProgram(program, [new Uint8Array(1000).fill(55)]),
+        new Error('program too long')
+      );
+
+      program = utils.concatArrays(program, new Uint8Array(1000).fill(34));
+      assert.throws(
+        () => logic.checkProgram(program),
+        new Error('program too long')
+      );
+    });
+    it('should fail on invalid program', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34, 255]);
+      assert.throws(
+        () => logic.checkProgram(program),
+        new Error('invalid instruction')
+      );
+    });
+    it('should fail on invalid args', () => {
+      const program = Uint8Array.from([1, 32, 1, 1, 34]);
+      assert.throws(
+        () => logic.checkProgram(program, '123' as any),
+        new Error('invalid arguments')
+      );
+    });
+    it('should fail on costly program', () => {
+      let program = Uint8Array.from([1, 38, 1, 1, 1, 40, 2]); // byte 0x01 + keccak256
+      let result = logic.checkProgram(program);
+      assert.strictEqual(result, true);
+
+      // 10x keccak256 more is fine
+      program = utils.concatArrays(program, new Uint8Array(10).fill(2));
+      result = logic.checkProgram(program);
+      assert.strictEqual(result, true);
+
+      // 800x keccak256 more is too costly
+      program = utils.concatArrays(program, new Uint8Array(800).fill(2));
+      //  old versions
+      const oldVersions = [0x1, 0x2, 0x3];
+      let i;
+      for (i = 0; i < oldVersions.length; i++) {
+        program[0] = oldVersions[i];
+        assert.throws(
+          () => logic.checkProgram(program),
+          new Error(
+            'program too costly for Teal version < 4. consider using v4.'
+          )
+        );
+      }
+      //  new versions
+      const newVersions = [0x4];
+      for (i = 0; i < newVersions.length; i++) {
+        program[0] = newVersions[i];
+        assert.ok(logic.checkProgram(program));
+      }
+    });
+    it('should support TEAL v2 opcodes', () => {
+      assert.ok(logic.langspecEvalMaxVersion >= 2);
+      assert.ok(logic.langspecLogicSigVersion >= 2);
+
+      // balance
+      let program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x60]); // int 0; balance
+      let result = logic.checkProgram(program);
+      assert.strictEqual(result, true);
+
+      // app_opted_in
+      program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x61]); // int 0; int 0; app_opted_in
+      result = logic.checkProgram(program);
+      assert.strictEqual(result, true);
+
+      // 800x keccak256 more is to costly
+      // prettier-ignore
+      program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x70, 0x00 ]); // int 0; int 0; asset_holding_get Balance
+      result = logic.checkProgram(program);
+      assert.strictEqual(result, true);
+    });
+    it('should support TEAL v3 opcodes', () => {
+      assert.ok(logic.langspecEvalMaxVersion >= 3);
+      assert.ok(logic.langspecLogicSigVersion >= 3);
+
+      // min_balance
+      let program = Uint8Array.from([0x03, 0x20, 0x01, 0x00, 0x22, 0x78]); // int 0; min_balance
+      assert.ok(logic.checkProgram(program));
+
+      // pushbytes
+      program = Uint8Array.from([
+        0x03,
+        0x20,
+        0x01,
+        0x00,
+        0x22,
+        0x80,
+        0x02,
+        0x68,
+        0x69,
+        0x48,
+      ]); // int 0; pushbytes "hi"; pop
+      assert.ok(logic.checkProgram(program));
+
+      // pushint
+      program = Uint8Array.from([
+        0x03,
+        0x20,
+        0x01,
+        0x00,
+        0x22,
+        0x81,
+        0x01,
+        0x48,
+      ]); // int 0; pushint 1; pop
+      assert.ok(logic.checkProgram(program));
+
+      // swap
+      program = Uint8Array.from([
+        0x03,
+        0x20,
+        0x02,
+        0x00,
+        0x01,
+        0x22,
+        0x23,
+        0x4c,
+        0x48,
+      ]); // int 0; int 1; swap; pop
+      assert.ok(logic.checkProgram(program));
+    });
+    it('should support TEAL v4 opcodes', () => {
+      assert.ok(logic.langspecEvalMaxVersion >= 4);
+
+      // divmodw
+      let program = Uint8Array.from([
+        0x04,
+        0x20,
+        0x03,
+        0x01,
+        0x00,
+        0x02,
+        0x22,
+        0x81,
+        0xd0,
+        0x0f,
+        0x23,
+        0x24,
+        0x1f,
+      ]); // int 1; pushint 2000; int 0; int 2; divmodw
+      assert.ok(logic.checkProgram(program));
+
+      // gloads i
+      program = Uint8Array.from([0x04, 0x20, 0x01, 0x00, 0x22, 0x3b, 0x00]); // int 0; gloads 0
+      assert.ok(logic.checkProgram(program));
+
+      // callsub
+      program = Uint8Array.from([
+        0x04,
+        0x20,
+        0x02,
+        0x01,
+        0x02,
+        0x22,
+        0x88,
+        0x00,
+        0x02,
+        0x23,
+        0x12,
+        0x49,
+      ]); // int 1; callsub double; int 2; ==; double: dup;
+      assert.ok(logic.checkProgram(program));
+
+      // b>=
+      program = Uint8Array.from([
+        0x04,
+        0x26,
+        0x02,
+        0x01,
+        0x11,
+        0x01,
+        0x10,
+        0x28,
+        0x29,
+        0xa7,
+      ]); // byte 0x11; byte 0x10; b>=
+      assert.ok(logic.checkProgram(program));
+
+      // b^
+      program = Uint8Array.from([
+        0x04,
+        0x26,
+        0x03,
+        0x01,
+        0x11,
+        0x01,
+        0x10,
+        0x01,
+        0x01,
+        0x28,
+        0x29,
+        0xad,
+        0x2a,
+        0x12,
+      ]); // byte 0x11; byte 0x10; b>=
+      assert.ok(logic.checkProgram(program));
+
+      // callsub, retsub
+      program = Uint8Array.from([
+        0x04,
+        0x20,
+        0x02,
+        0x01,
+        0x02,
+        0x22,
+        0x88,
+        0x00,
+        0x03,
+        0x23,
+        0x12,
+        0x43,
+        0x49,
+        0x08,
+        0x89,
+      ]); // int 1; callsub double; int 2; ==; return; double: dup; +; retsub;
+      assert.ok(logic.checkProgram(program));
+
+      // loop
+      program = Uint8Array.from([
+        0x04,
+        0x20,
+        0x04,
+        0x01,
+        0x02,
+        0x0a,
+        0x10,
+        0x22,
+        0x23,
+        0x0b,
+        0x49,
+        0x24,
+        0x0c,
+        0x40,
+        0xff,
+        0xf8,
+        0x25,
+        0x12,
+      ]); // int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; ==
+      assert.ok(logic.checkProgram(program));
+    });
+  });
+});
+
+describe('Template logic validation', () => {
+  describe('Split', () => {
+    it('should match the goldens', () => {
+      // Inputs
+      const owner =
+        'WO3QIJ6T4DZHBX5PWJH26JLHFSRT7W7M2DJOULPXDTUS6TUX7ZRIO4KDFY';
+      const receivers = [
+        'W6UUUSEAOGLBHT7VFT4H2SDATKKSG6ZBUIJXTZMSLW36YS44FRP5NVAU7U',
+        'XCIBIN7RT4ZXGBMVAMU3QS6L5EKB7XGROC5EPCNHHYXUIBAA5Q6C5Y7NEU',
+      ];
+      const rat1 = 100;
+      const rat2 = 30;
+      const expiryRound = 123456;
+      const minPay = 10000;
+      const maxFee = 5000000;
+      const split = new algosdk.LogicTemplates.Split(
+        owner,
+        receivers[0],
+        receivers[1],
+        rat1,
+        rat2,
+        expiryRound,
+        minPay,
+        maxFee
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAIAcCWsQICAMDEBx5kkE4mAyCztwQn0+DycN+vsk+vJWcsoz/b7NDS6i33HOkvTpf+YiC3qUpIgHGWE8/1LPh9SGCalSN7IaITeeWSXbfsS5wsXyC4kBQ38Z8zcwWVAym4S8vpFB/c0XC6R4mnPi9EBADsPDEQIhIxASMMEDIEJBJAABkxCSgSMQcyAxIQMQglEhAxAiEEDRAiQAAuMwAAMwEAEjEJMgMSEDMABykSEDMBByoSEDMACCEFCzMBCCEGCxIQMwAIIQcPEBA=';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = split.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        'KPYGWKTV7CKMPMTLQRNGMEQRSYTYDHUOFNV4UDSBDLC44CLIJPQWRTCPBU';
+      assert.deepStrictEqual(goldenAddress, split.getAddress());
+    });
+  });
+  describe('HTLC', () => {
+    it('sha256 should match the goldens', () => {
+      // Inputs
+      const owner =
+        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
+      const receiver =
+        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
+      const hashFn = 'sha256';
+      const hashImg = 'EHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8=';
+      const expiryRound = 600000;
+      const maxFee = 1000;
+      const htlc = new algosdk.LogicTemplates.HTLC(
+        owner,
+        receiver,
+        hashFn,
+        hashImg,
+        expiryRound,
+        maxFee
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IBB2YRNPIfx8AiI9UKues2ALw//DcSQjoeR7sfmp2/VfIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQEpEhAxCSoSMQIlDRAREA==';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = htlc.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        'FBZIR3RWVT2BTGVOG25H3VAOLVD54RTCRNRLQCCJJO6SVSCT5IVDYKNCSU';
+      assert.deepStrictEqual(goldenAddress, htlc.getAddress());
+      const goldenLtxn =
+        'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgEHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8g/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItASkSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEIChyiO42rPQZmq42un3UDl1H3kZii2K4CElLvSrIU+oqpHR5cGWjcGF5';
+      const o = {
+        from: goldenAddress,
+        to: receiver,
+        fee: 0,
+        amount: 0,
+        closeRemainderTo: receiver,
+        firstRound: 1,
+        lastRound: 100,
+        genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=',
+        type: 'pay',
+      };
+      const preImageAsBase64 = 'cHJlaW1hZ2U=';
+      const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock(
+        htlc.getProgram(),
+        o,
+        preImageAsBase64
+      );
+      assert.deepStrictEqual(
+        Buffer.from(goldenLtxn, 'base64'),
+        Buffer.from(actualTxn.blob)
+      );
+    });
+    it('keccak256 should match the goldens', () => {
+      // Inputs
+      const owner =
+        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
+      const receiver =
+        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
+      const hashFn = 'keccak256';
+      const hashImg = 'D7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAc=';
+      const expiryRound = 600000;
+      const maxFee = 1000;
+      const htlc = new algosdk.LogicTemplates.HTLC(
+        owner,
+        receiver,
+        hashFn,
+        hashImg,
+        expiryRound,
+        maxFee
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IA+3eDK7wazksjUplLNJM7nLifv/Q2y5AOjjjqHMsKAHIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQIpEhAxCSoSMQIlDRAREA==';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = htlc.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        '3MJ6JY3P6AU4R6I2RASYSAOPNI3QMWPZ7HYXJRNRGBIAXCHAY7QZRBH5PQ';
+      assert.deepStrictEqual(goldenAddress, htlc.getAddress());
+      const goldenLtxn =
+        'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgD7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAcg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItAikSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEINsT5ONv8CnI+RqIJYkBz2o3Bln5+fF0xbEwUAuI4MfhpHR5cGWjcGF5';
+      const o = {
+        from: goldenAddress,
+        to: receiver,
+        fee: 0,
+        amount: 0,
+        closeRemainderTo: receiver,
+        firstRound: 1,
+        lastRound: 100,
+        genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=',
+        type: 'pay',
+      };
+      const preImageAsBase64 = 'cHJlaW1hZ2U=';
+      const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock(
+        htlc.getProgram(),
+        o,
+        preImageAsBase64
+      );
+      assert.deepStrictEqual(
+        Buffer.from(goldenLtxn, 'base64'),
+        Buffer.from(actualTxn.blob)
+      );
+    });
+    it('other hash function should fail', () => {
+      // Inputs
+      const owner =
+        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
+      const receiver =
+        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
+      const hashFn = 'made-up-hash-fn';
+      const hashImg = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
+      const expiryRound = 600000;
+      const maxFee = 1000;
+      assert.throws(
+        () =>
+          new algosdk.LogicTemplates.HTLC(
+            owner,
+            receiver,
+            hashFn,
+            hashImg,
+            expiryRound,
+            maxFee
+          )
+      );
+    });
+  });
+  describe('Limit Order', () => {
+    it('should match the goldens', () => {
+      // Inputs
+      const owner =
+        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
+      const assetid = 12345;
+      const ratn = 30;
+      const ratd = 100;
+      const expiryRound = 123456;
+      const minTrade = 10000;
+      const maxFee = 5000000;
+      const limitOrder = new algosdk.LogicTemplates.LimitOrder(
+        owner,
+        assetid,
+        ratn,
+        ratd,
+        expiryRound,
+        minTrade,
+        maxFee
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = limitOrder.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I';
+      assert.deepStrictEqual(goldenAddress, limitOrder.getAddress());
+      const secretKey = Buffer.from(
+        'DTKVj7KMON3GSWBwMX9McQHtaDDi8SDEBi0bt4rOxlHNRahLa0zVG+25BDIaHB1dSoIHIsUQ8FFcdnCdKoG+Bg==',
+        'base64'
+      );
+      const actualBlob = algosdk.LogicTemplates.getSwapAssetsTransaction(
+        actualBytes,
+        3000,
+        10000,
+        secretKey,
+        10,
+        1234,
+        2234,
+        'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='
+      );
+      const expectedTxn1 = Buffer.from(
+        'gqRsc2lngaFsxLcBIAoAAcCWsQICkE4EuWBkHsDEByYBIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMRYiEjEQIxIQMQEkDhAyBCMSQABVMgQlEjEIIQQNEDEJMgMSEDMBECEFEhAzAREhBhIQMwEUKBIQMwETMgMSEDMBEiEHHTUCNQExCCEIHTUENQM0ATQDDUAAJDQBNAMSNAI0BA8QQAAWADEJKBIxAiEJDRAxBzIDEhAxCCISEBCjdHhuiaNhbXTNJxCjZmVlzQisomZ2zQTSomdoxCB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaaNncnDEIKz368WOGpdE/Ww0L8wUu5Ly2u2bpG3ZSMKCJvcvGApTomx2zQi6o3JjdsQgzUWoS2tM1RvtuQQyGhwdXUqCByLFEPBRXHZwnSqBvgajc25kxCBd4Wnq60VaWXeVmuPt0x1XgMQSyP4nUnlh5G2Rhyo+taR0eXBlo3BheQ==',
+        'base64'
+      );
+      const expectedTxn2 = Buffer.from(
+        'gqNzaWfEQKXv8Z6OUDNmiZ5phpoQJHmfKyBal4gBZLPYsByYnlXCAlXMBeVFG5CLP1k5L6BPyEG2/XIbjbyM0CGG55CxxAKjdHhuiqRhYW10zQu4pGFyY3bEIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITo2ZlZc0JJKJmds0E0qJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCs9+vFjhqXRP1sNC/MFLuS8trtm6Rt2UjCgib3LxgKU6Jsds0IuqNzbmTEIM1FqEtrTNUb7bkEMhocHV1KggcixRDwUVx2cJ0qgb4GpHR5cGWlYXhmZXKkeGFpZM0wOQ==',
+        'base64'
+      );
+      const expectedBlob = Buffer.concat([expectedTxn1, expectedTxn2]);
+      assert.deepStrictEqual(expectedBlob, Buffer.from(actualBlob));
+    });
+  });
+  describe('Periodic payment', () => {
+    it('should match the goldens', () => {
+      // Inputs
+      const receiver =
+        'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I';
+      const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=';
+      const amount = 500000;
+      const withdrawalWindow = 95;
+      const period = 100;
+      const expiryRound = 2445756;
+      const maxFee = 1000;
+      const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment(
+        receiver,
+        amount,
+        withdrawalWindow,
+        period,
+        expiryRound,
+        maxFee,
+        leaseb64
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = periodicPayment.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY';
+      assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress());
+      const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
+      const goldenStx =
+        'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk=';
+      const goldenStxBlob = Buffer.from(goldenStx, 'base64');
+      const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction(
+        actualBytes,
+        0,
+        1200,
+        goldenGenesisHash
+      );
+      const expectedDict = algosdk.decodeObj(new Uint8Array(goldenStxBlob));
+      const actualDict = algosdk.decodeObj(stx.blob);
+      assert.deepStrictEqual(expectedDict, actualDict);
+    });
+  });
+  describe('Limit Order', () => {
+    it('should match the goldens', () => {
+      // Inputs
+      const owner =
+        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
+      const assetid = 12345;
+      const ratn = 30;
+      const ratd = 100;
+      const expiryRound = 123456;
+      const minTrade = 10000;
+      const maxFee = 5000000;
+      const limitOrder = new algosdk.LogicTemplates.LimitOrder(
+        owner,
+        assetid,
+        ratn,
+        ratd,
+        expiryRound,
+        minTrade,
+        maxFee
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = limitOrder.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I';
+      assert.deepStrictEqual(goldenAddress, limitOrder.getAddress());
+    });
+  });
+  describe('Periodic payment', () => {
+    it('should match the goldens', () => {
+      // Inputs
+      const receiver =
+        'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I';
+      const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=';
+      const amount = 500000;
+      const withdrawalWindow = 95;
+      const period = 100;
+      const expiryRound = 2445756;
+      const maxFee = 1000;
+      const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment(
+        receiver,
+        amount,
+        withdrawalWindow,
+        period,
+        expiryRound,
+        maxFee,
+        leaseb64
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = periodicPayment.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY';
+      assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress());
+      const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
+      const goldenStx =
+        'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk=';
+      const goldenStxBlob = Buffer.from(goldenStx, 'base64');
+      const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction(
+        actualBytes,
+        0,
+        1200,
+        goldenGenesisHash
+      );
+      const expectedDict = algosdk.decodeObj(new Uint8Array(goldenStxBlob));
+      const actualDict = algosdk.decodeObj(stx.blob);
+      assert.deepStrictEqual(expectedDict, actualDict);
+    });
+  });
+  describe('Dynamic Fee', () => {
+    it('should match the goldens', () => {
+      // Inputs
+      const receiver =
+        '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM';
+      const amount = 5000;
+      const firstValid = 12345;
+      const lastValid = 12346;
+      const closeRemainder =
+        '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE';
+      const artificialLease = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
+      const leaseBytes = new Uint8Array(Buffer.from(artificialLease, 'base64'));
+      const dynamicFee = new algosdk.LogicTemplates.DynamicFee(
+        receiver,
+        amount,
+        firstValid,
+        lastValid,
+        closeRemainder,
+        leaseBytes
+      );
+      // Outputs
+      const goldenProgram =
+        'ASAFAgGIJ7lgumAmAyD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEyDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+SB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaTIEIhIzABAjEhAzAAcxABIQMwAIMQESEDEWIxIQMRAjEhAxBygSEDEJKRIQMQgkEhAxAiUSEDEEIQQSEDEGKhIQ';
+      const goldenBytes = Buffer.from(goldenProgram, 'base64');
+      const actualBytes = dynamicFee.getProgram();
+      assert.deepStrictEqual(goldenBytes, actualBytes);
+      const goldenAddress =
+        'GCI4WWDIWUFATVPOQ372OZYG52EULPUZKI7Y34MXK3ZJKIBZXHD2H5C5TI';
+      assert.deepStrictEqual(goldenAddress, dynamicFee.getAddress());
+      const privateKeyOneB64 =
+        'cv8E0Ln24FSkwDgGeuXKStOTGcze5u8yldpXxgrBxumFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT7w==';
+      const privateKeyOne = Buffer.from(privateKeyOneB64, 'base64');
+      const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=';
+      const txnAndLsig = algosdk.LogicTemplates.signDynamicFee(
+        actualBytes,
+        privateKeyOne,
+        goldenGenesisHash
+      );
+      const txnDict = txnAndLsig.txn;
+      const txnObj = new algosdk.Transaction(txnDict);
+      const txnBytes = txnObj.toByte();
+      const goldenTxn =
+        'iqNhbXTNE4ilY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmibHbNMDqibHjEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpo3JjdsQg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohOjc25kxCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76R0eXBlo3BheQ==';
+      assert.deepStrictEqual(
+        new Uint8Array(Buffer.from(goldenTxn, 'base64')),
+        txnBytes
+      );
+      const { lsig } = txnAndLsig;
+      const lsigBytes = lsig.toByte();
+      const goldenLsig =
+        'gqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYH';
+      assert.deepStrictEqual(
+        new Uint8Array(Buffer.from(goldenLsig, 'base64')),
+        lsigBytes
+      );
+      const privateKeyTwoB64 =
+        '2qjz96Vj9M6YOqtNlfJUOKac13EHCXyDty94ozCjuwwriI+jzFgStFx9E6kEk1l4+lFsW4Te2PY1KV8kNcccRg==';
+      const privateKeyTwo = Buffer.from(privateKeyTwoB64, 'base64');
+      const stxns = algosdk.LogicTemplates.getDynamicFeeTransactions(
+        txnDict,
+        lsig,
+        privateKeyTwo,
+        1234,
+        firstValid,
+        lastValid
+      );
+      const goldenStxns =
+        'gqNzaWfEQJBNVry9qdpnco+uQzwFicUWHteYUIxwDkdHqY5Qw2Q8Fc2StrQUgN+2k8q4rC0LKrTMJQnE+mLWhZgMMJvq3QCjdHhuiqNhbXTOAAWq6qNmZWXOAATzvqJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76NzbmTEICuIj6PMWBK0XH0TqQSTWXj6UWxbhN7Y9jUpXyQ1xxxGpHR5cGWjcGF5gqRsc2lngqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYHo3R4boujYW10zROIpWNsb3NlxCDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+aNmZWXOAAWq6qJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiE6NzbmTEIIU9h0wnKapwajF0N7K4zy3orGLF+rQ8kLIk/vW6FhPvpHR5cGWjcGF5';
+      const goldenStxnBytes = Buffer.from(goldenStxns, 'base64');
+      assert.deepStrictEqual(new Uint8Array(goldenStxnBytes), stxns);
+    });
+  });
+});