lib/order/txns/sell/makeExecuteAssetTxns.js

/*
 * Copyright (C) 2021-2022 Algodex VASP (BVI) Corp.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

const algosdk = require('algosdk');
const logger = require('../../../logger');
const AlgodError = require('../../../error/AlgodError');
const enc = require('../../../utils/encoder');
const teal = require('../../../teal');
const getOptedIn = require('../../../wallet/getOptedIn');

/**
 * # 🏭 makeExecuteAssetTxns(order)
 *
 *   ### ☠ Partial Order Execution:
 *
 * | Index | Direction | Type | Description | Signer |
 * | ----- | --------- | ---- | ----------- | ------ |
 * | TXN 0 | ESCROW TO ORDERBOOK | {@link algosdk.makeApplicationNoOpTxn} | Application call to execute | {@link Wallet} |
 * | TXN 1 | BUYER TO SELLER  | {@link algosdk.makePaymentTxn} | Pay transaction (from buyer/executor to escrow owner | {@link algosdk.LogicSigAccount} |
 * | TXN 2 | BUYER TO BUYER | {@link algosdk.makeAssetTransferTxn} | (Optional) asset opt-in transaction (for buyer/executor) | {@link Wallet} |
 * | TXN 2/3 | ESCROW TO BUYER | {@link algosdk.makeAssetTransferTxn} | Asset transfer (from escrow to buyer/executor) | {@link Wallet} |
 * | TXN 3/4 | BUYER TO ESCROW | {@link algosdk.makePaymentTxn} | Pay transaction for fee refund (from buyer/executor to escrow) | {@link Wallet} |
 *
 *
 *
 * ### ☠️ Full Order Execution:
 *
 * | Index | Direction | Type | Description | Signer |
 * | ----- | --------- | ---- | ----------- | ------ |
 * | TXN 0 | ESCROW TO ORDERBOOK | {@link algosdk.makeApplicationCloseOutTxn} | Transaction must be a call to a stateful contract | {@link Wallet} |
 * | TXN 1 | BUYER TO SELLER | {@link algosdk.makePaymentTxnWithSuggestedParams} |  Pay transaction (from buyer/executor to escrow owner) | {@link Wallet} |
 * | TXN 2 | BUYER TO BUYER | {@link algosdk.makeAssetTransferTxn} | (Optional) asset opt-in transaction (for buyer/executor) | {@link Wallet} |
 * | TXN 2/3 | ESCROW TO BUYER | {@link algosdk.makeAssetTransferTxn} |  Asset transfer (from escrow to buyer/executor) - closes out any remaining ASA to seller (escrow owner) as well | {@link Wallet} |
 * | TXN 3/4 | ESCROW TO SELLER | {@link algosdk.makePaymentTxn} | Pay transaction to close out to escrow owner | {@link Wallet} |
 *
 *
 *
 * #
 *
 * @param {Order} order The Order
 * @param {boolean} [withCloseout] Flag to closeout the executable order
 * @return {Promise<Transactions>}
 * @memberOf module:txns/sell
 */
