UNPKG

@ultrade/ultrade-js-sdk

Version:

Javascript SDK for interaction with the Ultrade AMM system

1,256 lines 51.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AmmClient = exports.PendingTxnResponse = void 0; const algosdk_1 = __importDefault(require("algosdk")); const master_1 = require("./artifacts/master"); const pool_1 = require("./artifacts/pool"); const stable_1 = require("./artifacts/stable"); const constants_1 = require("./constants"); const utils_1 = require("./utils"); class PendingTxnResponse { constructor(response) { this.poolError = response['pool-error']; this.txn = response['txn']; this.applicationIndex = response['application-index']; this.assetIndex = response['asset-index']; this.closeRewards = response['closing-rewards']; this.closingAmount = response['closing-amount']; this.confirmedRound = response['confirmed-round']; this.globalStateDelta = response['global-state-delta']; this.localStateDelta = response['local-state-delta']; this.receiverRewards = response['receiver-rewards']; this.senderRewards = response['sender-rewards']; this.innerTxns = response['inner-txns'] ? response['inner-txns'] : []; this.logs = response['logs'] ? response['logs'] : []; } } exports.PendingTxnResponse = PendingTxnResponse; class AmmClient { /** * construct AmmClient * * @param appId master app id * @param cluster one of node */ constructor(appId, client) { this.client = client; this.appId = appId; this.masterContract = new algosdk_1.default.ABIContract(master_1.MASTER_ABI); this.poolContract = new algosdk_1.default.ABIContract(pool_1.POOL_ABI); this.stableContract = new algosdk_1.default.ABIContract(stable_1.STABLE_ABI); this.assetsCache = {}; this.poolsCache = {}; } getAssetCache() { return this.assetsCache; } getPoolsCache() { return this.poolsCache; } /** * get status of current node. * * @returns */ getStatus() { return __awaiter(this, void 0, void 0, function* () { const status = yield this.client.status().do(); return status; }); } /** * check if an asset is stable coin. * * @returns */ isStableAsset(asset) { return __awaiter(this, void 0, void 0, function* () { try { const boxName = algosdk_1.default.encodeUint64(asset); const box = yield this.client .getApplicationBoxByName(this.appId, boxName) .do(); const flag = algosdk_1.default.decodeUint64(box.value, 'safe'); return flag != 0; } catch (e) { return false; } }); } /** * Returns the common needed parameters for a new transaction. * * @returns */ getTransactionParams() { return __awaiter(this, void 0, void 0, function* () { const params = yield this.client.getTransactionParams().do(); return params; }); } /** * returns Algodv2 instance. * * @returns */ getAlgodClient() { return this.client; } /** * create a pool for asset A and B. * * @param sender the sender of transaction * @param assetA * @param assetB * @returns */ createPair(sender, assetA, assetB, signer) { return __awaiter(this, void 0, void 0, function* () { if (assetA > assetB) [assetA, assetB] = [assetB, assetA]; const isPoolExist = yield this.checkIsPoolExist(assetA, assetB); if (isPoolExist) { throw new Error('pool already created'); } try { const f1 = yield this.isStableAsset(assetA); const f2 = yield this.isStableAsset(assetB); let tmpPoolId; if (f1 && f2) { tmpPoolId = yield this.getTmpStablePoolId(); } else { tmpPoolId = yield this.getTmpPoolId(); } const atc = new algosdk_1.default.AtomicTransactionComposer(); const sp = yield this.client.getTransactionParams().do(); const boxName = new Uint8Array([ ...algosdk_1.default.encodeUint64(assetA), ...algosdk_1.default.encodeUint64(assetB) ]); const appInfo = yield this.client .getApplicationByID(this.appId) .do(); const appCreator = appInfo.params.creator; atc.addMethodCall({ appID: this.appId, method: (0, utils_1.getMethodByName)(this.masterContract, 'create_pool'), methodArgs: [ { txn: algosdk_1.default.makePaymentTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: algosdk_1.default.getApplicationAddress(this.appId), amount: 3324100 }), signer }, assetA, assetB ], appForeignApps: [tmpPoolId], appForeignAssets: [assetA, assetB], appAccounts: [appCreator], boxes: [ { appIndex: this.appId, name: boxName }, { appIndex: this.appId, name: algosdk_1.default.encodeUint64(assetA) }, { appIndex: this.appId, name: algosdk_1.default.encodeUint64(assetB) } ], sender: sender, suggestedParams: Object.assign(Object.assign({}, sp), { flatFee: true, fee: 12000 }), signer }); const res = yield atc.execute(this.client, 4); return res; } catch (e) { throw new Error('create pair failed: ' + e.message); } }); } /** * Add liquidity to A-B pool * * @param sender the sender of transaction * @param aId id of asset A to send the A-B pool * @param aAmt amount of asset A * @param bId id of asset B * @param bAmt amount of asset B to send the A-B pool * @param mintAmt * @returns */ addLiquidity(sender, aId, aAmt, bId, bAmt, mintAmt, signer) { return __awaiter(this, void 0, void 0, function* () { if (aId > bId) { [aId, bId] = [bId, aId]; [aAmt, bAmt] = [bAmt, aAmt]; } if (aAmt <= 1000 || bAmt <= 1000) { throw new Error('too small amount'); } const poolId = yield this.getPoolIdByAssets(aId, bId); const state = yield this.getPoolState(poolId); if (state['a'] != aId || state['b'] != bId) { throw new Error('incorrect pair'); } const params = yield this.client.getTransactionParams().do(); const poolAddress = algosdk_1.default.getApplicationAddress(poolId); const poolToken = state['p']; const atc = new algosdk_1.default.AtomicTransactionComposer(); if (!(yield this.isOptedInAsset(poolToken, sender))) { atc.addTransaction({ txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: params, to: sender, amount: 0, assetIndex: poolToken }), signer }); } const sp = yield this.client.getTransactionParams().do(); const firstMint = state['ra'] == 0 && state['rb'] == 0; const sf1 = yield this.isStableAsset(aId); const sf2 = yield this.isStableAsset(bId); if (firstMint) { if (aId == 0) { atc.addMethodCall({ sender, appID: poolId, method: (0, utils_1.getMethodByName)(this.poolContract, 'fund'), methodArgs: [ { txn: algosdk_1.default.makePaymentTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: aAmt }), signer }, { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ amount: bAmt, from: sender, suggestedParams: params, to: poolAddress, assetIndex: bId }), signer } ], appForeignAssets: [bId, poolToken], suggestedParams: Object.assign(Object.assign({}, sp), { flatFee: true, fee: 2000 }), signer }); const res = yield atc.execute(this.client, 4); return res; } else { let method; if (sf1 && sf2) { method = (0, utils_1.getMethodByName)(this.stableContract, 'fund'); } else { method = (0, utils_1.getMethodByName)(this.poolContract, 'fund'); } atc.addMethodCall({ sender, appID: poolId, method, methodArgs: [ { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: aAmt, assetIndex: aId }), signer }, { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ amount: bAmt, from: sender, suggestedParams: params, to: poolAddress, assetIndex: bId }), signer } ], appForeignAssets: [aId, bId, poolToken], suggestedParams: Object.assign(Object.assign({}, sp), { flatFee: true, fee: 2000 }), signer }); const res = yield atc.execute(this.client, 4); return res; } } else { if (aId == 0) { atc.addMethodCall({ sender, appID: poolId, method: (0, utils_1.getMethodByName)(this.poolContract, 'mint'), methodArgs: [ { txn: algosdk_1.default.makePaymentTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: aAmt }), signer }, { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ amount: bAmt, from: sender, suggestedParams: params, to: poolAddress, assetIndex: bId }), signer }, mintAmt ], appForeignAssets: [bId, poolToken], suggestedParams: Object.assign(Object.assign({}, sp), { flatFee: true, fee: 4000 }), signer }); const res = yield atc.execute(this.client, 4); return res; } else { let method; let methodArgs; if (sf1 && sf2) { method = (0, utils_1.getMethodByName)(this.stableContract, 'mint'); methodArgs = [aAmt, bAmt, mintAmt]; if (aAmt == 0) { methodArgs.push({ txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: bAmt, assetIndex: bId }), signer }); } else if (bAmt == 0) { methodArgs.push({ txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: aAmt, assetIndex: aId }), signer }); } else { methodArgs.push({ txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: aAmt, assetIndex: aId }), signer }); methodArgs.push({ txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: bAmt, assetIndex: bId }), signer }); } } else { method = (0, utils_1.getMethodByName)(this.poolContract, 'mint'); methodArgs = [ { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: sp, to: poolAddress, amount: aAmt, assetIndex: aId }), signer }, { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ amount: bAmt, from: sender, suggestedParams: params, to: poolAddress, assetIndex: bId }), signer }, mintAmt ]; } atc.addMethodCall({ sender, appID: poolId, method, methodArgs, appForeignAssets: [aId, bId, poolToken], suggestedParams: Object.assign(Object.assign({}, sp), { flatFee: true, fee: 4000 }), signer }); const res = yield atc.execute(this.client, 4); return res; } } }); } /** * remove liquidity from the pool * * @param sender the sender of transaction * @param poolId * @param poolTokenAmt * @param aMinAmt * @param bMinAmt * @returns */ removeLiquidity(sender, poolId, poolTokenAmt, aMinAmt, bMinAmt, signer) { return __awaiter(this, void 0, void 0, function* () { const poolState = yield this.getPoolState(poolId); const poolTokenId = poolState['p']; const poolTokenBalance = yield this.getBalance(poolTokenId, sender); if (poolTokenBalance < poolTokenAmt) throw new Error('insufficient pool token balance'); const sp = yield this.client.getTransactionParams().do(); const foreignAssets = poolState['a'] == 0 ? [poolTokenId, poolState['b']] : [poolTokenId, poolState['a'], poolState['b']]; const sf1 = yield this.isStableAsset(poolState['a']); const sf2 = yield this.isStableAsset(poolState['b']); let method; if (sf1 && sf2) { method = (0, utils_1.getMethodByName)(this.poolContract, 'burn'); } else { method = (0, utils_1.getMethodByName)(this.stableContract, 'burn'); } const atc = new algosdk_1.default.AtomicTransactionComposer(); atc.addMethodCall({ sender, appID: poolId, method, methodArgs: [ { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ amount: poolTokenAmt, from: sender, suggestedParams: sp, to: algosdk_1.default.getApplicationAddress(poolId), assetIndex: poolTokenId }), signer }, aMinAmt, bMinAmt ], appForeignAssets: foreignAssets, suggestedParams: Object.assign(Object.assign({}, sp), { flatFee: true, fee: 4000 }), signer }); const res = yield atc.execute(this.client, 4); return res; }); } /** * swap tokens * * @param sender the sender of transaction * @param inId asset id of input * @param inAmt input amount * @param outId asset id of output * @returns */ swap(sender, inId, inAmt, outId, slippage, signer) { return __awaiter(this, void 0, void 0, function* () { let [assetIn, assetOut] = [inId, outId]; if (assetIn > assetOut) [assetIn, assetOut] = [assetOut, assetIn]; const poolId = yield this.getPoolIdByAssets(assetIn, assetOut); const poolAddress = algosdk_1.default.getApplicationAddress(poolId); let poolState = yield this.getPoolState(poolId); const poolToken = poolState['p']; const outAmt = this.getSwapOutputByState(inId, outId, poolState, inAmt); const minSwapAmt = (BigInt(outAmt) * (BigInt(100000) - BigInt(slippage * 1000))) / BigInt(100000); const params = yield this.client.getTransactionParams().do(); const atc = new algosdk_1.default.AtomicTransactionComposer(); if (!(yield this.isOptedInAsset(outId, sender))) { atc.addTransaction({ txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: params, to: sender, amount: 0, assetIndex: outId }), signer }); } if (inId == 0) { let foreignAssets = [outId, poolToken]; atc.addMethodCall({ sender, appID: poolId, method: (0, utils_1.getMethodByName)(this.poolContract, 'swap'), methodArgs: [ { txn: algosdk_1.default.makePaymentTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: params, to: poolAddress, amount: inAmt }), signer }, minSwapAmt ], appForeignAssets: foreignAssets, suggestedParams: Object.assign(Object.assign({}, params), { flatFee: true, fee: 2000 }), signer }); const res = yield atc.execute(this.client, 4); return res; } else { let foreignAssets; if (inId < outId) { foreignAssets = [inId, outId, poolToken]; } else { foreignAssets = [outId, inId, poolToken]; } atc.addMethodCall({ sender, appID: poolId, method: (0, utils_1.getMethodByName)(this.poolContract, 'swap'), methodArgs: [ { txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: sender, suggestedParams: params, to: poolAddress, amount: inAmt, assetIndex: inId }), signer }, minSwapAmt ], appForeignAssets: foreignAssets, suggestedParams: Object.assign(Object.assign({}, params), { flatFee: true, fee: 2000 }), signer }); const res = yield atc.execute(this.client, 4); return res; } }); } /** * get LP token by asset A and B * * @param assetA * @param assetB * @returns */ getPoolToken(assetA, assetB) { return __awaiter(this, void 0, void 0, function* () { const poolId = yield this.getPoolIdByAssets(assetA, assetB); const state = yield this.getPoolState(poolId); if (state['a'] != assetA || state['b'] != assetB) { throw new Error('incorrect pair'); } return state['p']; }); } /** * get pool app id by asset A and B * * @param assetA * @param assetB * @returns */ getPoolIdByAssets(assetA, assetB) { return __awaiter(this, void 0, void 0, function* () { if (assetA > assetB) [assetA, assetB] = [assetB, assetA]; const cachedId = String(assetA) + String(assetB); if (this.poolsCache[cachedId]) return this.poolsCache[cachedId].poolId; else { const boxName = new Uint8Array([ ...algosdk_1.default.encodeUint64(assetA), ...algosdk_1.default.encodeUint64(assetB) ]); const box = yield this.client .getApplicationBoxByName(this.appId, boxName) .do(); const poolId = algosdk_1.default.decodeUint64(box.value, 'safe'); return poolId; } }); } /** * get pool app id by LP token * @param tokenId * @returns */ getPoolByToken(tokenId) { return __awaiter(this, void 0, void 0, function* () { const pair = Object.values(this.poolsCache).find((pair) => pair.poolState['p'] == tokenId); if (pair) return pair; else { const pairs = yield this.getPairs(); const idx = pairs.findIndex((pair) => pair.poolState['p'] == tokenId); if (idx == -1) throw new Error('non-exist pool'); const correctPair = pairs[idx]; return correctPair; } }); } /** * Get pool by asset A id and asset B id * @param assetA * @param assetB * @returns */ getPoolByAssets(assetA, assetB) { return __awaiter(this, void 0, void 0, function* () { if (assetA > assetB) [assetA, assetB] = [assetB, assetA]; const cachedId = String(assetA) + String(assetB); if (this.poolsCache[cachedId]) { const poolState = yield this.getPoolState(this.poolsCache[cachedId].poolId); return Object.assign(Object.assign({}, this.poolsCache[cachedId]), { poolState }); } else { const boxName = new Uint8Array([ ...algosdk_1.default.encodeUint64(assetA), ...algosdk_1.default.encodeUint64(assetB) ]); const box = yield this.client .getApplicationBoxByName(this.appId, boxName) .do(); const poolId = algosdk_1.default.decodeUint64(box.value, 'safe'); const poolState = yield this.getPoolState(poolId); const poolToken = poolState['p']; let aa; if (assetA == 0) { aa = { params: { name: constants_1.ALGO.name, decimals: constants_1.ALGO.decimals, 'unit-name': constants_1.ALGO.unitName } }; } else { aa = yield this.getAsset(assetA); } const bb = yield this.getAsset(assetB); const pair = { aId: assetA, aName: aa.params['name'], aDecimals: aa.params['decimals'], aUnitName: aa.params['unit-name'], bId: assetB, bName: bb.params['name'], bDecimals: bb.params['decimals'], bUnitName: bb.params['unit-name'], poolId, poolToken, poolState, type: poolState['pt'] || 'CONSTANT_PRODUCT', fee: poolState['f'] || 20 }; this.poolsCache[cachedId] = pair; return pair; } }); } /** * get all pools * * @returns */ getPairs() { return __awaiter(this, void 0, void 0, function* () { const appAddress = algosdk_1.default.getApplicationAddress(this.appId); const appInfo = yield this.client.accountInformation(appAddress).do(); const createdApps = appInfo['created-apps']; let pools = []; for (const app of createdApps) { const poolId = app['id']; const stateArray = app['params']['global-state']; const poolState = (0, utils_1.decodeStateArray)(stateArray); const aId = poolState['a']; const bId = poolState['b']; const poolToken = poolState['p']; const type = poolState['pt'] || 'CONSTANT_PRODUCT'; const fee = poolState['f'] || 20; const cachedId = String(aId) + String(bId); const cachedPool = this.poolsCache[cachedId]; if (cachedPool) { pools.push(Object.assign(Object.assign({}, cachedPool), { poolToken, poolId, poolState })); } else { let assetA; if (aId == 0) { assetA = { params: { decimals: constants_1.ALGO.decimals, name: constants_1.ALGO.name, 'unit-name': constants_1.ALGO.unitName } }; } else { assetA = yield this.getAsset(aId); } const assetB = yield this.getAsset(bId); const pool = { aId, aDecimals: assetA.params['decimals'], aName: assetA.params['name'], aUnitName: assetA.params['unit-name'], bId, bDecimals: assetB.params['decimals'], bName: assetB.params['name'], bUnitName: assetB.params['unit-name'], poolToken, poolId, poolState, fee, type: type }; pools.push(pool); this.poolsCache[cachedId] = pool; } } return pools; }); } /** * get balances of an address * * @param address * @returns */ getBalances(address) { return __awaiter(this, void 0, void 0, function* () { const accountInfo = yield this.client.accountInformation(address).do(); const balances = { '0': accountInfo['amount'] }; const assets = accountInfo['assets']; for (let i = 0; i < assets.length; i++) { const assetId = assets[i]['asset-id']; const assetAmt = assets[i]['amount']; balances[assetId] = assetAmt; } return balances; }); } /** * get balance of an address per asset * * @param assetId * @param address * @returns */ getBalance(assetId, address) { return __awaiter(this, void 0, void 0, function* () { const balances = yield this.getBalances(address); return balances[assetId.toString()]; }); } /** * check if an address opted in an asset * * @param assetId * @param addr * @returns */ isOptedInAsset(assetId, addr) { return __awaiter(this, void 0, void 0, function* () { const balances = yield this.getBalances(addr); return Object.keys(balances).includes(assetId.toString()); }); } /** * get assets of addr * * @param addr * @returns */ getAssetList(addr) { return __awaiter(this, void 0, void 0, function* () { const accountInfo = yield this.client.accountInformation(addr).do(); const as = accountInfo['assets']; const assetList = []; for (let i = 0; i < as.length; i++) { const a = as[i]; try { const b = yield this.client.getAssetByID(a['asset-id']).do(); assetList.push({ id: b['index'], name: b['params']['unit-name'] }); } catch (e) { console.log(e); } } return assetList; }); } /** * get output amount when a user swap A to B * * @param assetIn id of input asset * @param assetOut id of output asset * @param state pool state * @param amtIn input amount * @returns */ getSwapOutputByState(assetIn, assetOut, state, amtIn) { const aSup = state['ra']; const bSup = state['rb']; let [inSup, outSup] = [aSup, bSup]; if (assetIn > assetOut) [inSup, outSup] = [bSup, aSup]; const amtOut = (BigInt(amtIn) * BigInt(1000 - 3) * BigInt(outSup)) / (BigInt(inSup) * BigInt(1000) + BigInt(amtIn) * BigInt(1000 - 3)); return amtOut; } /** * get output amount after swap * * @param txId transaction id * @returns */ getAmountAfterSwap(txId) { return __awaiter(this, void 0, void 0, function* () { const txnInfo = yield this.client .pendingTransactionInformation(txId) .do(); const res = new PendingTxnResponse(txnInfo); if (res.logs.length) { return algosdk_1.default.decodeUint64(res.logs[0], 'safe'); } else { throw new Error('no swap transaction'); } }); } /** * Get liquidity token amounts * * @param assetA id of asset A to send the A-B pool * @param assetAAmt amount of asset A * @param assetB id of asset B * @param assetBAmt amount of asset B to send the A-B pool * @returns int */ getMintAmt(assetA, assetAAmt, assetB, assetBAmt) { return __awaiter(this, void 0, void 0, function* () { if (assetA > assetB) { [assetA, assetB] = [assetB, assetA]; [assetAAmt, assetBAmt] = [assetBAmt, assetAAmt]; } const poolId = yield this.getPoolIdByAssets(assetA, assetB); const state = yield this.getPoolState(poolId); if (state['a'] != assetA || state['b'] != assetB) { throw new Error('incorrect pair'); } const reserveA = BigInt(state['ra']); const reserveB = BigInt(state['rb']); const issued = BigInt(state['ma']); if (reserveA == BigInt(0) && reserveB == BigInt(0)) { // on_fund const scale = BigInt(1000); return (0, utils_1.sqrt)(BigInt(assetAAmt) * BigInt(assetBAmt)) - scale; } else { // on_mint if (BigInt(assetAAmt) * reserveB < BigInt(assetBAmt) * reserveA) { return (BigInt(assetAAmt) * issued) / reserveA; } else { return (BigInt(assetBAmt) * issued) / reserveB; } } }); } /** * Get liquidity token amounts by pool state * * @param state pool state * @param assetAAmt amount of asset A * @param assetBAmt amount of asset B to send the A-B pool * @returns int */ getMintAmtByState(state, assetAAmt, assetBAmt) { const reserveA = BigInt(state['ra']); const reserveB = BigInt(state['rb']); const issued = BigInt(state['ma']); if (reserveA == BigInt(0) && reserveB == BigInt(0)) { // on_fund const scale = BigInt(1000); return (0, utils_1.sqrt)(BigInt(assetAAmt) * BigInt(assetBAmt)) - scale; } else { // on_mint if (BigInt(assetAAmt) * reserveB < BigInt(assetBAmt) * reserveA) { return (BigInt(assetAAmt) * issued) / reserveA; } else { return (BigInt(assetBAmt) * issued) / reserveB; } } } /** * Make opt-in for asset/pool * * @param sender the sender of transaction * @param assetId id of asset/pool * @param signer transaction signer * @returns */ prepareOptInTxn(sender, assetId, signer) { return __awaiter(this, void 0, void 0, function* () { const params = yield this.client.getTransactionParams().do(); const atc = new algosdk_1.default.AtomicTransactionComposer(); atc.addTransaction({ txn: algosdk_1.default.makeAssetTransferTxnWithSuggestedParamsFromObject({ amount: 0, from: sender, suggestedParams: params, to: sender, assetIndex: assetId }), signer }); const res = yield atc.execute(this.client, 4); return res; }); } /** * Get assets amounts after pool token burn * * @param assetA id of asset A * @param assetB id of asset B * @param burnAmt burn amount * @returns */ getAssetAmtAfterBurnLP(assetA, assetB, burnAmt) { return __awaiter(this, void 0, void 0, function* () { const poolId = yield this.getPoolIdByAssets(assetA, assetB); const state = yield this.getPoolState(poolId); if (state['a'] != assetA || state['b'] != assetB) { throw new Error('incorrect pair'); } const issued = BigInt(state['ma']); const reserveA = BigInt(state['ra']); const reserveB = BigInt(state['rb']); const aBurnedAmt = (BigInt(reserveA) * BigInt(burnAmt)) / issued; const bBurnedAmt = (BigInt(reserveB) * BigInt(burnAmt)) / issued; return { assetA: aBurnedAmt, assetB: bBurnedAmt }; }); } /** * Get assets amounts after pool token burn by state * * @param state pool state * @param burnAmt burn amount * @returns */ getAssetAmtAfterBurnLPByState(state, burnAmt) { const issued = BigInt(state['ma']); const reserveA = BigInt(state['ra']); const reserveB = BigInt(state['rb']); const aBurnedAmt = (BigInt(reserveA) * BigInt(burnAmt)) / issued; const bBurnedAmt = (BigInt(reserveB) * BigInt(burnAmt)) / issued; return { assetA: aBurnedAmt, assetB: bBurnedAmt }; } /** * get input amount from output when a user swap A to B * * @param assetIn id of input asset * @param assetOut id of output asset * @param amtOut output amount * @returns */ getSwapInput(assetIn, assetOut, amtOut) { return __awaiter(this, void 0, void 0, function* () { let [assetA, assetB] = [assetIn, assetOut]; if (assetIn > assetOut) [assetA, assetB] = [assetOut, assetIn]; const poolId = yield this.getPoolIdByAssets(assetA, assetB); const state = yield this.getPoolState(poolId); if (state['a'] != assetA || state['b'] != assetB) { throw new Error('incorrect pair'); } const aSup = state['ra']; const bSup = state['rb']; let [inSup, outSup] = [aSup, bSup]; if (assetIn > assetOut) [inSup, outSup] = [bSup, aSup]; const amtIn = (BigInt(amtOut) * BigInt(inSup) * BigInt(1000)) / (BigInt(1000 - 3) * (BigInt(outSup) - BigInt(amtOut))) + BigInt(1); return amtIn; }); } /** * get input amount from output when a user swap A to B * * @param assetIn id of input asset * @param assetOut id of output asset * @param amtOut output amount * @param state pool state * @returns */ getSwapInputByState(assetIn, assetOut, state, amtOut) { const aSup = state['ra']; const bSup = state['rb']; let [inSup, outSup] = [aSup, bSup]; if (assetIn > assetOut) [inSup, outSup] = [bSup, aSup]; const amtIn = (BigInt(amtOut) * BigInt(inSup) * BigInt(1000)) / (BigInt(1000 - 3) * (BigInt(outSup) - BigInt(amtOut))) + BigInt(1); return amtIn; } /** * get pool ratio * @param assetIn id of asset A * @param assetOut id of asset B * @returns */ getPoolRatio(assetIn, assetOut) { return __awaiter(this, void 0, void 0, function* () { let [assetA, assetB] = [assetIn, assetOut]; if (assetIn.index > assetOut.index) [assetA, assetB] = [assetOut, assetIn]; const poolId = yield this.getPoolIdByAssets(assetA.index, assetB.index); const state = yield this.getPoolState(poolId); if (state['a'] != assetA.index || state['b'] != assetB.index) { throw new Error('incorrect pair'); } if (state['ra'] == 0 || state['rb'] == 0) return 0; const aSup = { amount: BigInt(state['ra']), decimals: assetA.decimals }; const bSup = { amount: BigInt(state['rb']), decimals: assetB.decimals }; let [inSup, outSup] = [aSup, bSup]; if (assetIn.index > assetOut.index) [inSup, outSup] = [bSup, aSup]; const ratio = Number(inSup.amount) / Math.pow(10, inSup.decimals) / (Number(outSup.amount) / Math.pow(10, outSup.decimals)); return ratio; }); } /** * get pool ratio * @param state pool state * @param aDecimals decimals of asset A * @param bDecimals decimals of asset B * @returns */ getPoolRatioByState(state, aDecimals, bDecimals) { if (state['ra'] == 0 || state['rb'] == 0) return 0; const aSup = { amount: BigInt(state['ra']), decimals: aDecimals }; const bSup = { amount: BigInt(state['rb']), decimals: bDecimals }; const ratio = Number(aSup.amount) / Math.pow(10, aSup.decimals) / (Number(bSup.amount) / Math.pow(10, bSup.decimals)); return ratio; } /** * Get pool state * @param poolId id of pool * @returns */ getPoolState(poolId) { return __awaiter(this, void 0, void 0, function* () { const appInfo = yield this.client.getApplicationByID(poolId).do(); const stateArray = appInfo['params']['global-state']; const state = (0, utils_1.decodeStateArray)(stateArray); return state; }); } /** * Get all pool states * @returns */ getPoolStates() { return __awaiter(this, void 0, void 0, function* () { const appAddress = algosdk_1.default.getApplicationAddress(this.appId); const appInfo = yield this.client.accountInformation(appAddress).do(); const createdApps = appInfo['created-apps']; const states = {}; createdApps.forEach((app) => { const id = app['id']; const stateArray = app['params']['global-state']; const state = (0, utils_1.decodeStateArray)(stateArray); states[id] = state; }); return states; }); } /** * get price impact when a user swaps A to B * @param assetA id of asset A * @param assetB id of asset B * @param inAmt input amout * @returns */ getPriceImpact(assetA, assetB, inAmt) { return __awaiter(this, void 0, void 0, function* () { const poolId = yield this.getPoolIdByAssets(assetA, assetB); const state = yield this.getPoolState(poolId); let [inId, outId] = [assetA, assetB]; if (assetA > assetB) [inId, outId] = [assetB, assetA]; if (state['a'] != inId || state['b'] != outId) { throw new Error('incorrect pair'); } const aSup = state['ra']; const bSup = state['rb']; let [inSup, outSup] = [aSup, bSup]; if (assetA > assetB) [inSup, outSup] = [outSup, inSup]; const pb = inSup / outSup; const pa = (inSup + inAmt) / ((inSup * outSup) / (inSup + inAmt)); return (pa - pb) / pb; }); } /** * get price impact when a user swaps A to B by state * @param poolState pool state * @param assetA id of asset A * @param assetB id of asset B * @param inAmt input amout * @returns */ getPriceImpactByState(poolState, inId, outId, inAmt) { const aSup = poolState['ra']; const bSup = poolState['rb']; let [inSup, outSup] = [aSup, bSup]; if (inId > outId) [inSup, outSup] = [bSup, aSup]; const pb = inSup / outSup; const pa = (inSup + inAmt) / ((inSup * outSup) / (inSup + inAmt)); return (pa - pb) / pb; } /** * get price impact and swap amount when a user swaps A to B * @param assetA id of asset A * @param assetB id of asset B * @param inAmt input amout * @returns */ getSwapResults(inId, outId, amount) { return __awaiter(this, void 0, void 0, function* () { let [assetA, assetB] = [inId, outId]; if (inId > outId) [assetA, assetB] = [outId, inId]; const poolId = yield this.getPoolIdByAssets(assetA, assetB); const state = yield this.getPoolState(poolId); if (state['a'] != assetA || state['b'] != assetB) { throw new Error('incorrect pair'); } const aSup = state['ra']; const bSup = state['rb']; let [inSup, outSup] = [aSup, bSup]; if (inId > outId) [inSup, outSup] = [bSup, aSup]; const swapOutput = (BigInt(amount) * BigInt(1000 - 3) * BigInt(outSup)) / (BigInt(inSup * 1000) + BigInt(amount) * BigInt(1000 - 3)); const swapInput = (BigInt(amount) * BigInt(inSup) * BigInt(1000)) / (BigInt(1000 - 3) * (BigInt(outSup) - BigInt(amount))); return { swapOutput, swapInput }; }); } getSwapResultsByState(inId, outId, state, amount) { let [assetA, assetB] = [inId, outId]; if (inId > outId) [assetA, assetB] = [outId, inId]; if (state['a'] != assetA || state['b'] != assetB) { throw new Error('incorrect pair'); } const aSup = state['ra']; const bSup = state['rb']; let [inSup, outSup] = [aSup, bSup]; if (inId > outId) [inSup, outSup] = [bSup, aSup]; const swapOutput = (BigInt(amount) * BigInt(1000 - 3) * BigInt(outSup)) / (BigInt(inSup * 1000) + BigInt(amount) * BigInt(1000 - 3)); const swapInput = (BigInt(amount) * BigInt(inSup) * BigInt(1000)) / (BigInt(1000 - 3) * (BigInt(outSup) - BigInt(amount))); return { swapOutput, swapInput }; } getTmpPoolId() { return __awaiter(this, void 0, void 0, function* () { const state = yield this.getAppState(); return state['tp']; }); } getTmpStablePoolId() { return __awaiter(this, void 0, void 0, function* () { const state = yield this.getAppSt