UNPKG

@algodex/algodex-sdk-patch

Version:

API calls for interacting with the Algorand blockchain

1,170 lines (964 loc) 59.2 kB
///////////////////////////// // Alexander Trefonas // // 7/9/2021 // // Copyright Algodev Inc // // All Rights Reserved. // ///////////////////////////// const http = require('http'); const algosdk = require('algosdk'); const BigN = require('js-big-decimal'); const TextEncoder = require("text-encoding").TextEncoder; const axios = require('axios').default; const LESS_THAN = -1; const EQUAL = 0; const GREATER_THAN = 1; let MyAlgo = null; let myAlgoWalletUtil = null; if (typeof window != 'undefined' ) { MyAlgo = require('@randlabs/myalgo-connect'); myAlgoWalletUtil = require('./MyAlgoWalletUtil.js'); } if(process.env.NODE_ENV === 'test') { myAlgoWalletUtil = require('./MyAlgoWalletUtil.js'); MyAlgo = function TestMyAlgo() { if (!new.target) { throw Error("Cannot be called without the new keyword"); } this.signTransaction = () => true } } const algoDelegateTemplate = require('./algo_delegate_template_teal.js'); const algoDelegateTemplateV4 = require('./algo_delegate_template_teal_v4.js'); const algoDelegateTemplateV5 = require('./algo_delegate_template_teal_v5.js'); const algoDelegateTemplateV6 = require('./algo_delegate_template_teal_v6.js'); const algoDelegateTemplateV7 = require('./algo_delegate_template_teal_v7.js'); const asaDelegateTemplate = require('./ASA_delegate_template_teal.js'); const asaDelegateTemplateV4 = require('./ASA_delegate_template_teal_v4.js'); const asaDelegateTemplateV5 = require('./ASA_delegate_template_teal_v5.js'); const asaDelegateTemplateV6 = require('./ASA_delegate_template_teal_v6.js'); const asaDelegateTemplateV7 = require('./ASA_delegate_template_teal_v7.js'); //require('./dex_teal.js'); //FIXME - import below from algodex_api.js let myAlgoWallet = null; if (MyAlgo != null) { // console.debug("pointing to bridge URL"); myAlgoWallet = new MyAlgo(); } const constants = require('./constants.js'); let ALGO_ESCROW_ORDER_BOOK_ID = -1; let ASA_ESCROW_ORDER_BOOK_ID = -1; let ALGOD_SERVER = constants.TEST_ALGOD_SERVER; let ALGOD_PORT = constants.TEST_ALGOD_PORT; let ALGOD_TOKEN = constants.TEST_ALGOD_TOKEN; let ALGOD_INDEXER_SERVER = constants.TEST_INDEXER_SERVER; let ALGOD_INDEXER_PORT = constants.TEST_INDEXER_PORT; let ALGOD_INDEXER_TOKEN = constants.TEST_INDEXER_TOKEN; let compilationResults = {}; const AlgodexInternalApi = { setAlgodServer : function (algod_server) { ALGOD_SERVER = algod_server; }, setAlgodToken : function (algod_token) { ALGOD_TOKEN = algod_token; }, setAlgodPort : function (algod_port) { ALGOD_PORT = algod_port; }, setAlgodIndexer : function (server, port, token) { ALGOD_INDEXER_SERVER = server; ALGOD_INDEXER_PORT = port; ALGOD_INDEXER_TOKEN = token; }, doAlertInternal : function doAlertInternal() { alert(2); console.debug("internal api call!!!"); }, initSmartContracts : function initSmartContracts(algoOrderBookId, asaOrderBookId) { ALGO_ESCROW_ORDER_BOOK_ID = algoOrderBookId; ASA_ESCROW_ORDER_BOOK_ID = asaOrderBookId; }, // call application createTransactionFromLogicSig : async function createTransactionFromLogicSig(client, lsig, AppID, appArgs, transType, params) { // define sender try { const sender = lsig.address(); // get node suggested parameters if (params == null) { params = await client.getTransactionParams().do(); } // create unsigned transaction let txn = null; if (transType == "appNoOp") { txn = algosdk.makeApplicationNoOpTxn(sender, params, AppID, appArgs) } else if (transType == "appOptIn") { txn = algosdk.makeApplicationOptInTxn(lsig.address(), params, AppID, appArgs); } return txn; } catch (e) { throw e; } }, // Generate order number generateOrder : function generateOrder(makerWalletAddr, N, D, min, assetId, includeMakerAddr=true) { let rtn = N + "-" + D + "-" + min + "-" + assetId; if (includeMakerAddr) { rtn = makerWalletAddr + "-" + rtn; } console.debug("generateOrder final str is: " + rtn); return rtn; }, dumpVar : function dumpVar(x) { return JSON.stringify(x, null, 2); }, getExecuteOrderTransactionsAsTakerFromOrderEntry : async function getExecuteOrderTransactionsAsTakerFromOrderEntry(algodClient, orderBookEscrowEntry, takerCombOrderBalance, params, walletConnector) { console.debug("looking at another orderbook entry to execute orderBookEscrowEntry: " + this.dumpVar(orderBookEscrowEntry)); // rec contains the original order creators address let orderCreatorAddr = orderBookEscrowEntry['orderCreatorAddr']; let n = orderBookEscrowEntry['n']; let d = orderBookEscrowEntry['d']; let min = 0; //orders are set to 0 minimum for now let assetid = orderBookEscrowEntry['assetId']; let isASAEscrow = orderBookEscrowEntry['isASAEscrow']; let escrowSource = this.buildDelegateTemplateFromArgs(min,assetid,n,d,orderCreatorAddr, isASAEscrow, orderBookEscrowEntry['version']); const enableLsigLogging = constants.DEBUG_SMART_CONTRACT_SOURCE; // escrow logging let lsig = await this.getLsigFromProgramSource(algosdk, algodClient, escrowSource,enableLsigLogging); if (!isASAEscrow) { console.debug("NOT asa escrow"); return await this.getExecuteAlgoOrderTxnsAsTaker(orderBookEscrowEntry, algodClient ,lsig, takerCombOrderBalance, params, walletConnector); } else { console.debug("asa escrow"); return await this.getExecuteASAOrderTxns(orderBookEscrowEntry, algodClient, lsig, takerCombOrderBalance, params, walletConnector); } }, // Helper function to get ASA Order Txns (3-4 transactions) getExecuteASAOrderTakerTxnAmounts(takerCombOrderBalance, orderBookEscrowEntry) { console.debug("printing!!!"); console.debug({takerCombOrderBalance, orderBookEscrowEntry}); const orderBookEntry = orderBookEscrowEntry['orderEntry']; const min_asa_balance = 0; // 1000000-250000-0-15322902 // n-d-minOrderSize-assetId const orderBookEntrySplit = orderBookEntry.split("-"); const n = orderBookEntrySplit[0]; const d = orderBookEntrySplit[1]; let escrowAsaTradeAmount = orderBookEscrowEntry['asaBalance']; const currentEscrowASABalance = orderBookEscrowEntry['asaBalance']; const price = new BigN(d).divide(new BigN(n), 30); const bDecOne = new BigN(1); const executionFees = 0.004 * 1000000; let closeoutFromASABalance = true; escrowAsaTradeAmount = new BigN(escrowAsaTradeAmount); let algoTradeAmount = price.multiply(escrowAsaTradeAmount); if (algoTradeAmount.getValue().includes('.')) { algoTradeAmount = algoTradeAmount.floor().add(bDecOne); //round up to give seller more money } //FIXME - check if lower than order balance const maxTradeAmount = Math.min(takerCombOrderBalance['algoBalance'], takerCombOrderBalance['walletAlgoBalance'] - executionFees); const emptyReturnVal = { 'algoTradeAmount': 0, 'escrowAsaTradeAmount': 0, 'executionFees': 0, 'closeoutFromASABalance': false } if (algoTradeAmount.compareTo(new BigN(maxTradeAmount)) == GREATER_THAN && algoTradeAmount.compareTo(bDecOne) == GREATER_THAN && algoTradeAmount.subtract(new BigN(maxTradeAmount)).compareTo(bDecOne) == GREATER_THAN) { console.debug("here999a reducing algoTradeAmount, currently at: " + algoTradeAmount.getValue()); algoTradeAmount = new BigN(maxTradeAmount); escrowAsaTradeAmount = algoTradeAmount.divide(price, 30); console.debug("checking max: " + escrowAsaTradeAmount.getValue() + " " + 1 ); if (escrowAsaTradeAmount.compareTo(bDecOne) == LESS_THAN) { //don't allow 0 value escrowAsaTradeAmount = bDecOne; } console.debug("here999b reduced to algoTradeAmount escrowAsaAmount", algoTradeAmount.getValue(), escrowAsaTradeAmount.getValue()); if (escrowAsaTradeAmount.getValue().includes('.')) { //round ASA amount escrowAsaTradeAmount = escrowAsaTradeAmount.floor(); algoTradeAmount = price.multiply(escrowAsaTradeAmount); if (algoTradeAmount.getValue().includes('.')) { algoTradeAmount = algoTradeAmount.floor().add(bDecOne); //round up to give seller more money console.debug("here999bc increased algo to algoTradeAmount escrowAsaAmount", algoTradeAmount.getValue(), escrowAsaTradeAmount.getValue()); } console.debug("here999c changed to algoTradeAmount escrowAsaAmount", algoTradeAmount.getValue(), escrowAsaTradeAmount.getValue()); } } //FIXME: factor in fees? if (new BigN(currentEscrowASABalance).subtract(escrowAsaTradeAmount) .compareTo(new BigN(min_asa_balance)) == GREATER_THAN) { console.debug("asa escrow here9992 (currentASABalance - escrowAsaAmount) > min_asa_balance", currentEscrowASABalance, escrowAsaTradeAmount.getValue(), min_asa_balance); closeoutFromASABalance = false; } if (takerCombOrderBalance['walletAlgoBalance'] < executionFees + parseInt(algoTradeAmount.getValue())) { console.debug("here9992b algo balance too low, returning early! ", executionFees, algoTradeAmount.getValue(), takerCombOrderBalance); return emptyReturnVal; //no balance left to use for buying ASAs } escrowAsaTradeAmount = parseInt(escrowAsaTradeAmount.getValue()); algoTradeAmount = parseInt(algoTradeAmount.getValue()); if (escrowAsaTradeAmount <= 0) { console.debug("here77zz escrowAsaTradeAmount is at 0 or below. returning early! nothing to do"); return emptyReturnVal; } if (algoTradeAmount <= 0) { console.debug("here77zb algoTradeAmount is at 0 or below. returning early! nothing to do"); return emptyReturnVal; } //FIXME - need more logic to transact correct price in case balances dont match order balances console.debug("closeoutFromASABalance: " + closeoutFromASABalance); console.debug("almost final amounts algoTradeAmount escrowAsaAmount ", algoTradeAmount, escrowAsaTradeAmount); //algoTradeAmount = algoTradeAmount / 2; console.debug("n: ", n, " d: ", d, " asset amount: " , escrowAsaTradeAmount); return { 'algoTradeAmount': algoTradeAmount, 'escrowAsaTradeAmount': escrowAsaTradeAmount, 'executionFees': executionFees, 'closeoutFromASABalance': closeoutFromASABalance } }, getExecuteASAOrderTxns : async function getExecuteASAOrderTxns(orderBookEscrowEntry, algodClient, lsig, takerCombOrderBalance, params, walletConnector) { console.debug("inside executeASAOrder!", this.dumpVar(takerCombOrderBalance)); console.debug("orderBookEscrowEntry ", this.dumpVar(orderBookEscrowEntry)); try { let retTxns = []; let appAccts = []; const orderCreatorAddr = orderBookEscrowEntry['orderCreatorAddr']; const orderBookEntry = orderBookEscrowEntry['orderEntry']; const appId = ASA_ESCROW_ORDER_BOOK_ID; const takerAddr = takerCombOrderBalance['takerAddr']; const assetId = orderBookEscrowEntry['assetId']; appAccts.push(orderCreatorAddr); appAccts.push(takerAddr); let closeRemainderTo = undefined; const refundFees = 0.002 * 1000000; // fees refunded to escrow in case of partial execution const {algoTradeAmount, escrowAsaTradeAmount, executionFees, closeoutFromASABalance: initialCloseoutFromASABalance} = this.getExecuteASAOrderTakerTxnAmounts(takerCombOrderBalance, orderBookEscrowEntry); if (algoTradeAmount == 0) { console.debug("nothing to do, returning early"); return null; } let closeoutFromASABalance = initialCloseoutFromASABalance; console.debug('closeoutFromASABalance here111: ' + closeoutFromASABalance); if (orderBookEscrowEntry.useForceShouldCloseOrNot) { closeoutFromASABalance = orderBookEscrowEntry.forceShouldClose; console.debug('closeoutFromASABalance here222: ' + closeoutFromASABalance); } takerCombOrderBalance['algoBalance'] -= executionFees; takerCombOrderBalance['algoBalance'] -= algoTradeAmount; takerCombOrderBalance['walletAlgoBalance'] -= executionFees; takerCombOrderBalance['walletAlgoBalance'] -= algoTradeAmount; takerCombOrderBalance['asaBalance'] += escrowAsaTradeAmount; takerCombOrderBalance['walletASABalance'] += escrowAsaTradeAmount; console.debug("ASA here110 algoAmount asaAmount txnFee takerOrderBalance: ", algoTradeAmount, escrowAsaTradeAmount, executionFees, this.dumpVar(takerCombOrderBalance)); console.debug("receiving ASA " + escrowAsaTradeAmount + " from " + lsig.address()); console.debug("sending ALGO amount " + algoTradeAmount + " to " + orderCreatorAddr); if (closeoutFromASABalance == true) { // only closeout if there are no more ASA in the account console.debug('closeoutFromASABalance here333: ' + closeoutFromASABalance); closeRemainderTo = orderCreatorAddr; } let transaction1 = null; let appCallType = null; if (closeRemainderTo == undefined) { appCallType = "execute"; } else { appCallType = "execute_with_closeout"; } let appArgs = []; var enc = new TextEncoder(); appArgs.push(enc.encode(appCallType)); appArgs.push(enc.encode(orderBookEntry)); if (orderBookEscrowEntry.txnNum != null) { //uniquify this transaction even if this arg isn't used appArgs.push(enc.encode(orderBookEscrowEntry.txnNum)); } // appArgs.push(algosdk.decodeAddress(orderCreatorAddr).publicKey); //appArgs.push(enc.encode(assetId)); console.debug(appArgs.length); if (closeRemainderTo == undefined) { transaction1 = algosdk.makeApplicationNoOpTxn(lsig.address(), params, appId, appArgs, appAccts, [0], [assetId]); } else { transaction1 = algosdk.makeApplicationCloseOutTxn(lsig.address(), params, appId, appArgs, appAccts, [0], [assetId]); } console.debug("app call type is: " + appCallType); let fixedTxn2 = { type: 'pay', from: takerAddr, to: orderCreatorAddr, amount: algoTradeAmount, ...params }; // *** const takerAlreadyOptedIntoASA = takerCombOrderBalance.takerIsOptedIn; console.debug({takerAlreadyOptedIntoASA}); // asset opt-in transfer let transaction2b = null; if (!takerAlreadyOptedIntoASA) { transaction2b = { type: "axfer", from: takerAddr, to: takerAddr, amount: 0, assetIndex: assetId, ...params }; } // Make asset xfer // Asset transfer from escrow account to order executor let transaction3 = algosdk.makeAssetTransferTxnWithSuggestedParams(lsig.address(), takerAddr, closeRemainderTo, undefined, escrowAsaTradeAmount, undefined, assetId, params); let transaction4 = null; if (closeRemainderTo != undefined) { // Make payment tx signed with lsig back to owner creator console.debug("making transaction4 due to closeRemainderTo"); transaction4 = algosdk.makePaymentTxnWithSuggestedParams(lsig.address(), orderCreatorAddr, 0, orderCreatorAddr, undefined, params); } else { // Make fee refund transaction transaction4 = { type: 'pay', from: takerAddr, to: lsig.address(), amount: refundFees, ...params }; } myAlgoWalletUtil.setTransactionFee(fixedTxn2); if (transaction2b != null) { myAlgoWalletUtil.setTransactionFee(transaction2b); } let txns = []; txns.push(transaction1); txns.push(fixedTxn2); if (transaction2b != null) { console.debug("adding transaction2b due to asset not being opted in"); txns.push(transaction2b); } else { console.debug("NOT adding transaction2b because already opted"); } txns.push(transaction3); txns.push(transaction4); if (closeRemainderTo != undefined ) { txns = this.formatTransactionsWithMetadata(txns, takerAddr, orderBookEscrowEntry, 'execute_full', 'asa') } else { txns = this.formatTransactionsWithMetadata(txns, takerAddr, orderBookEscrowEntry, 'execute_partial', 'asa') } // it goes by reference so modifying array affects individual objects and vice versa if (!!walletConnector && walletConnector.connector.connected) { retTxns.push({ 'unsignedTxn': transaction1, 'lsig': lsig }); retTxns.push({ 'unsignedTxn': fixedTxn2, 'needsUserSig': true, amount: fixedTxn2.amount, txType: "algo", }); if (transaction2b != null) { retTxns.push({ 'unsignedTxn': transaction2b, 'needsUserSig': true }); } retTxns.push({ 'unsignedTxn': transaction3, amount: escrowAsaTradeAmount, txType: "asa", 'lsig': lsig }); retTxns.push({ 'unsignedTxn': transaction4, 'needsUserSig': true }); return retTxns } const groupID = algosdk.computeGroupID(txns); for (let i = 0; i < txns.length; i++) { txns[i].group = groupID; } let signedTx1 = algosdk.signLogicSigTransactionObject(transaction1, lsig); //let signedTx2 = await myAlgoWallet.signTransaction(fixedTxn2); let signedTx3 = algosdk.signLogicSigTransactionObject(transaction3, lsig); let signedTx4 = null; if (closeRemainderTo != undefined) { signedTx4 = algosdk.signLogicSigTransactionObject(transaction4, lsig); } retTxns.push({ 'signedTxn': signedTx1.blob, }); retTxns.push({ 'unsignedTxn': fixedTxn2, 'needsUserSig': true, amount: fixedTxn2.amount, txType: "algo", }); if (transaction2b != null) { retTxns.push({ 'unsignedTxn': transaction2b, 'needsUserSig': true }); } retTxns.push({ 'signedTxn': signedTx3.blob, amount: escrowAsaTradeAmount, txType: "asa", }); if (signedTx4 != null) { retTxns.push({ 'signedTxn': signedTx4.blob, }); } else { retTxns.push({ 'unsignedTxn': transaction4, 'needsUserSig': true }); } return retTxns; } catch (e) { console.debug(e); if (e.text != undefined) { alert(e.text); } else { alert(e); } } }, getExecuteAlgoOrderTakerTxnAmounts(orderBookEscrowEntry, takerCombOrderBalance) { console.debug("orderBookEscrowEntry, takerCombOrderBalance", this.dumpVar(orderBookEscrowEntry), this.dumpVar( takerCombOrderBalance) ); const orderCreatorAddr = orderBookEscrowEntry['orderCreatorAddr']; const orderBookEntry = orderBookEscrowEntry['orderEntry']; const currentEscrowAlgoBalance = orderBookEscrowEntry['algoBalance']; let algoAmountReceiving = orderBookEscrowEntry['algoBalance']; const assetId = orderBookEscrowEntry['assetId']; const takerAddr = takerCombOrderBalance['takerAddr']; console.debug("assetid: " + assetId); let orderBookEntrySplit = orderBookEntry.split("-"); let n = orderBookEntrySplit[0]; let d = orderBookEntrySplit[1]; let appAccts = []; appAccts.push(orderCreatorAddr); appAccts.push(takerAddr); // Call stateful contract const txnFee = 0.002 * 1000000; algoAmountReceiving -= txnFee; // this will be the transfer amount console.debug("here1"); console.debug("takerOrderBalance: " + this.dumpVar(takerCombOrderBalance)); console.debug("algoAmount: " + algoAmountReceiving); const price = new BigN(d).divide(new BigN(n), 30); const bDecOne = new BigN(1); const emptyReturnVal = { 'algoAmountReceiving': 0, 'asaAmountSending': 0, 'txnFee': 0 }; if (algoAmountReceiving <= 0) { console.debug("here5"); console.debug("can't afford, returning early"); return emptyReturnVal; // can't afford any transaction! } algoAmountReceiving = new BigN(algoAmountReceiving); let asaAmount = algoAmountReceiving.divide(price, 30); console.debug("here6"); console.debug("asa amount: " + asaAmount.getValue()); let hasSpecialCaseOkPrice = false; if (asaAmount.getValue().includes('.') && asaAmount.compareTo(bDecOne) == LESS_THAN) { // Since we can only sell at least one unit, figure out the 'real' price we are selling at, // since we will need to adjust upwards the ASA amount to 1, giving a worse deal for the seller (taker) let adjPrice = asaAmount.multiply(price); const takerLimitPrice = new BigN(takerCombOrderBalance['limitPrice']); console.debug("here6a2 figuring out adjusted price for hasSpecialCaseGoodPrice", {adjPrice, asaAmount, price, takerLimitPrice}); if (adjPrice.compareTo(takerLimitPrice) == GREATER_THAN) { hasSpecialCaseOkPrice = true; } } if (asaAmount.getValue().includes('.') && asaAmount.compareTo(bDecOne) == LESS_THAN && hasSpecialCaseOkPrice) { console.debug("here6aa asa less than one, changing ASA amount to 1"); asaAmount = bDecOne; algoAmountReceiving = price.multiply(bDecOne); if (algoAmountReceiving.getValue().includes('.')) { // give slightly worse deal for taker if decimal algoAmountReceiving = algoAmountReceiving.floor(); console.debug("here6aa decreasing algoAmount due to decimal: " + algoAmountReceiving.getValue()); } if (new BigN(currentEscrowAlgoBalance).compareTo(algoAmountReceiving) == LESS_THAN) { algoAmountReceiving = new BigN(currentEscrowAlgoBalance); } algoAmountReceiving = algoAmountReceiving.subtract(new BigN(0.002 * 1000000)); // reduce for fees } else if (asaAmount.getValue().includes('.')) { // round down decimals. possibly change this later? asaAmount = asaAmount.floor(); console.debug("here7"); console.debug("increasing from decimal asa amount: " + asaAmount.getValue()); // recalculating receiving amount // use math.floor to give slightly worse deal for taker algoAmountReceiving = asaAmount.multiply(price).floor(); console.debug("recalculating receiving amount to: " + algoAmountReceiving.getValue()); } if (new BigN(takerCombOrderBalance['asaBalance']).compareTo(asaAmount) == LESS_THAN) { console.debug("here8"); console.debug("here8 reducing asa amount due to taker balance: ", asaAmount.getValue()); asaAmount = new BigN(takerCombOrderBalance['asaBalance']); console.debug("here8 asa amount is now: ", asaAmount.getValue()); algoAmountReceiving = price.multiply(asaAmount); console.debug("here9"); console.debug("recalculating algoamount: " + algoAmountReceiving.getValue()); if (algoAmountReceiving.getValue().includes('.')) { // give slightly worse deal for taker if decimal algoAmountReceiving = algoAmountReceiving.floor(); console.debug("here10 increasing algoAmount due to decimal: " + algoAmountReceiving.getValue()); } } console.debug("almost final ASA amount: " + asaAmount.getValue()); // These are expected to be integers now algoAmountReceiving = parseInt(algoAmountReceiving.getValue()); asaAmount = parseInt(asaAmount.getValue()); algoAmountReceiving = Math.max(0, algoAmountReceiving); return { 'algoAmountReceiving': algoAmountReceiving, 'asaAmountSending': asaAmount, 'txnFee': txnFee } }, // Helper function to execute the order (3 transactions) // escrowAsaAmount is not used currently getExecuteAlgoOrderTxnsAsTaker : async function getExecuteAlgoOrderTxnsAsTaker(orderBookEscrowEntry, algodClient, lsig, takerCombOrderBalance, params, walletConnector) { try { console.debug("in getExecuteAlgoOrderTxnsAsTaker"); console.debug("orderBookEscrowEntry, algodClient, takerCombOrderBalance", this.dumpVar(orderBookEscrowEntry), algodClient, takerCombOrderBalance); const orderCreatorAddr = orderBookEscrowEntry['orderCreatorAddr']; const orderBookEntry = orderBookEscrowEntry['orderEntry']; const appId = ALGO_ESCROW_ORDER_BOOK_ID; const currentEscrowAlgoBalance = orderBookEscrowEntry['algoBalance']; const assetId = orderBookEscrowEntry['assetId']; const takerAddr = takerCombOrderBalance['takerAddr']; console.debug("assetid: " + assetId); let retTxns = []; let appArgs = []; var enc = new TextEncoder(); let appAccts = []; appAccts.push(orderCreatorAddr); appAccts.push(takerAddr); // Call stateful contract let closeRemainderTo = undefined; const refundFees = 0.002 * 1000000; // fees refunded to escrow in case of partial execution const {algoAmountReceiving, asaAmountSending, txnFee} = this.getExecuteAlgoOrderTakerTxnAmounts(orderBookEscrowEntry, takerCombOrderBalance); if (algoAmountReceiving == 0) { console.debug("algoAmountReceiving is 0, nothing to do, returning early"); return null; } takerCombOrderBalance['algoBalance'] -= txnFee; takerCombOrderBalance['algoBalance'] += algoAmountReceiving; takerCombOrderBalance['asaBalance'] -= asaAmountSending; console.debug("here11 algoAmount asaAmount txnFee takerOrderBalance: ", algoAmountReceiving, asaAmountSending, txnFee, this.dumpVar(takerCombOrderBalance)); console.debug("receiving " + algoAmountReceiving + " from " + lsig.address()); console.debug("sending ASA amount " + asaAmountSending + " to " + orderCreatorAddr); if (currentEscrowAlgoBalance - algoAmountReceiving < constants.MIN_ESCROW_BALANCE) { closeRemainderTo = orderCreatorAddr; } if (orderBookEscrowEntry.useForceShouldCloseOrNot) { if (orderBookEscrowEntry.forceShouldClose === true) { closeRemainderTo = orderCreatorAddr; } else { closeRemainderTo = undefined; } } let appCallType = null; if (closeRemainderTo == undefined) { appCallType = "execute"; } else { appCallType = "execute_with_closeout"; } console.debug("arg1: " + appCallType); console.debug("arg2: " + orderBookEntry); appArgs.push(enc.encode(appCallType)); appArgs.push(enc.encode(orderBookEntry)); if (orderBookEscrowEntry.txnNum != null) { //uniquify this transaction even if this arg isn't used appArgs.push(enc.encode(orderBookEscrowEntry.txnNum)); } // appArgs.push(algosdk.decodeAddress(orderCreatorAddr).publicKey); console.debug(appArgs.length); let transaction1 = null; if (closeRemainderTo == undefined) { transaction1 = algosdk.makeApplicationNoOpTxn(lsig.address(), params, appId, appArgs, appAccts); } else { transaction1 = algosdk.makeApplicationCloseOutTxn(lsig.address(), params, appId, appArgs, appAccts); } // Make payment tx signed with lsig let transaction2 = algosdk.makePaymentTxnWithSuggestedParams(lsig.address(), takerAddr, algoAmountReceiving, closeRemainderTo, undefined, params); // Make asset xfer const transaction3 = { type: "axfer", from: takerAddr, to: orderCreatorAddr, amount: asaAmountSending, assetIndex: assetId, ...params }; let transaction4 = null; if (closeRemainderTo == undefined) { // create refund transaction for fees transaction4 = { type: 'pay', from: takerAddr, to: lsig.address(), amount: refundFees, ...params }; } // delete fixedTxn1.note; delete transaction3.note; //delete fixedTxn1.lease; delete transaction3.lease; delete transaction3.appArgs; //myAlgoWalletUtil.setTransactionFee(fixedTxn1); myAlgoWalletUtil.setTransactionFee(transaction3); let txns = [transaction1, transaction2, transaction3]; if (transaction4 != null) { txns.push(transaction4); } if (closeRemainderTo ==undefined) { txns = this.formatTransactionsWithMetadata(txns, takerAddr, orderBookEscrowEntry, 'execute_partial', 'algo') } else { txns = this.formatTransactionsWithMetadata(txns, takerAddr, orderBookEscrowEntry, 'execute_full', 'algo') } //algosdk.assignGroupID(txns); if (!!walletConnector && walletConnector.connector.connected) { retTxns.push({ 'unsignedTxn': transaction1, 'lsig': lsig }); retTxns.push({ 'unsignedTxn': transaction2, 'amount': transaction2.amount, 'lsig': lsig, 'txType': "algo", }); retTxns.push({ 'unsignedTxn': transaction3, 'needsUserSig': true, 'amount': transaction3.amount, 'txType': "asa", 'lsig': lsig }); retTxns.push({ 'unsignedTxn': transaction4, 'needsUserSig': true }); return retTxns } const groupID = algosdk.computeGroupID(txns); for (let i = 0; i < txns.length; i++) { txns[i].group = groupID; } let signedTx1 = algosdk.signLogicSigTransactionObject(txns[0], lsig); let signedTx2 = algosdk.signLogicSigTransactionObject(txns[1], lsig); retTxns.push({ 'signedTxn': signedTx1.blob, }); retTxns.push({ 'signedTxn': signedTx2.blob, amount: transaction2.amount, txType: 'algo' }); retTxns.push({ 'unsignedTxn': transaction3, 'needsUserSig': true, txType: 'asa', amount: transaction3.amount }); if (transaction4 != null) { retTxns.push({ 'unsignedTxn': transaction4, 'needsUserSig': true }); } return retTxns; } catch (e) { console.debug(e); if (e.text != undefined) { alert(e.text); } else { alert(e); } } }, getQueuedTakerOrders : function getQueuedTakerOrders(takerWalletAddr, isSellingASA_AsTakerOrder, allOrderBookOrders) { console.debug("getQueuedTakerOrders order book list isSellingASA_AsTakerOrder: " + isSellingASA_AsTakerOrder); let queuedOrders = []; // getAllOrderBookEscrowOrders is UI dependant and needs to be customized for the React version if (allOrderBookOrders == null || allOrderBookOrders.length == 0) { return; } // FIXME: don't allow executions against own orders! check wallet address doesn't match // takerWalletAddr for (let i = 0; i < allOrderBookOrders.length; i++) { let orderBookEntry = allOrderBookOrders[i]; if (orderBookEntry['escrowOrderType'] == 'buy' && !isSellingASA_AsTakerOrder) { // only look for sell orders in this case continue; } if (orderBookEntry['escrowOrderType'] == 'sell' && isSellingASA_AsTakerOrder) { // only look for buy orders in this case continue; } orderBookEntry.price = parseFloat(orderBookEntry.price); queuedOrders.push(orderBookEntry); } if (isSellingASA_AsTakerOrder) { // sort highest first (index 0) to lowest (last index) // these are buy orders, so we want to sell to the highest first queuedOrders.sort((a, b) => (a.price < b.price) ? 1 : (a.price === b.price) ? ((a.price < b.price) ? 1 : -1) : -1 ) } else { // sort lowest first (index 0) to highest (last index) // these are sell orders, so we want to buy the lowest first queuedOrders.sort((a, b) => (a.price > b.price) ? 1 : (a.price === b.price) ? ((a.price > b.price) ? 1 : -1) : -1 ) } //console.debug("queued orders: ", this.dumpVar(queuedOrders)); return queuedOrders; }, closeASAOrder : async function closeASAOrder(algodClient, escrowAddr, creatorAddr, index, appArgs, lsig, assetId, metadata) { console.debug("closing asa order!!!"); try { // get node suggested parameters let params = await algodClient.getTransactionParams().do(); // create unsigned transaction let txn = algosdk.makeApplicationClearStateTxn(lsig.address(), params, index, appArgs) let txId = txn.txID().toString(); // Submit the transaction // create optin transaction // sender and receiver are both the same let sender = lsig.address(); let recipient = creatorAddr; // We set revocationTarget to undefined as // This is not a clawback operation let revocationTarget = undefined; // CloseReaminerTo is set to undefined as // we are not closing out an asset let closeRemainderTo = creatorAddr; // We are sending 0 assets let amount = 0; // signing and sending "txn" allows sender to begin accepting asset specified by creator and index let txn2 = algosdk.makeAssetTransferTxnWithSuggestedParams(sender, recipient, closeRemainderTo, revocationTarget, amount, undefined, assetId, params); // Make payment tx signed with lsig let txn3 = algosdk.makePaymentTxnWithSuggestedParams(lsig.address(), creatorAddr, 0, creatorAddr, undefined, params); let txn4 = { type: 'pay', from: creatorAddr, to: creatorAddr, amount: 0, ...params }; let txns = [txn, txn2, txn3, txn4]; let makerAccountInfo = await this.getAccountInfo(creatorAddr) let escrowAccountInfo = await this.getAccountInfo(escrowAddr) let noteMetadata = { algoBalance: makerAccountInfo.amount, asaBalance:(makerAccountInfo.assets && makerAccountInfo.assets.length > 0) ? makerAccountInfo.assets[0].amount : 0, assetId: assetId, n: metadata.n, d: metadata.d, orderEntry: metadata.orderBookEntry, version: metadata.version, escrowAddr: escrowAccountInfo.address, escrowOrderType:"close", txType: "close", isASAescrow: true, } txns = this.formatTransactionsWithMetadata(txns, creatorAddr, noteMetadata, 'close', 'asa'); const groupID = algosdk.computeGroupID(txns); for (let i = 0; i < txns.length; i++) { txns[i].group = groupID; } let signedTx = algosdk.signLogicSigTransactionObject(txn, lsig); txId = signedTx.txID; //console.debug("signedTxn:" + JSON.stringify(signedTx)); console.debug("Signed transaction with txID: %s", txId); let signedTx2 = algosdk.signLogicSigTransactionObject(txn2, lsig); let txId2 = signedTx2.txID; //console.debug("signedTxn:" + JSON.stringify(signedTx)); console.debug("Signed transaction with txID: %s", txId2); let signedTx3 = algosdk.signLogicSigTransactionObject(txn3, lsig); let txId3 = signedTx3.txID; //console.debug("signedTxn:" + JSON.stringify(signedTx)); console.debug("Signed transaction3 with txID: %s", txId3); //this.printTransactionDebug([signedTx.blob]); let signedTx4 = await myAlgoWallet.signTransaction(txn4); console.debug("zzsigned txn: " + signedTx4.txID); let signed = []; signed.push(signedTx.blob); signed.push(signedTx2.blob); signed.push(signedTx3.blob); signed.push(signedTx4.blob); this.printTransactionDebug(signed); //console.debug(Buffer.concat(signed.map(txn => Buffer.from(txn))).toString('base64')); let tx = await algodClient.sendRawTransaction(signed).do(); console.debug(tx.txId); const confirmation = await this.waitForConfirmation(tx.txId); // display results console.debug({confirmation}); return confirmation; } catch (e) { throw e; } }, getAccountInfo : async function getAccountInfo(accountAddr, returnEmptyAccount=true) { const getEmptyAccountInfo = (address) => { return { "address":address, "amount":0,"amount-without-pending-rewards":0,"apps-local-state":[], "apps-total-schema":{"num-byte-slice":0,"num-uint":0},"assets":[], "created-apps":[],"created-assets":[],"pending-rewards":0, "reward-base":0,"rewards":0,"round":-1,"status":"Offline" } } let port = (!!ALGOD_INDEXER_PORT) ? ':' + ALGOD_INDEXER_PORT : ''; try { const response = await axios.get(ALGOD_INDEXER_SERVER + port + "/v2/accounts/"+accountAddr, {headers: {'X-Algo-API-Token': ALGOD_INDEXER_TOKEN}}); if (response.data && response.data.account) { return response.data.account; } else if (returnEmptyAccount) { return getEmptyAccountInfo(accountAddr); } else { return null; } } catch (e) { if (returnEmptyAccount) { return getEmptyAccountInfo(accountAddr); } return null; } }, // close order closeOrder : async function closeOrder(algodClient, escrowAddr, creatorAddr, appIndex, appArgs, lsig, metadata) { let accountInfo = await this.getAccountInfo(lsig.address()); let alreadyOptedIn = false; if (accountInfo != null && accountInfo['assets'] != null && accountInfo['assets'].length > 0 && accountInfo['assets'][0] != null) { await closeASAOrder(algodClient, escrowAddr, creatorAddr, appIndex, appArgs, lsig); return; } try { // get node suggested parameters let params = await algodClient.getTransactionParams().do(); // create unsigned transaction let txn = algosdk.makeApplicationClearStateTxn(lsig.address(), params, appIndex, appArgs) let txId = txn.txID().toString(); // Submit the transaction // Make payment tx signed with lsig let txn2 = algosdk.makePaymentTxnWithSuggestedParams(lsig.address(), creatorAddr, 0, creatorAddr, undefined, params); let txn3 = { type: 'pay', from: creatorAddr, to: creatorAddr, amount: 0, ...params }; myAlgoWalletUtil.setTransactionFee(txn3); let txns = [txn, txn2, txn3]; let makerAccountInfo = await this.getAccountInfo(creatorAddr) let escrowAccountInfo = await this.getAccountInfo(escrowAddr) let noteMetadata = { algoBalance: makerAccountInfo.amount, asaBalance: (makerAccountInfo.assets && makerAccountInfo.assets.length > 0) ? makerAccountInfo.assets[0].amount : 0, n: metadata.n, d: metadata.d, orderEntry: metadata.orderBookEntry, assetId:0, version: metadata.version, escrowAddr: escrowAccountInfo.address, escrowOrderType:"close", txType: "close", isASAescrow: true, } txns = this.formatTransactionsWithMetadata(txns, creatorAddr, noteMetadata, 'close', 'algo'); const groupID = algosdk.computeGroupID(txns) for (let i = 0; i < txns.length; i++) { txns[i].group = groupID; } let signedTx = algosdk.signLogicSigTransactionObject(txn, lsig); txId = signedTx.txID; //console.debug("signedTxn:" + JSON.stringify(signedTx)); console.debug("Signed transaction with txID: %s", txId); let signedTx2 = algosdk.signLogicSigTransactionObject(txn2, lsig); let txId2 = signedTx2.txID; //console.debug("signedTxn:" + JSON.stringify(signedTx)); console.debug("Signed transaction with txID: %s", txId2); let signedTx3 = await myAlgoWallet.signTransaction(txn3); console.debug("zzsigned txn: " + signedTx3.txID); //this.printTransactionDebug([signedTx.blob]); let signed = []; signed.push(signedTx.blob); signed.push(signedTx2.blob); signed.push(signedTx3.blob); this.printTransactionDebug(signed); //console.debug(Buffer.concat(signed.map(txn => Buffer.from(txn))).toString('base64')); let tx = await algodClient.sendRawTransaction(signed).do(); const confirmation = await this.waitForConfirmation(tx.txId); // display results console.debug({confirmation}); return confirmation; } catch (e) { throw e; } }, // Wait for a transaction to be confirmed waitForConfirmation : async (txId) => { function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } const maxLoops = 25; let loopCount = 0; while (loopCount < maxLoops) { // Check the pending transactions let port = (!!ALGOD_INDEXER_PORT) ? ':' + ALGOD_INDEXER_PORT : ''; let response = null; let isError = false; try { response = await axios.get(ALGOD_INDEXER_SERVER + port + "/v2/transactions/"+txId, {headers: {'X-Algo-API-Token': ALGOD_INDEXER_TOKEN}}); } catch (e) { isError = true; } if (response == null || response.data == null || response.data.transaction == null) { isError = true; } if (!isError) { const txnInfo = response.data.transaction; if (txnInfo["confirmed-round"] !== null && txnInfo["confirmed-round"] > 0) { // Got the completed Transaction console.debug(`Transaction ${txId} confirmed in round ${txnInfo["confirmed-round"]}`); return { txId, status: "confirmed", statusMsg: `Transaction confirmed in round ${txnInfo["confirmed-round"]}`, transaction: txnInfo }; } if (txnInfo["pool-error"] !== null && txnInfo["pool-error"].length > 0) { // transaction has been rejected return { txId, status: "rejected", statusMsg: 'Transa