async function makeExecuteAssetTxns(
    order,
    withCloseout,
) {
  if (!(order.client instanceof algosdk.Algodv2)) {
    throw new AlgodError('Order must have a valid SDK client');
  }

  if (typeof order.appId !== 'number') {
    throw new TypeError('Must have valid Application Index');
  }

  if (typeof order.contract !== 'undefined' && typeof order.contract.entry !== 'string') {
    throw new TypeError('Order must have a valid contract state with an entry!');
  }

  if (order.execution === 'maker') {
    throw new Error('Must be maker only mode!');
  }


  // TODO use optIn
  const _optedIn = await getOptedIn(order.indexer, order.wallet, order.asset.id); // why are we passing the address when the method expects an object?
  // ToDO: discuss the below
  // This is part of ensuring the splitTransactions remain unique

  const _selectedEscrowOrder = order?.orderBookEscrowEntry;

  order.contract.params = {
    ...await teal.getTransactionParams(order.client, order.contract.params, true), // not sure if this needs updating. Is the cache hiding the error?
    flatFee: false,
    fee: 0,
  };

  /**
       * taker Asset Structures
       * @type {Structures}
       */

  /**
         * Application Arguments
         * @type {Array}
         */
  const _appAccts = [
    order.contract.creator,
    order.wallet.address,
  ];

  //   scrappy way to add batch uniqueness without encoding extra appArg
  const uniqueNote = enc.encode(`${Math.random()}`);

  /**
         * Application Arguments
         * @type {Array<Uint8Array>}
         */
  const _appArgs = [
    enc.encode(
            withCloseout ?
                'execute_with_closeout' :
                'execute',
    ),
    enc.encode(order.contract.entry), // Will there ever be an entry that includes the address, if so .slice(59)?
    // new Uint8Array([order.version]),
  ];

  if (typeof _selectedEscrowOrder?.txnNum === 'number') _appArgs.push(enc.encode(_selectedEscrowOrder.txnNum));

  const _outerTxns = [{
    // Payment Transaction
    unsignedTxn: withCloseout ?
            algosdk.makeApplicationCloseOutTxn(
                order.contract.lsig.address(),
                order.contract.params,
                order.appId,
                _appArgs,
                _appAccts,
                [0],
                [order.asset.id],
                uniqueNote) :
            algosdk.makeApplicationNoOpTxn(
                order.contract.lsig.address(),
                order.contract.params,
                order.appId,
                _appArgs,
                _appAccts,
                [0],
                [order.asset.id],
                uniqueNote),

    lsig: order.contract.lsig,
  }];

  //   Transaction rerpresenting taker address paying escrowCreator for asset
  _outerTxns.push({
    unsignedTxn: algosdk.makePaymentTxnWithSuggestedParams(
        order.wallet.address,
        order.contract.creator,
        order.contract.total,
        undefined, // was breaking because passing closeToRemainder in makePaymentTxn
        uniqueNote,
        order.contract.params,
        undefined,
    ),
    senderAcct: order.wallet.address,
  });

  if (!_optedIn) {
    logger.debug({address: order.address, asset: order.asset.id}, 'Opting in!');
    // OptIn Transaction
    _outerTxns.push({
      unsignedTxn: algosdk.makeAssetTransferTxnWithSuggestedParams(
          order.wallet.address,
          order.wallet.address,
          undefined,
          undefined,
          0,
          uniqueNote,
          order.asset.id,
          order.contract.params,
          undefined,
      ),
      senderAcct: order.wallet.address,
    });
  }

  //   Payment represents escrow sending asset to taker
  _outerTxns.push({
    unsignedTxn: algosdk.makeAssetTransferTxnWithSuggestedParams(
        order.contract.lsig.address(),
        order.wallet.address,
        withCloseout ? order.contract.creator : undefined, // undefined unless shouldClose === true
        undefined,
        order.contract.amount,
        uniqueNote,
        order.asset.id,
        order.contract.params,
        undefined,
    ),
    lsig: order.contract.lsig,
  });

  _outerTxns[1].unsignedTxn.flatFee = true;
  if (!_optedIn) _outerTxns[2].flatFee = true;

  const refundFees = 0.002 * 1000000; // this was what refund fees were in v1

  _outerTxns.push(withCloseout ? { // TODO: Make sure that you check the sell side to see if that conditional needs modification
    unsignedTxn:
            algosdk.makePaymentTxnWithSuggestedParams(
                order.contract.lsig.address(),
                order.contract.creator,
                0,
                order.contract.creator,
                undefined,
                order.contract.params,
            ),


    lsig: order.contract.lsig,

  } : {
    unsignedTxn: algosdk.makePaymentTxnWithSuggestedParams(
        order.wallet.address,
        order.contract.lsig.address(),
        refundFees,
            withCloseout ? order.contract.creator : undefined,
            uniqueNote,
            order.contract.params,
            undefined,
    ),
    senderAcct: order.wallet.address,
  },
  );

  return _outerTxns;
}

module.exports = makeExecuteAssetTxns;