@algodex/algodex-sdk-patch
Version:
API calls for interacting with the Algorand blockchain
1,208 lines (1,012 loc) • 52.3 kB
JavaScript
/////////////////////////////
// Alexander Trefonas //
// 7/9/2021 //
// Copyright Algodev Inc //
// All Rights Reserved. //
/////////////////////////////
const http = require('http');
const algosdk = require('algosdk');
const {formatJsonRpcRequest} = require("@json-rpc-tools/utils")
const BigN = require('js-big-decimal');
const Big = require('big.js')
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 = (txns) => {
return txns.map(txn => {
return { txn: txn, blob: "fakeBlob" }
})
}
}
}
require('./algo_delegate_template_teal.js');
require('./ASA_delegate_template_teal.js');
//require('./dex_teal.js');
const dexInternal = require('./algodex_internal_api.js');
if (MyAlgo != null) {
myAlgoWallet = new MyAlgo();
// console.debug("printing my algo wallet");
// console.debug(myAlgoWallet)
}
const constants = require('./constants.js');
const { ESCROW_CONTRACT_VERSION } = require('./constants.js');
const bigDecimal = require('js-big-decimal');
let ALGO_ESCROW_ORDER_BOOK_ID = -1;
let ASA_ESCROW_ORDER_BOOK_ID = -1;
function scientificToString(n) {
if (!(n instanceof Big)) {
throw new TypeError('Must be a Big.js value!');
}
let num = Object.create(n);
if (num.toString().includes('e')) {
if (num.e > 0) {
// TODO: large numbers
} else {
// Patch Scientific notation to String
num = num.toFixed(Math.abs(n.e));
}
}
return (num instanceof Big) ? num.toString() : num;
}
const AlgodexApi = {
scientificToString,
doAlert : function doAlert() {
alert(1);
console.debug("api call!!!");
},
getConstants : () => {
return constants;
},
allSettled : function(promises) {
let wrappedPromises = promises.map(p => Promise.resolve(p)
.then(
val => ({ status: 'promiseFulfilled', value: val }),
err => ({ status: 'promiseRejected', reason: err })));
return Promise.all(wrappedPromises);
},
initSmartContracts : function(environment) {
if (environment == "local") {
ALGO_ESCROW_ORDER_BOOK_ID = constants.LOCAL_ALGO_ORDERBOOK_APPID;
ASA_ESCROW_ORDER_BOOK_ID = constants.LOCAL_ASA_ORDERBOOK_APPID;
} else if (environment == "test") {
ALGO_ESCROW_ORDER_BOOK_ID = constants.TEST_ALGO_ORDERBOOK_APPID;
ASA_ESCROW_ORDER_BOOK_ID = constants.TEST_ASA_ORDERBOOK_APPID;
} else if (environment == "public_test") {
ALGO_ESCROW_ORDER_BOOK_ID = constants.PUBLIC_TEST_ALGO_ORDERBOOK_APPID;
ASA_ESCROW_ORDER_BOOK_ID = constants.PUBLIC_TEST_ASA_ORDERBOOK_APPID;
} else if (environment == "production") {
ALGO_ESCROW_ORDER_BOOK_ID = constants.PROD_ALGO_ORDERBOOK_APPID;
ASA_ESCROW_ORDER_BOOK_ID = constants.PROD_ASA_ORDERBOOK_APPID;
} else {
throw "environment must be local, test, or production";
}
if ("ALGODEX_ALGO_ESCROW_APP" in process.env) {
ALGO_ESCROW_ORDER_BOOK_ID = parseInt(process.env.ALGODEX_ALGO_ESCROW_APP)
}
if ("ALGODEX_ASA_ESCROW_APP" in process.env) {
ASA_ESCROW_ORDER_BOOK_ID = parseInt(process.env.ALGODEX_ASA_ESCROW_APP)
}
//console.debug("ALGO APP ID:", ALGO_ESCROW_ORDER_BOOK_ID)
//console.debug("ASA APP ID:", ASA_ESCROW_ORDER_BOOK_ID)
dexInternal.initSmartContracts(ALGO_ESCROW_ORDER_BOOK_ID, ASA_ESCROW_ORDER_BOOK_ID);
//console.debug({ALGO_ESCROW_ORDER_BOOK_ID, ASA_ESCROW_ORDER_BOOK_ID});
},
getOrderBookId : function(isAlgoEscrowApp) {
if (isAlgoEscrowApp) {
return ALGO_ESCROW_ORDER_BOOK_ID;
}
return ASA_ESCROW_ORDER_BOOK_ID
},
getMinWalletBalance : async function(accountInfo, includesFullAccountInfo = false) {
if (!includesFullAccountInfo) {
try {
accountInfo = await this.getAccountInfo(accountInfo.address); // get full account info
} catch (e) {
return 1000000;
}
}
if (!accountInfo || !accountInfo.address) {
return 1000000;
}
console.debug("in getMinWalletBalance. Checking: " + accountInfo.address);
console.debug({accountInfo});
let minBalance = 0;
if (accountInfo['created-apps']) {
minBalance += 100000 * (accountInfo['created-apps'].length); // Apps
}
if (accountInfo['assets']) {
minBalance += accountInfo['assets'].length * 100000;
}
if (accountInfo['apps-total-schema'] != undefined && accountInfo['apps-total-schema']['num-uint']) {
minBalance += (25000+3500) * accountInfo['apps-total-schema']['num-uint']; // Total Ints
}
if (accountInfo['apps-total-schema'] != undefined && accountInfo['apps-total-schema']['num-byte-slice']) {
minBalance += (25000+25000) * accountInfo['apps-total-schema']['num-byte-slice']; // Total Bytes
}
minBalance += 1000000;
return minBalance;
},
//Options are: local, test, production
initIndexer : function(environment) {
let server = null;
let port = null;
let token = null;
this.initSmartContracts(environment);
if (environment == "local") {
server = constants.LOCAL_INDEXER_SERVER;
port = constants.LOCAL_INDEXER_PORT;
token = constants.LOCAL_INDEXER_TOKEN;
} else if (environment == "test") {
server = constants.TEST_INDEXER_SERVER;
port = constants.TEST_INDEXER_PORT;
token = constants.TEST_INDEXER_TOKEN;
} else if (environment == "public_test") {
server = constants.PUBLIC_TEST_INDEXER_SERVER;
port = constants.PUBLIC_TEST_INDEXER_PORT;
token = constants.PUBLIC_TEST_INDEXER_TOKEN;
} else if (environment == "production") {
server = constants.PROD_INDEXER_SERVER;
port = constants.PROD_INDEXER_PORT;
token = constants.PROD_INDEXER_TOKEN;
} else {
throw "environment must be local, test, or production";
}
const indexerClient = new algosdk.Indexer(token, server, port);
console.debug({server, port, token});
dexInternal.setAlgodIndexer(server, port, token);
return indexerClient;
},
//local, test, production
initAlgodClient : function(environment) {
let algodServer = null;
let port = null;
let token = null;
this.initSmartContracts(environment);
if (environment == "local") {
algodServer = constants.LOCAL_ALGOD_SERVER;
port = constants.LOCAL_ALGOD_PORT;
token = constants.LOCAL_ALGOD_TOKEN;
} else if (environment == "test") {
algodServer = constants.TEST_ALGOD_SERVER;
port = constants.TEST_ALGOD_PORT;
token = constants.TEST_ALGOD_TOKEN;
} else if (environment == "public_test") {
algodServer = constants.PUBLIC_TEST_ALGOD_SERVER;
port = constants.PUBLIC_TEST_ALGOD_PORT;
token = constants.PUBLIC_TEST_ALGOD_TOKEN;
} else if (environment == "production") {
algodServer = constants.PROD_ALGOD_SERVER;
port = constants.PROD_ALGOD_PORT;
token = constants.PROD_ALGOD_TOKEN;
} else {
throw "environment must be local, test, or production";
}
//console.debug({server: algodServer, token, port});
const algodClient = new algosdk.Algodv2(token, algodServer, port);
if (!!myAlgoWalletUtil) {
myAlgoWalletUtil.setAlgodServer(algodServer);
}
dexInternal.setAlgodServer(algodServer);
dexInternal.setAlgodPort(port);
dexInternal.setAlgodToken(token);
return algodClient;
},
// Wait for a transaction to be confirmed
waitForConfirmation : async function(txId) {
return dexInternal.waitForConfirmation(txId);
},
dumpVar : function dumpVar(x) {
return dexInternal.dumpVar(x);
},
getNumeratorAndDenominatorFromPrice : function getNumeratorAndDenominatorFromPrice(limitPrice) {
let countDecimals = function (limitPrice) {
if(Math.floor(limitPrice) === limitPrice) return 0;
return scientificToString(new Big(limitPrice)).split(".")[1].length || 0;
}
const origDecCount = countDecimals(limitPrice);
let d = 10**origDecCount * limitPrice;
let n = 10**origDecCount;
d = Math.round(d);
n = Math.round(n);
return {
n: n,
d: d
}
},
createOrderBookEntryObj : function createOrderBookEntryObj (blockChainOrderVal, price, n, d, min, escrowAddr,
algoBalance, asaBalance, escrowOrderType, isASAEscrow, orderCreatorAddr, assetId, version=3) {
const orderEntry =
{
orderEntry: blockChainOrderVal, // this should match what's in the blockchain
price: price, // d/n
n: n,
d: d,
min: min,
escrowAddr: escrowAddr,
algoBalance: algoBalance,
asaBalance: asaBalance,
escrowOrderType: escrowOrderType,
isASAEscrow: isASAEscrow,
orderCreatorAddr: orderCreatorAddr,
assetId: assetId,
version: version
};
return orderEntry;
},
getCutOrderTimes : (queuedOrder) => {
console.debug('in getCutOrderTimes: ', JSON.stringify(queuedOrder) );
let cutOrderAmount = null, splitTimes = null;
if (queuedOrder.isASAEscrow) {
cutOrderAmount = Math.max(1, queuedOrder.asaBalance / 4);
splitTimes = Math.floor(queuedOrder.asaBalance / cutOrderAmount);
} else {
const minOrderAmount = Math.max(queuedOrder.price + 1, 500000);
cutOrderAmount = Math.max(minOrderAmount, queuedOrder.algoBalance / 4);
splitTimes = Math.floor(queuedOrder.algoBalance / cutOrderAmount);
}
cutOrderAmount = Math.floor(cutOrderAmount);
if (splitTimes == 0) {
splitTimes = 1;
}
return {
'cutOrderAmount': cutOrderAmount,
'splitTimes': splitTimes
};
},
executeOrder : async function executeOrder (algodClient, isSellingASA, assetId,
userWalletAddr, limitPrice, orderAssetAmount, orderAlgoAmount, allOrderBookOrders, includeMaker, walletConnector) {
console.debug("in executeOrder");
let queuedOrders = dexInternal.getQueuedTakerOrders(userWalletAddr, isSellingASA, allOrderBookOrders);
let allTransList = [];
let transNeededUserSigList = [];
let execAccountInfo = await this.getAccountInfo(userWalletAddr);
let alreadyOptedIn = false;
console.debug("herezz56");
console.debug({execAccountInfo});
let takerMinBalance = await this.getMinWalletBalance(execAccountInfo, true);
console.debug({min_bal: takerMinBalance});
let walletAssetAmount = 0;
const walletAlgoAmount = execAccountInfo['amount'] - takerMinBalance - (0.004 * 1000000);
if (walletAlgoAmount <= 0) {
console.debug("not enough to trade!! returning early");
return;
}
if (execAccountInfo != null && execAccountInfo['assets'] != null
&& execAccountInfo['assets'].length > 0) {
for (let i = 0; i < execAccountInfo['assets'].length; i++) {
let asset = execAccountInfo['assets'][i];
if (asset['asset-id'] == assetId) {
walletAssetAmount = asset['amount']
break;
//console.debug("execAccountInfo: " + execAccountInfo);
}
}
}
const getTakerOptedIn = (accountInfo, assetId) => {
let takerAlreadyOptedIntoASA = false;
if (accountInfo != null && accountInfo['assets'] != null
&& accountInfo['assets'].length > 0) {
for (let i = 0; i < accountInfo['assets'].length; i++) {
if (accountInfo['assets'][i]['asset-id'] === assetId) {
takerAlreadyOptedIntoASA = true;
break;
}
}
}
return takerAlreadyOptedIntoASA;
}
const takerIsOptedIn = getTakerOptedIn(execAccountInfo, assetId);
orderAssetAmount = Math.max(1, orderAssetAmount);
orderAlgoAmount = Math.max(1, orderAlgoAmount);
if (isSellingASA) {
// we are selling an ASA so check wallet balance
orderAlgoBalance = walletAlgoAmount;
orderAssetBalance = Math.min(orderAssetAmount, walletAssetAmount);
} else {
// wallet ASA balance doesn't matter since we are selling algos
orderAlgoBalance = Math.min(orderAlgoAmount, walletAlgoAmount);
orderAssetBalance = walletAssetAmount;
}
const takerOrderBalance = {
'asaBalance': orderAssetBalance,
'algoBalance': orderAlgoBalance,
'walletAlgoBalance': walletAlgoAmount,
'walletASABalance': walletAssetAmount,
'limitPrice': limitPrice,
'takerAddr': userWalletAddr,
'walletMinBalance': takerMinBalance,
'takerIsOptedIn': takerIsOptedIn
};
console.debug("initial taker orderbalance: ", this.dumpVar(takerOrderBalance));
//let walletBalance = 10; // wallet balance
//let walletASABalance = 15;
if (queuedOrders == null && !includeMaker) {
console.debug("null queued orders, returning early");
return;
}
if (queuedOrders == null) {
queuedOrders = [];
}
let txOrderNum = 0;
let groupNum = 0;
let txnFee = 0.004 * 1000000 //FIXME minimum fee;
//console.debug("queued orders: ", this.dumpVar(queuedOrders));
let params = await algodClient.getTransactionParams().do();
let lastExecutedPrice = -1;
const getCutOrderTimes = this.getCutOrderTimes;
for (let i = 0; i < queuedOrders.length; i++) {
if (takerOrderBalance['orderAlgoAmount'] <= txnFee) {
// Overspending issues
continue;
}
if (isSellingASA && parseFloat(takerOrderBalance['asaBalance']) <= 0) {
console.debug('breaking due to 0 asaBalance balance!');
break;
}
if (!isSellingASA && parseFloat(takerOrderBalance['algoBalance']) <= 0) {
console.debug('breaking due to 0 algoBalance balance!');
break;
}
if (isSellingASA && parseFloat(takerOrderBalance['limitPrice']) > queuedOrders[i]['price']) {
//buyer & seller prices don't match
continue;
}
if (!isSellingASA && parseFloat(takerOrderBalance['limitPrice']) < queuedOrders[i]['price']) {
//buyer & seller prices don't match
continue;
}
// let cutOrder = null;
// let splitTimes = 1;
const getSplitTimesByIter = (i) => {
let cutOrder = null;
let splitTimes = 1;
if (i == 0) {
cutOrder = getCutOrderTimes(queuedOrders[i]);
splitTimes = cutOrder.splitTimes;
} else {
cutOrder = null;
}
return {cutOrder, splitTimes};
}
const {cutOrder, splitTimes} = getSplitTimesByIter(i);
console.debug('cutOrder, splitTimes: ', {cutOrder, splitTimes});
let runningBalance = queuedOrders[i].isASAEscrow ? queuedOrders[i].asaBalance :
queuedOrders[i].algoBalance;
let outerBreak = false;
for (let jj = 0; jj < splitTimes; jj++) {
if (runningBalance <= 0) {
throw "Unexpected 0 or below balance";
}
console.debug("running balance: " + runningBalance + " isASAEscrow: " + queuedOrders[i].isASAEscrow);
const queuedOrder = Object.assign({}, queuedOrders[i]);
if (cutOrder != null) {
const shouldClose = (jj < cutOrder.splitTimes - 1) ? false : null;
const useForceShouldCloseOrNot = (jj < cutOrder.splitTimes - 1);
queuedOrder.forceShouldClose = shouldClose;
queuedOrder.useForceShouldCloseOrNot = useForceShouldCloseOrNot;
queuedOrder.txnNum = jj;
if (jj >= splitTimes - 1) {
// This is the last iteration, so simply use the running balance
if (queuedOrder.isASAEscrow) {
queuedOrder.asaBalance = runningBalance;
} else {
queuedOrder.algoBalance = runningBalance;
}
} else {
if (queuedOrder.isASAEscrow) {
queuedOrder.asaBalance = Math.min(cutOrder.cutOrderAmount, runningBalance);
} else {
queuedOrder.algoBalance = Math.min(cutOrder.cutOrderAmount, runningBalance);
}
}
}
let singleOrderTransList =
await dexInternal.getExecuteOrderTransactionsAsTakerFromOrderEntry(algodClient,
queuedOrder, takerOrderBalance, params, walletConnector);
if (singleOrderTransList == null) {
// Overspending issue
outerBreak = true;
break;
}
const [algo, asa] = this.getAlgoandAsaAmounts(singleOrderTransList);
this.finalPriceCheck(algo ,asa , limitPrice, isSellingASA)
lastExecutedPrice = queuedOrder['price'];
for (let k = 0; k < singleOrderTransList.length; k++) {
let trans = singleOrderTransList[k];
trans['txOrderNum'] = txOrderNum;
trans['groupNum'] = groupNum;
txOrderNum++;
allTransList.push(trans);
if (trans['needsUserSig'] === true) {
transNeededUserSigList.push(trans);
}
}
groupNum++;
runningBalance -= cutOrder != null ? cutOrder.cutOrderAmount : 0;
}
if (outerBreak) {
break;
}
}
let makerTxns = null;
console.debug('here55999a ', {lastExecutedPrice, limitPrice} );
if (includeMaker) {
const numAndDenom = lastExecutedPrice != -1 ? this.getNumeratorAndDenominatorFromPrice(lastExecutedPrice) :
this.getNumeratorAndDenominatorFromPrice(limitPrice);
let leftoverASABalance = Math.floor(takerOrderBalance['asaBalance']);
let leftoverAlgoBalance = Math.floor(takerOrderBalance['algoBalance']);
console.debug("includeMaker is true");
if (isSellingASA && leftoverASABalance > 0) {
console.debug("leftover ASA balance is: " + leftoverASABalance);
makerTxns = await this.getPlaceASAToSellASAOrderIntoOrderbook(algodClient,
userWalletAddr, numAndDenom.n, numAndDenom.d, 0, assetId, leftoverASABalance, false, walletConnector );
} else if (!isSellingASA && leftoverAlgoBalance > 0) {
console.debug("leftover Algo balance is: " + leftoverASABalance);
makerTxns = await this.getPlaceAlgosToBuyASAOrderIntoOrderbook(algodClient,
userWalletAddr, numAndDenom.n, numAndDenom.d, 0, assetId, leftoverAlgoBalance, false, walletConnector);
}
}
if (makerTxns != null) {
for (let k = 0; k < makerTxns.length; k++) {
let trans = makerTxns[k];
trans['txOrderNum'] = txOrderNum;
trans['groupNum'] = groupNum;
txOrderNum++;
allTransList.push(trans);
if (trans['needsUserSig'] === true) {
transNeededUserSigList.push(trans);
}
if (typeof(trans.lsig) !== 'undefined') {
if(!walletConnector || !walletConnector.connector.connected) {
let signedTxn = algosdk.signLogicSigTransactionObject(trans.unsignedTxn, trans.lsig);
trans.signedTxn = signedTxn.blob;
}
}
}
groupNum++;
}
if (allTransList == null || allTransList.length == 0) {
console.debug("no transactions, returning early");
}
let txnsForSigning = [];
for (let i = 0; i < transNeededUserSigList.length; i++) {
txnsForSigning.push(transNeededUserSigList[i]['unsignedTxn']);
}
console.debug("here 8899b signing!!");
if (txnsForSigning == null || txnsForSigning.length == 0) {
return;
}
if(!!walletConnector && walletConnector.connector.connected) {
const confirmedWalletConnectArr = await this.signAndSendWalletConnectTransactions(algodClient, allTransList, params, walletConnector);
return confirmedWalletConnectArr;
}
let signedTxns = await myAlgoWallet.signTransaction(txnsForSigning);
if (!Array.isArray(signedTxns)) {
signedTxns = [signedTxns];
}
for (let i = 0; i < transNeededUserSigList.length; i++) {
transNeededUserSigList[i]['signedTxn'] = signedTxns[i].blob;
}
signedTxns = [];
let sentTxns = [];
let lastGroupNum = -1;
for (let i = 0; i < allTransList.length; i++) { // loop to end of array
if (lastGroupNum != allTransList[i]['groupNum']) {
// If at beginning of new group, send last batch of transactions
if (signedTxns.length > 0) {
try {
this.printTransactionDebug(signedTxns);
let txn = await algodClient.sendRawTransaction(signedTxns).do();
sentTxns.push(txn.txId);
console.debug("sent: " + txn.txId);
} catch (e) {
console.debug(e);
}
}
// send batch of grouped transactions
signedTxns = [];
lastGroupNum = allTransList[i]['groupNum'];
}
signedTxns.push(allTransList[i]['signedTxn']);
if (i == allTransList.length - 1) {
// If at end of list send last batch of transactions
if (signedTxns.length > 0) {
try {
this.printTransactionDebug(signedTxns);
const DO_SEND = true;
if (DO_SEND) {
let txn = await algodClient.sendRawTransaction(signedTxns).do();
sentTxns.push(txn.txId);
console.debug("sent: " + txn.txId);
} else {
console.debug("skipping sending for debugging reasons!!!");
}
} catch (e) {
console.debug(e);
}
}
break;
}
}
console.debug("going to wait for confirmations");
let waitConfirmedPromises = [];
for (let i = 0; i < sentTxns.length; i++) {
console.debug("creating promise to wait for: " + sentTxns[i]);
const confirmPromise = this.waitForConfirmation(sentTxns[i]);
waitConfirmedPromises.push(confirmPromise);
}
console.debug("final9 trans are: " );
// console.debug(alTransList);
// console.debug(transNeededUserSigList);
console.debug("going to send all ");
let confirmedTransactions = await this.allSettled(waitConfirmedPromises);
let transResults = JSON.stringify(confirmedTransactions, null, 2);
console.debug("trans results after confirmed are: " );
console.debug(transResults);
// await this.waitForConfirmation(algodClient, txn.txId);
return;
},
closeOrderFromOrderBookEntry : async function closeOrderFromOrderBookEntry(algodClient, escrowAccountAddr, creatorAddr, orderBookEntry, version) {
let valSplit = orderBookEntry.split("-");
console.debug("closing order from order book entry!");
console.debug("escrowAccountAddr, creatorAddr, orderBookEntry, version",
escrowAccountAddr, creatorAddr, orderBookEntry, version);
let n = valSplit[0];
let d = valSplit[1];
let min = valSplit[2];
let assetid = valSplit[3];
let infoForMetadata= {n:n, d:d, min:min, orderBookEntry: orderBookEntry, version:version}
let appArgs = [];
let enc = new TextEncoder();
appArgs.push(enc.encode("close"));
appArgs.push(enc.encode(orderBookEntry));
// appArgs.push(enc.encode(creatorAddr));
console.debug("args length: " + appArgs.length);
let accountInfo = await this.getAccountInfo(escrowAccountAddr);
let assetId = null;
if (accountInfo != null && accountInfo['assets'] != null
&& accountInfo['assets'].length > 0 && accountInfo['assets'][0] != null) {
// check if escrow has an assetId in the blockchain
assetId = accountInfo['assets'][0]['asset-id'];
}
const isAsaOrder = (assetId != null);
let escrowSource = this.buildDelegateTemplateFromArgs(min,assetid,n,d,creatorAddr, isAsaOrder, version);
let lsig = await dexInternal.getLsigFromProgramSource(algosdk, algodClient, escrowSource, constants.DEBUG_SMART_CONTRACT_SOURCE);
console.debug("lsig is: " + lsig.address());
if (lsig.address() != escrowAccountAddr) {
throw 'Lsig address does not equal input address! ' + lsig.address() + ' vs ' + escrowAccountAddr;
}
if (assetId == null) {
console.debug("closing order");
await dexInternal.closeOrder(algodClient, escrowAccountAddr, creatorAddr, ALGO_ESCROW_ORDER_BOOK_ID, appArgs, lsig, infoForMetadata);
} else {
console.debug("closing ASA order");
await dexInternal.closeASAOrder(algodClient, escrowAccountAddr, creatorAddr, ASA_ESCROW_ORDER_BOOK_ID, appArgs, lsig, assetId, infoForMetadata );
}
},
assignGroups: function assignGroups (txns) {
const groupID = algosdk.computeGroupID(txns)
for (let i = 0; i < txns.length; i++) {
txns[i].group = groupID;
}
},
finalPriceCheck: function finalPriceCheck(algoAmount,asaAmount, limitPrice, isSellingASA) {
function LimitPriceException(message) {
this.message = message;
this.name = 'LimitPriceException';
}
LimitPriceException.prototype = Error.prototype;
const buyLimit = new BigN(limitPrice).multiply(new BigN(1.002));
const sellLimit = new BigN(limitPrice).multiply(new BigN(0.998));
if (!isSellingASA
&& new BigN(algoAmount).divide(new BigN(asaAmount)).compareTo(buyLimit) === GREATER_THAN) {
// Throw an exception if price is 0.2% higher than limit price set by user
throw new LimitPriceException("Attempting to buy at a price higher than limit price");
}
if (isSellingASA
&& new BigN(algoAmount).divide(new BigN(asaAmount)).compareTo(sellLimit) === LESS_THAN) {
// Throw an exception if price is 0.2% lower than limit price set by user
throw new LimitPriceException("Attempting to sell at a price lower than limit price");
}
console.debug({algoAmount, asaAmount, limitPrice});
return
},
getAlgoandAsaAmounts:
function (txnList) {
const algo = txnList
.filter(
(txObj) =>{
return Object.keys(txObj).includes("txType") &&
txObj.txType === "algo"}
)
.map((txObj) => txObj.amount)[0];
const asa = txnList
.filter(
(txObj) =>{
return Object.keys(txObj).includes("txType") &&
txObj.txType === "asa"}
)
.map((txObj) => txObj.amount)[0];
return [algo, asa];
},
signAndSendWalletConnectTransactions:
async function (algodClient, outerTxns, params, walletConnector) {
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
const groups = groupBy(outerTxns, "groupNum");
let numberOfGroups = Object.keys(groups);
const groupedGroups = numberOfGroups.map(group => {
const allTxFormatted = (groups[group].map(txn => {
if (!txn.unsignedTxn.name) {
if (txn.unsignedTxn.type === "pay") {return algosdk.makePaymentTxnWithSuggestedParams(txn.unsignedTxn.from, txn.unsignedTxn.to, txn.unsignedTxn.amount, undefined, undefined, params)}
if (txn.unsignedTxn.type === "axfer") {return algosdk.makeAssetTransferTxnWithSuggestedParams(txn.unsignedTxn.from, txn.unsignedTxn.to, undefined, undefined, txn.unsignedTxn.amount, undefined, txn.unsignedTxn.assetIndex, params)}
} else {
return txn.unsignedTxn;
}
}))
algosdk.assignGroupID(allTxFormatted.map(toSign => toSign));
return allTxFormatted;
}
)
const txnsToSign = groupedGroups.map(group => {
const encodedGroup = group.map(txn => {
const encodedTxn = Buffer.from(algosdk.encodeUnsignedTransaction(txn)).toString("base64");
if (algosdk.encodeAddress(txn.from.publicKey) !== walletConnector.connector.accounts[0]) return { txn: encodedTxn, signers: [] };
return { txn: encodedTxn };
})
return encodedGroup;
})
const formattedTxn = txnsToSign.flat();
const request = formatJsonRpcRequest("algo_signTxn", [formattedTxn]);
const result = await walletConnector.connector.sendCustomRequest(request);
let resultsFormattted = result.map((element, idx) => {
return element ? {
txID: formattedTxn[idx].txn,
blob: new Uint8Array(Buffer.from(element, "base64"))
} : {
...algosdk.signLogicSigTransactionObject(outerTxns[idx].unsignedTxn, outerTxns[idx].lsig)
};
});
let orderedRawTransactions = resultsFormattted.map(obj => obj.blob);
for (let i = 0; i < outerTxns.length; i++) {
outerTxns[i]['signedTxn'] = orderedRawTransactions[i];
}
let lastGroupNum = -1
orderedRawTransactions = []
let walletConnectSentTxn = []
for (let i = 0; i < outerTxns.length; i++) { // loop to end of array
if (lastGroupNum != outerTxns[i]['groupNum']) {
// If at beginning of new group, send last batch of transactions
if (orderedRawTransactions.length > 0) {
try {
this.printTransactionDebug(orderedRawTransactions);
let txn = await algodClient.sendRawTransaction(orderedRawTransactions).do();
walletConnectSentTxn.push(txn.txId);
console.debug("sent: " + txn.txId);
} catch (e) {
console.debug(e);
}
}
// send batch of grouped transactions
orderedRawTransactions = [];
lastGroupNum = outerTxns[i]['groupNum'];
}
orderedRawTransactions.push(outerTxns[i]['signedTxn']);
if (i == outerTxns.length - 1) {
// If at end of list send last batch of transactions
if (orderedRawTransactions.length > 0) {
try {
this.printTransactionDebug(orderedRawTransactions);
const DO_SEND = true;
if (DO_SEND) {
let txn = await algodClient.sendRawTransaction(orderedRawTransactions).do();
walletConnectSentTxn.push(txn.txId);
console.debug("sent: " + txn.txId);
} else {
console.debug("skipping sending for debugging reasons!!!");
}
} catch (e) {
console.debug(e);
}
}
break;
}
}
return walletConnectSentTxn;
},
signAndSendTransactions :
async function signAndSendTransactions(algodClient, outerTxns) {
console.debug("inside signAndSend transactions");
let txnsForSig = [];
let txns = [];
for (let i = 0; i < outerTxns.length; i++) {
txns.push(outerTxns[i].unsignedTxn);
if (outerTxns[i].needsUserSig == true) {
txnsForSig.push(outerTxns[i].unsignedTxn);
}
}
this.assignGroups(txns);
let signedTxnsFromUser = await myAlgoWallet.signTransaction(txnsForSig);
if (Array.isArray(signedTxnsFromUser)) {
let userSigIndex = 0;
for (let i = 0; i < outerTxns.length; i++) {
if (outerTxns[i].needsUserSig) {
outerTxns[i].signedTxn = signedTxnsFromUser[userSigIndex].blob;
userSigIndex++;
}
}
} else {
for (let i = 0; i < outerTxns.length; i++) {
if (outerTxns[i].needsUserSig) {
outerTxns[i].signedTxn = signedTxnsFromUser.blob;
break;
}
}
}
for (let i = 0; i < outerTxns.length; i++) {
if (!outerTxns[i].needsUserSig) {
let signedLsig = await algosdk.signLogicSigTransactionObject(outerTxns[i].unsignedTxn, outerTxns[i].lsig);
outerTxns[i].signedTxn = signedLsig.blob;
}
}
let signed = [];
for (let i = 0; i < outerTxns.length; i++) {
signed.push(outerTxns[i].signedTxn);
}
console.debug("printing transaction debug");
this.printTransactionDebug(signed);
const groupTxn = await algodClient.sendRawTransaction(signed).do()
return this.waitForConfirmation(groupTxn.txId)
},
generateOrder : function (makerWalletAddr, n, d, min, assetId, includeMakerAddr) {
return dexInternal.generateOrder(makerWalletAddr, n, d, min, assetId, includeMakerAddr);
},
getPlaceAlgosToBuyASAOrderIntoOrderbook : async function
getPlaceAlgosToBuyASAOrderIntoOrderbook(algodClient, makerWalletAddr, n, d, min, assetId, algoOrderSize, signAndSend, walletConnector) {
console.debug("placeAlgosToBuyASAOrderIntoOrderbook makerWalletAddr, n, d, min, assetId",
makerWalletAddr, n, d, min, assetId);
let program = this.buildDelegateTemplateFromArgs(min, assetId, n, d, makerWalletAddr, false, constants.ESCROW_CONTRACT_VERSION);
let lsig = await this.getLsigFromProgramSource(algosdk, algodClient, program, constants.DEBUG_SMART_CONTRACT_SOURCE);
let generatedOrderEntry = dexInternal.generateOrder(makerWalletAddr, n, d, min, assetId);
console.debug("address is: " + lsig.address());
console.debug("here111 generatedOrderEntry " + generatedOrderEntry);
// check if the lsig has already opted in
let alreadyOptedIntoOrderbook = false;
let makerAccountInfo = await this.getAccountInfo(makerWalletAddr);
let makerAlreadyOptedIntoASA = false;
if (makerAccountInfo != null && makerAccountInfo['assets'] != null
&& makerAccountInfo['assets'].length > 0) {
for (let i = 0; i < makerAccountInfo['assets'].length; i++) {
if (makerAccountInfo['assets'][i]['asset-id'] === assetId) {
makerAlreadyOptedIntoASA = true;
break;
}
}
}
// let escrowAccountInfo = await this.getAccountInfo(lsig.address());
// if (escrowAccountInfo != null && escrowAccountInfo['apps-local-state'] != null
// && escrowAccountInfo['apps-local-state'].length > 0
// && escrowAccountInfo['apps-local-state'][0].id == ALGO_ESCROW_ORDER_BOOK_ID) {
// alreadyOptedIntoOrderbook = true;
// }
console.debug({makerAlreadyOptedIntoASA});
console.debug({alreadyOptedIntoOrderbook});
if (alreadyOptedIntoOrderbook == false && algoOrderSize < constants.MIN_ASA_ESCROW_BALANCE) {
algoOrderSize = constants.MIN_ASA_ESCROW_BALANCE;
}
console.debug("alreadyOptedIn: " + alreadyOptedIntoOrderbook);
// console.debug("acct info:" + JSON.stringify(escrowAccountInfo));
let params = await algodClient.getTransactionParams().do();
console.debug("sending trans to: " + lsig.address());
let txn = {
...params,
type: 'pay',
from: makerWalletAddr,
to: lsig.address(),
amount: parseInt(algoOrderSize), // the order size that gets stored into the contract account
};
let outerTxns = [];
outerTxns.push({
unsignedTxn: txn,
needsUserSig: true
});
myAlgoWalletUtil.setTransactionFee(txn);
console.debug("here3 calling app from logic sig to open order");
let appArgs = [];
var enc = new TextEncoder();
appArgs.push(enc.encode("open"));
//console.debug("before slice: " + generatedOrderEntry);
console.debug(generatedOrderEntry.slice(59));
//console.debug("after slice: " + generatedOrderEntry.slice(59));
appArgs.push(enc.encode(generatedOrderEntry.slice(59)));
//let arr = Uint8Array.from([0x2]);
let arr = Uint8Array.from([constants.ESCROW_CONTRACT_VERSION]);
appArgs.push(arr);
console.debug("app args 2: " + arr);
//console.debug("owners bit addr: " + ownersBitAddr);
//console.debug("herezzz_888");
console.debug(appArgs.length);
let logSigTrans = null;
if (!alreadyOptedIntoOrderbook) {
logSigTrans = await dexInternal.createTransactionFromLogicSig(algodClient, lsig,
ALGO_ESCROW_ORDER_BOOK_ID, appArgs, "appOptIn", params);
outerTxns.push({
unsignedTxn: logSigTrans,
needsUserSig: false,
lsig: lsig
});
}
// asset opt-in transfer
let assetOptInTxn = null;
if (!makerAlreadyOptedIntoASA) {
assetOptInTxn = {
type: "axfer",
from: makerWalletAddr,
to: makerWalletAddr,
amount: 0,
assetIndex: assetId,
...params
};
outerTxns.push({
unsignedTxn: assetOptInTxn,
needsUserSig: true
});
}
unsignedTxns = [];
for (let i = 0; i < outerTxns.length; i++) {
unsignedTxns.push(outerTxns[i].unsignedTxn);
}
let noteMetadata = {
algoBalance: makerAccountInfo.amount,
asaBalance:(makerAccountInfo.assets && makerAccountInfo.assets.length > 0) ? makerAccountInfo.assets[0].amount : 0,
assetId: assetId,
n:n,
d:d,
escrowAddr: lsig.address(),
orderEntry: generatedOrderEntry,
escrowOrderType:"buy",
version: constants.ESCROW_CONTRACT_VERSION
};
// look into accuracy of above object
unsignedTxns = dexInternal.formatTransactionsWithMetadata(unsignedTxns, makerWalletAddr, noteMetadata, "open", "algo");
if (signAndSend) {
return await this.signAndSendTransactions(algodClient, outerTxns);
}
if(!walletConnector || !walletConnector.connector.connected){this.assignGroups(unsignedTxns)};
return outerTxns;
},
executeMarketOrder :
async function executeMarketOrder(algodClient, isSellingASA, assetId,
userWalletAddr, limitPrice, orderAssetAmount, orderAlgoAmount, allOrderBookOrders, includeMaker, walletConnector) {
console.log("in Execute Market Order")
return this.executeOrder(algodClient, isSellingASA, assetId,
userWalletAddr, limitPrice, orderAssetAmount, orderAlgoAmount, allOrderBookOrders, includeMaker, walletConnector)
},
getPlaceASAToSellASAOrderIntoOrderbook :
async function getPlaceASAToSellASAOrderIntoOrderbook(algodClient, makerWalletAddr, n, d, min, assetId, assetAmount, signAndSend, walletConnector) {
console.debug("checking assetId type");
assetId = parseInt(assetId+"");
let outerTxns = [];
let program = this.buildDelegateTemplateFromArgs(min, assetId, n, d, makerWalletAddr, true, constants.ESCROW_CONTRACT_VERSION);
let lsig = await this.getLsigFromProgramSource(algosdk, algodClient, program, constants.DEBUG_SMART_CONTRACT_SOURCE);
let generatedOrderEntry = dexInternal.generateOrder(makerWalletAddr, n, d, min, assetId);
console.debug("address is: " + lsig.address());
let makerAccountInfo = await this.getAccountInfo(makerWalletAddr);
// check if the lsig has already opted in
let accountInfo = await this.getAccountInfo(lsig.address());
let alreadyOptedIn = false;
if (accountInfo != null && accountInfo['apps-local-state'] != null
&& accountInfo['apps-local-state'].length > 0
&& accountInfo['apps-local-state'][0].id == ASA_ESCROW_ORDER_BOOK_ID) {
alreadyOptedIn = true;
}
console.debug("alreadyOptedIn: " + alreadyOptedIn);
console.debug("acct info:" + JSON.stringify(accountInfo));
let params = await algodClient.getTransactionParams().do();
console.debug("sending trans to: " + lsig.address());
let assetSendTrans = {
...params,
fee: 1000,
flatFee: true,
type: 'axfer',
assetIndex: assetId,
from: makerWalletAddr,
to: lsig.address(),
amount: assetAmount
};
let noteMetadata = {
algoBalance: makerAccountInfo.amount,
asaBalance:(makerAccountInfo.assets && makerAccountInfo.assets.length > 0) ? makerAccountInfo.assets[0].amount : 0,
assetId: assetId,
n:n,
d:d,
escrowAddr: accountInfo.address,
orderEntry: generatedOrderEntry,
escrowOrderType:"sell",
version: constants.ESCROW_CONTRACT_VERSION
}
console.debug("herez88888 ", this.dumpVar(assetSendTrans));
if (alreadyOptedIn) {
outerTxns.push({
unsignedTxn: assetSendTrans,
needsUserSig: true
});
// Below Conditional is neccessarry for when an order is already open and the maker is just adding more asset value into it
if (signAndSend) {
unsignedTxns = [];
for (let i = 0; i < outerTxns.length; i++) {
unsignedTxns.push(outerTxns[i].unsignedTxn);
}
unsignedTxns = dexInternal.formatTransactionsWithMetadata(unsignedTxns, makerWalletAddr, noteMetadata, "open", "asa")
return await this.signAndSendTransactions(algodClient, outerTxns);
} else {
return outerTxns;
}
}
let payTxn = {
...params,
type: 'pay',
from: makerWalletAddr,
to: lsig.address(),
amount: constants.MIN_ASA_ESCROW_BALANCE, //fund with enough to subtract from later
};
myAlgoWalletUtil.setTransactionFee(payTxn);
console.debug("typeof: " + typeof payTxn.txId);
console.debug("the val: " + payTxn.txId);
let payTxId = payTxn.txId;
//console.debug("confirmed!!");
// create unsigned transaction
console.debug("here3 calling app from logic sig to open order");
let appArgs = [];
var enc = new TextEncoder();
appArgs.push(enc.encode("open"));
console.debug("before slice: " + generatedOrderEntry);
console.debug(generatedOrderEntry.slice(59));
console.debug("after slice: " + generatedOrderEntry.slice(59));
appArgs.push(enc.encode(generatedOrderEntry.slice(59)));
appArgs.push(new Uint8Array([constants.ESCROW_CONTRACT_VERSION]));
// add owners address as arg
//ownersAddr = "WYWRYK42XADLY3O62N52BOLT27DMPRA3WNBT2OBRT65N6OEZQWD4OSH6PI";
//ownersBitAddr = (algosdk.decodeAddress(ownersAddr)).publicKey;
console.debug(appArgs.length);
let logSigTrans = await dexInternal.createTransactionFromLogicSig(algodClient, lsig,
ASA_ESCROW_ORDER_BOOK_ID, appArgs, "appOptIn", params);
// create optin transaction
// sender and receiver are both the same
let sender = lsig.address();
let recipient = sender;
// 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 = undefined;
// We are sending 0 assets
let amount = 0;
// signing and sending "txn" allows sender to begin accepting asset specified by creator and index
let logSigAssetOptInTrans = algosdk.makeAssetTransferTxnWithSuggestedParams(sender, recipient, closeRemainderTo,
revocationTarget,
amount, undefined, assetId, params);
outerTxns.push({
unsignedTxn: payTxn,
needsUserSig: true