/*
* 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');
/**
* # 🏭 makePlaceAlgoTxns(order)
*
* > Transaction Factory for Placing Buy Orders
*
*
* Place a buy order into the Algodex {@tutorial Orderbook}. This is referred to as a {@tutorial Maker} order which is
* "Placed into the Orderbook". If the order has been previously placed it will have a contract
* key called "creator". This key determines if the order should call the ALGO delegate contract
* application opt in.
*
* Once the initial transaction has been created and the contract.creator has been sent, the sdk
* reverts to regular payment transactions to the escrow account. These payment transactions
* should include a note that relates to the operation for indexing since they will not have
* the orderbook application call.
*
* The transaction generator also supports bypassing the opt-in check by passing in the optIn flag
*
* ## ALGO Delegate Contract Transactions
*
* ### ➕ Open Order Transactions:
*
* | Index | Direction | Type | Description | Signer |
* | ----- | --------- | ---- | ----------- | ------ |
* | TXN 0 | BUYER TO ESCROW | {@link algosdk.makePaymentTxn} | Pay from order creator to escrow account | {@link Wallet} |
* | TXN 1 | ESCROW TO ORDERBOOK | {@link algosdk.makeApplicationOptInTxn} | Stateful app opt-in to order book | {@link algosdk.LogicSigAccount} |
* | TXN 2 | BUYER TO BUYER | {@link algosdk.makeAssetTransferTxn} | (Optional) ASA opt-in for the order creator's original wallet account | {@link Wallet} |
*
* ### 💰 Add Funds to Order Escrow Transactions:
*
* | Index | Direction | Type | Description | Signer |
* | ----- | --------- | ---- | ----------- | ------ |
* | TXN 0 | BUYER TO ESCROW | {@link algosdk.makePaymentTxn} | Pay from order creator to escrow account | {@link Wallet} |
* | TXN 1 | BUYER TO BUYER | {@link algosdk.makeAssetTransferTxn} | (Optional) ASA opt-in for the order creator's original wallet account | {@link Wallet} |
*
* #
*
*
* @example
* const {makePlaceAlgoTxns, compile} = require('@algodex/algodex-sdk')
* const txns = makePlaceAlgoTxns( await compile({
* 'asset': {
* 'id': 15322902,
* 'decimals': 6,
* },
* 'address': 'WYWRYK42XADLY3O62N52BOLT27DMPRA3WNBT2OBRT65N6OEZQWD4OSH6PI',
* 'price': 2,
* 'amount': 1,
* 'total': 2,
* 'execution': 'maker',
* 'type': 'buy',
* 'appId': 22045503,
* 'version': 6,
* }))
*
* @param {Order} order The Order
* @param {boolean} [optIn] Flag for opting in
* @return {Promise<Transactions>}
* @memberOf module:txns/buy
*/
async function makePlaceAlgoTxns(
order,
optIn = false,
) {
if (!(order.indexer instanceof algosdk.Indexer)) {
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!');
}
if (order.contract?.amount === 0) {
throw new Error('Cannot place a maker order with a 0 Amount');
}
logger.info({order: {price: order.price, amount: order.amount, total: order.total}, optIn}, 'Make Place Algo Txns');
// TODO: Note that contains the creator address and the current operation.
const _note = undefined;
// If the order has a creator key, it already exists
const _exists = typeof order?.contract?.creator !== 'undefined';
if (_exists) {
throw new TypeError('Adding to an existing algo escrow is disabled!');
}
let accountInfo;
if (typeof order?.wallet?.assets === 'undefined' && !optIn) {
logger.warn({address: order.address}, 'Loading account info!');
({account: accountInfo} = await order.indexer.lookupAccountByID(order.address).do()); // We need to have the same structure between the conditionals
} else {
accountInfo = order.wallet;
}
const _optIn = !optIn ?
await getOptedIn(order.indexer, accountInfo, order.asset.id) :
optIn;
const _suggestedParams = await teal.getTransactionParams(order.client, order.contract.params, true);
/**
* Place Algo Structure
* @type {Structures}
*/
const _outerTxns = [{
// Payment Transaction
// TODO: Add Note that tracks this payment transaction when the escrow already exists
unsignedTxn: algosdk.makePaymentTxnWithSuggestedParams(
order.address,
order.contract.lsig.address(),
order.contract.total,
undefined,
_note,
_suggestedParams,
undefined,
),
senderAcct: order.address,
}];
// Open Order Transaction
if (!_exists) {
logger.debug({entry: order.contract.entry.slice(59)}, 'Creating new order!');
/**
* Application Arguments
* @type {Array<Uint8Array>}
*/
const _appArgs = [
enc.encode('open'),
enc.encode(order.contract.entry.slice(59)),
new Uint8Array([order.version]),
];
// Create Escrow appOptIn Transaction
// This contract application args are parsed by the Algodex Indexer
_outerTxns.push({
unsignedTxn: await teal.txns.makeTransactionFromLogicSig(
order.client,
'appOptIn',
order.contract.lsig,
order?.contract?.params,
order.appId,
_appArgs,
),
lsig: order.contract.lsig,
});
}
// Optin Transaction
if (!_optIn) {
logger.debug({address: order.address, asset: order.asset.id}, 'Opting in!');
_outerTxns.push({
unsignedTxn: algosdk.makeAssetTransferTxnWithSuggestedParams(
order.address,
order.address,
undefined,
undefined,
0,
undefined,
order.asset.id,
_suggestedParams,
undefined,
),
senderAcct: order.address,
});
}
return _outerTxns;
}
module.exports = makePlaceAlgoTxns;