UNPKG

nerve-sdk-js

Version:
1,284 lines (1,247 loc) 65.6 kB
const sdk = require('../api/sdk'); const util = require('../test/api/util'); const cryptos = require("crypto"); const BigNumber = require('bignumber.js'); const nerve = require('../index'); async function inputsOrOutputsOfSwapAddLiquidity(fromAddress, to, tokenAmountA, tokenAmountB, pairAddress) { let balanceA = await util.getNulsBalance(fromAddress, tokenAmountA.chainId, tokenAmountA.assetId); let balanceB = await util.getNulsBalance(fromAddress, tokenAmountB.chainId, tokenAmountB.assetId); let inputs = [ { address: fromAddress, amount: tokenAmountA.amount, assetsChainId: tokenAmountA.chainId, assetsId: tokenAmountA.assetId, nonce: balanceA.data.nonce, locked: 0, }, { address: fromAddress, amount: tokenAmountB.amount, assetsChainId: tokenAmountB.chainId, assetsId: tokenAmountB.assetId, nonce: balanceB.data.nonce, locked: 0, } ]; let outputs = [ { address: pairAddress, amount: tokenAmountA.amount, assetsChainId: tokenAmountA.chainId, assetsId: tokenAmountA.assetId, locked: 0 }, { address: pairAddress, amount: tokenAmountB.amount, assetsChainId: tokenAmountB.chainId, assetsId: tokenAmountB.assetId, locked: 0 } ]; return {success: true, data: {inputs: inputs, outputs: outputs}}; } async function inputsOrOutputsOfStableAddLiquidityOrTrade(fromAddress, to, tokenAmounts, feeTo, feeTokenAmount) { let inputs = []; let outputs = []; let hasFee = false; if (feeTo && feeTokenAmount) { hasFee = true; } let length = tokenAmounts.length; for (let i = 0; i < length; i++) { let tokenAmount = tokenAmounts[i]; let balance = await util.getNulsBalance(fromAddress, tokenAmount.chainId, tokenAmount.assetId); inputs.push({ address: fromAddress, amount: tokenAmount.amount, assetsChainId: tokenAmount.chainId, assetsId: tokenAmount.assetId, nonce: balance.data.nonce, locked: 0, }); let fromAmount = new BigNumber(tokenAmount.amount); if (hasFee && tokenAmount.chainId == feeTokenAmount.chainId && tokenAmount.assetId == feeTokenAmount.assetId) { fromAmount = fromAmount.minus(new BigNumber(feeTokenAmount.amount)); outputs.push({ address: feeTo, amount: feeTokenAmount.amount, assetsChainId: tokenAmount.chainId, assetsId: tokenAmount.assetId, locked: 0 }); } outputs.push({ address: to, amount: fromAmount.toFixed(), assetsChainId: tokenAmount.chainId, assetsId: tokenAmount.assetId, locked: 0 }); } return {success: true, data: {inputs: inputs, outputs: outputs}}; } function int32ToBytes(x) { return Buffer.concat([ Buffer.from([0xFF & x]), Buffer.from([0xFF & x >> 8]), Buffer.from([0xFF & x >> 16]), Buffer.from([0xFF & x >> 24]) ]); } function containsCurrency(currentPathArray, token) { let length = currentPathArray.length; for (let i = 0; i < length; i++) { let pair = currentPathArray[i]; if (swap.tokenEquals(pair.token0, token) || swap.tokenEquals(pair.token1, token)) { return true; } } return false; } function getAmountOutForBestTrade(amountIn, reserveIn, reserveOut, feeRate) { if (typeof feeRate === 'undefined') { feeRate = 3; } let _amountIn = new BigNumber(amountIn); if(_amountIn.isLessThanOrEqualTo(0)) { return new BigNumber('0'); } let _reserveIn = new BigNumber(reserveIn); let _reserveOut = new BigNumber(reserveOut); if(_reserveIn.isLessThanOrEqualTo(0) || _reserveOut.isLessThanOrEqualTo(0)) { return new BigNumber('0'); } // let amountInWithFee = _amountIn.times(997); let amountInWithFee = _amountIn.times(1000 - feeRate); let numerator = amountInWithFee.times(_reserveOut); let denominator = _reserveIn.times(1000).plus(amountInWithFee); let amountOut = numerator.dividedToIntegerBy(denominator); return amountOut; } function getAmountInForBestTrade(amountOut, reserveIn, reserveOut, feeRate) { if (typeof feeRate === 'undefined') { feeRate = 3; } let _amountOut = new BigNumber(amountOut); if(_amountOut.isLessThanOrEqualTo(0)) { return new BigNumber('0'); } let _reserveIn = new BigNumber(reserveIn); let _reserveOut = new BigNumber(reserveOut); if(_reserveOut.isLessThanOrEqualTo(_amountOut) || _reserveIn.isLessThanOrEqualTo(0) || _reserveOut.isLessThanOrEqualTo(0)) { return new BigNumber('0'); } let numerator = _reserveIn.times(_amountOut).times(1000); // let denominator = _reserveOut.minus(_amountOut).times(997); let denominator = _reserveOut.minus(_amountOut).times(1000 - feeRate); let amountIn = numerator.dividedToIntegerBy(denominator).plus(1); return amountIn; } function calcBestTradeExactIn(chainId, pairs, tokenAmountIn, tokenOut, currentPathArray, wholeTradeArray, orginTokenAmountIn, maxPairSize, stableGroupArray) { let tokenIn = tokenAmountIn.token; let length = pairs.length; let tokens = swap.tokenSort(tokenIn, tokenOut); let subIndex = -1; for (let i = 0; i < length; i++) { let pair = pairs[i]; if (swap.tokenEquals(pair.token0, tokens[0]) && swap.tokenEquals(pair.token1, tokens[1])) { subIndex = i; break; } } if (subIndex != -1) { let pair = pairs[subIndex]; let reserves = swap.getReserves(tokenIn, tokenOut, pair); let reserveIn = new BigNumber(reserves[0]); let reserveOut = new BigNumber(reserves[1]); let amountOut = getAmountOutForBestTrade(tokenAmountIn.amount, reserveIn, reserveOut, reserves[2]); let tokenAmountOut = swap.tokenAmount(tokenOut.chainId, tokenOut.assetId, amountOut); wholeTradeArray.push({ path: [pair], tokenAmountIn: orginTokenAmountIn, tokenAmountOut: tokenAmountOut }); pairs = pairs.slice(0, subIndex).concat(pairs.slice(subIndex + 1, length)); } // pair去重,检查交易对中是否有多个相同组下的普通token和稳定币token 筛选机制,留下一个,留下稳定币token在Pair池中数量最多的 if (stableGroupArray) { pairs = deduplicationGroupPair(pairs, stableGroupArray); } let trades = realBestTradeExactIn(chainId, pairs, tokenAmountIn, tokenOut, currentPathArray, wholeTradeArray, orginTokenAmountIn, 0, maxPairSize, stableGroupArray); if (trades.length == 0) return {}; trades.sort(function (a, b) {return b.tokenAmountOut.amount.minus(a.tokenAmountOut.amount)}); let trade = trades[0]; if (trade.tokenAmountOut.amount.isEqualTo(0)) return {}; return trade; } function deduplicationGroupPair(pairs, stableGroupArray) { let resultPairs = []; let groupPairMap = {}; for (let i = 0; i < pairs.length; i++) { let pair = pairs[i]; let stableInfo = swap.checkStableToken(pair.token0, stableGroupArray); if (stableInfo.success) { let array = groupPairMap[swap.tokenStr(stableInfo.lpToken) + "-" + swap.tokenStr(pair.token1)]; if (!array) { array = []; } pair.stableToken = pair.token0; pair.stableTokenReserve = pair.reserve0; pair.stableTokenDecimals = stableInfo['groupCoin'][swap.tokenStr(pair.token0)].decimals; array.push(pair); } else { stableInfo = swap.checkStableToken(pair.token1, stableGroupArray); if (stableInfo.success) { let array = groupPairMap[swap.tokenStr(stableInfo.lpToken) + "-" + swap.tokenStr(pair.token0)]; if (!array) { array = []; groupPairMap[swap.tokenStr(stableInfo.lpToken) + "-" + swap.tokenStr(pair.token0)] = array; } pair.stableToken = pair.token1; pair.stableTokenReserve = pair.reserve1; pair.stableTokenDecimals = stableInfo['groupCoin'][swap.tokenStr(pair.token1)].decimals; array.push(pair); } else { resultPairs.push(pair); } } } let keys = Object.keys(groupPairMap); if (keys.length > 0) { for (let i = 0; i < keys.length; i++) { let key = keys[i]; // console.log('============key', key); let array = groupPairMap[key]; if (array.length == 0) continue; if (array.length == 1) { resultPairs.push(array[0]); } else { // 留下一个,留下稳定币token在Pair池中数量最多的 let index = 0; let maxReserve = new BigNumber('0'); for (let j = 0; j < array.length; j++) { let pair = array[j]; let currentReserve = new BigNumber(pair.stableTokenReserve).times(new BigNumber('10').pow(18 - pair.stableTokenDecimals)); // console.log('token', swap.tokenStr(pair.stableToken), 'currentReserve', currentReserve.toFixed()); if (currentReserve.isGreaterThan(maxReserve)) { maxReserve = currentReserve; index = j; } } resultPairs.push(array[index]); } } } return resultPairs; } function makeStableLinkPair(tokenA, tokenB, amountA, amountB) { let array = swap.tokenSort(tokenA, tokenB); let token0 = array[0]; let result = swap.tokenEquals(tokenA, token0) ? swap.pair(tokenA, tokenB, amountA, amountB, 0) : swap.pair(tokenB, tokenA, amountB, amountA, 0); return result; } function realBestTradeExactIn(chainId, pairs, tokenAmountIn, out, currentPathArray, wholeTradeArray, orginTokenAmountIn, depth, maxPairSize, stableGroupArray) { // console.log('-------------', 'pairs', JSON.stringify(pairs), 'depth', depth); let length = pairs.length; for (let i = 0; i < length; i++) { let pair = pairs[i]; // console.log('pair', JSON.stringify(pair), 'depth', depth); let linkPair; let tokenIn = tokenAmountIn.token; if (!swap.tokenEquals(pair.token0, tokenIn) && !swap.tokenEquals(pair.token1, tokenIn)) { // add for link + if (stableGroupArray) { let stableResult = isGroupStable(pair.token0, tokenAmountIn.token, stableGroupArray); if (stableResult.success) { linkPair = makeStableLinkPair(pair.token0, tokenAmountIn.token, '0', '0'); linkPair.groupCoin = stableResult.groupCoin; tokenIn = pair.token0; } else { stableResult = isGroupStable(pair.token1, tokenAmountIn.token, stableGroupArray); if (stableResult.success) { linkPair = makeStableLinkPair(pair.token1, tokenAmountIn.token, '0', '0'); linkPair.groupCoin = stableResult.groupCoin; tokenIn = pair.token1; } else { continue; } } // add for link - } else { continue; } } let tokenOut = swap.tokenEquals(pair.token0, tokenIn) ? pair.token1 : pair.token0; if (containsCurrency(currentPathArray, tokenOut)) continue; // 计算tokenOutAmount let amountIn = tokenAmountIn.amount; // add for link + if (linkPair) { // 检查稳定币池是否有足够的金额 let linkIn = linkPair['groupCoin'][swap.tokenStr(tokenAmountIn.token)]; let linkOut = linkPair['groupCoin'][swap.tokenStr(tokenIn)]; let linkAmountOut = new BigNumber(amountIn).times(new BigNumber('10').pow(linkOut.decimals)).div(new BigNumber('10').pow(linkIn.decimals)); if (new BigNumber(linkOut.balance).isLessThan(linkAmountOut)) { amountIn = '0'; } else { amountIn = linkAmountOut.toFixed(); } } // add for link - let reserves = swap.getReserves(tokenIn, tokenOut, pair); let reserveIn = new BigNumber(reserves[0]); let reserveOut = new BigNumber(reserves[1]); if (reserveIn.isEqualTo(0) || reserveOut.isEqualTo(0)) continue; let amountOut = getAmountOutForBestTrade(amountIn, reserveIn, reserveOut, reserves[2]); if (swap.tokenEquals(tokenOut, out)) { let cloneCurrentPathArray = currentPathArray.slice(); // add for link + if (linkPair) { cloneCurrentPathArray.push(linkPair); } // add for link - cloneCurrentPathArray.push(pair); let tokenAmountOut = swap.tokenAmount(tokenOut.chainId, tokenOut.assetId, amountOut); wholeTradeArray.push({ path: cloneCurrentPathArray, tokenAmountIn: orginTokenAmountIn, tokenAmountOut: tokenAmountOut }); } else if (depth < (maxPairSize - 1) && length > 1) { let cloneCurrentPathArray = currentPathArray.slice(); // add for link + if (linkPair) { cloneCurrentPathArray.push(linkPair); } // add for link - cloneCurrentPathArray.push(pair); let subPairs = pairs.slice(0, i); subPairs = subPairs.concat(pairs.slice(i + 1, length)); realBestTradeExactIn( chainId, subPairs, swap.tokenAmount(tokenOut.chainId, tokenOut.assetId, amountOut), out, cloneCurrentPathArray, wholeTradeArray, orginTokenAmountIn, depth + 1, maxPairSize, stableGroupArray ); } } return wholeTradeArray; } function calcBestTradeExactOut(chainId, pairs, _in, tokenAmountOut, currentPathArray, wholeTradeArray, orginTokenAmountOut, maxPairSize, stableGroupArray) { let tokenOut = tokenAmountOut.token; let length = pairs.length; let tokens = swap.tokenSort(_in, tokenOut); let subIndex = -1; for (let i = 0; i < length; i++) { let pair = pairs[i]; if (swap.tokenEquals(pair.token0, tokens[0]) && swap.tokenEquals(pair.token1, tokens[1])) { subIndex = i; break; } } if (subIndex != -1) { let pair = pairs[subIndex]; let reserves = swap.getReserves(_in, tokenOut, pair); let reserveIn = new BigNumber(reserves[0]); let reserveOut = new BigNumber(reserves[1]); let amountIn = getAmountInForBestTrade(tokenAmountOut.amount, reserveIn, reserveOut, reserves[2]); let tokenAmountIn = swap.tokenAmount(_in.chainId, _in.assetId, amountIn); wholeTradeArray.push({ path: [pair], tokenAmountIn: tokenAmountIn, tokenAmountOut: orginTokenAmountOut }); pairs = pairs.slice(0, subIndex).concat(pairs.slice(subIndex + 1, length)); } // pair去重,检查交易对中是否有多个相同组下的普通token和稳定币token 筛选机制,留下一个,留下稳定币token在Pair池中数量最多的 if (stableGroupArray) { pairs = deduplicationGroupPair(pairs, stableGroupArray); } let trades = realBestTradeExactOut(chainId, pairs, _in, tokenAmountOut, currentPathArray, wholeTradeArray, orginTokenAmountOut, 0, maxPairSize, stableGroupArray); if (trades.length == 0) return {}; trades.sort(function (a, b) {return a.tokenAmountIn.amount.minus(b.tokenAmountIn.amount)}); for (let i=0;i<trades.length;i++) { let trade = trades[i]; if (trade.tokenAmountIn.amount.isEqualTo(0)) continue; return trade; } return {}; } function realBestTradeExactOut(chainId, pairs, _in, tokenAmountOut, currentPathArray, wholeTradeArray, orginTokenAmountOut, depth, maxPairSize, stableGroupArray) { let length = pairs.length; for (let i = 0; i < length; i++) { let pair = pairs[i]; let linkPair; let currentOut = tokenAmountOut.token; if (!swap.tokenEquals(pair.token0, currentOut) && !swap.tokenEquals(pair.token1, currentOut)) { // add for link + if (stableGroupArray) { let stableResult = isGroupStable(pair.token0, tokenAmountOut.token, stableGroupArray); if (stableResult.success) { linkPair = makeStableLinkPair(pair.token0, tokenAmountOut.token, '0', '0'); linkPair.groupCoin = stableResult.groupCoin; currentOut = pair.token0; } else { stableResult = isGroupStable(pair.token1, tokenAmountOut.token, stableGroupArray); if (stableResult.success) { linkPair = makeStableLinkPair(pair.token1, tokenAmountOut.token, '0', '0'); linkPair.groupCoin = stableResult.groupCoin; currentOut = pair.token1; } else { continue; } } // add for link - } else { continue; } } let currentIn = swap.tokenEquals(pair.token0, currentOut) ? pair.token1 : pair.token0; if (containsCurrency(currentPathArray, currentIn)) continue; // 计算tokenInAmount let amountOut = tokenAmountOut.amount; // add for link + if (linkPair) { // 检查稳定币池是否有足够的金额 let linkIn = linkPair['groupCoin'][swap.tokenStr(currentOut)]; let linkOut = linkPair['groupCoin'][swap.tokenStr(tokenAmountOut.token)]; let linkAmountIn = new BigNumber(amountOut).times(new BigNumber('10').pow(linkIn.decimals)).div(new BigNumber('10').pow(linkOut.decimals)); if (new BigNumber(linkOut.balance).isLessThan(amountOut)) { amountOut = '0'; } else { amountOut = linkAmountIn.toFixed(); } } // add for link - let reserves = swap.getReserves(currentIn, currentOut, pair); let reserveIn = new BigNumber(reserves[0]); let reserveOut = new BigNumber(reserves[1]); if (reserveIn.isEqualTo(0) || reserveOut.isEqualTo(0)) continue; let amountIn = getAmountInForBestTrade(amountOut, reserveIn, reserveOut, reserves[2]); if (swap.tokenEquals(currentIn, _in)) { let cloneCurrentPathArray = currentPathArray.slice(); // add for link + if (linkPair) { cloneCurrentPathArray.push(linkPair); } // add for link - cloneCurrentPathArray.push(pair); let tokenAmountIn = swap.tokenAmount(currentIn.chainId, currentIn.assetId, amountIn); wholeTradeArray.push({ path: cloneCurrentPathArray, tokenAmountIn: tokenAmountIn, tokenAmountOut: orginTokenAmountOut }); } else if (depth < (maxPairSize - 1) && length > 1) { let cloneCurrentPathArray = currentPathArray.slice(); // add for link + if (linkPair) { cloneCurrentPathArray.push(linkPair); } // add for link - cloneCurrentPathArray.push(pair); let subPairs = pairs.slice(0, i); subPairs = subPairs.concat(pairs.slice(i + 1, length)); realBestTradeExactOut( chainId, subPairs, _in, swap.tokenAmount(currentIn.chainId, currentIn.assetId, amountIn), cloneCurrentPathArray, wholeTradeArray, orginTokenAmountOut, depth + 1, maxPairSize, stableGroupArray ); } } return wholeTradeArray; } function isGroupStable(tokenA, tokenB, stableGroupArray) { let result = {success: false}; let length = stableGroupArray.length; for (let i = 0; i < length; i++) { let info = stableGroupArray[i]; if (info['groupCoin'][swap.tokenStr(tokenA)] && info['groupCoin'][swap.tokenStr(tokenB)]) { result.success = true; result.address = info.address; result.lpToken = swap.parseTokenStr(info.lpToken); result.groupCoin = info.groupCoin; return result; } } return result; } function getCumulativeAmountsOfStableSwap(amounts, coins) { let result = new BigNumber('0'); if (!amounts) { return result; } let length = amounts.length; let amount; let exponent; for (let i = 0; i < length; i++) { amount = new BigNumber(amounts[i]); exponent = 18 - Number(coins[i].decimals); if (exponent == 0) { result = result.plus(amount); } else { result = result.plus(amount.shiftedBy(exponent)); } } return result; } var swap = { /** * 当前时间,单位秒 * @returns {number} */ currentTime() { var times = new Date().valueOf(); return Number(times.toString().substr(0, times.toString().length - 3)); //交易时间 }, /** * * 组装一个token的json对象 */ token(chainId, assetId) { return {chainId: chainId, assetId: assetId}; }, /** * @param amount 资产数量 * @returns {{amount: *, chainId: *, assetId: *}} */ tokenAmount(chainId, assetId, amount) { let token = this.token(chainId, assetId); return {chainId: chainId, assetId: assetId, amount: amount, token: token}; }, /** * 按`chainId`和`assetId`排序两个token */ tokenSort(token0, token1) { let positiveSequence = token0.chainId < token1.chainId || (token0.chainId == token1.chainId && token0.assetId < token1.assetId); if (positiveSequence) { return [token0, token1]; } return [token1, token0]; }, tokenStr(token) { return token.chainId + '-' + token.assetId; }, parseTokenStr(tokenStr) { let split = tokenStr.split("-"); return {chainId: Number(split[0].trim()), assetId: Number(split[1].trim())}; }, /** * 根据token计算swap交易对地址 */ getStringPairAddress(chainId, token0, token1) { let array = this.tokenSort(token0, token1); let all = Buffer.concat([ cryptos.createHash('sha256').update(int32ToBytes(array[0].chainId)).digest(), cryptos.createHash('sha256').update(int32ToBytes(array[0].assetId)).digest(), cryptos.createHash('sha256').update(int32ToBytes(array[1].chainId)).digest(), cryptos.createHash('sha256').update(int32ToBytes(array[1].assetId)).digest() ]); let prefix = null; if (5 === chainId) { prefix = 'TNVT'; } else if (9 === chainId) { prefix = "NERVE"; } return sdk.getStringAddressBase(chainId, 4, null, cryptos.createHash('sha256').update(all).digest(), prefix); }, /** * * 比较两个token是否相同 */ tokenEquals(tokenA, tokenB) { return tokenA.chainId == tokenB.chainId && tokenA.assetId == tokenB.assetId; }, /** * * 组装一个交易对的json对象 */ pair(token0, token1, reserve0, reserve1, feeRate) { if (typeof feeRate === 'undefined') { feeRate = 3; } return {token0: token0, token1: token1, reserve0: reserve0, reserve1: reserve1, feeRate: feeRate}; }, /** * * 根据指定的token,返回相应顺序的流动性数量 */ getReserves(tokenA, tokenB, pair) { let array = this.tokenSort(tokenA, tokenB); let token0 = array[0]; let result = this.tokenEquals(tokenA, token0) ? [pair.reserve0, pair.reserve1, pair.feeRate] : [pair.reserve1, pair.reserve0, pair.feeRate]; return result; }, /** * 检查`token`是否为稳定币池中的token * @param token * @param stableGroupArray */ checkStableToken(token, stableGroupArray) { let result = {success: false}; let length = stableGroupArray.length; for (let i = 0; i < length; i++) { let info = stableGroupArray[i]; if (info['groupCoin'][this.tokenStr(token)]) { result.success = true; result.address = info.address; result.lpToken = this.parseTokenStr(info.lpToken); result.groupCoin = info.groupCoin; return result; } } return result; }, /** * 计算最优交易路径 */ bestTradeExactIn(chainId, pairs, tokenAmountIn, tokenOut, maxPairSize, stableGroupArray) { let trade = calcBestTradeExactIn(chainId, pairs, tokenAmountIn, tokenOut, [], [], tokenAmountIn, maxPairSize, stableGroupArray); if (!trade.path) return trade; let tokenIn = trade.tokenAmountIn.token; let amountIn = new BigNumber(trade.tokenAmountIn.amount); let tokenPath = [tokenIn]; let path = trade.path; let length = path.length; const _1 = new BigNumber(1); const _1000 = new BigNumber(1000); let _rate = _1; for (let i = 0; i < length; i++) { let pair = path[i]; let token0 = pair.token0; let token1 = pair.token1; if (this.tokenEquals(tokenIn, token0)) { tokenPath.push(token1); tokenIn = token1; } else { tokenPath.push(token0); tokenIn = token0; } _rate = _rate.multipliedBy(_1.minus(new BigNumber(pair.feeRate).div(_1000))); } _rate = _1.minus(_rate); const pathFee = amountIn.multipliedBy(_rate); trade.path = tokenPath; trade.pathFee = pathFee.toFixed(0); return trade; }, /** * 计算最优交易路径 */ bestTradeExactOut(chainId, pairs, tokenIn, tokenAmountOut, maxPairSize, stableGroupArray) { let trade = calcBestTradeExactOut(chainId, pairs, tokenIn, tokenAmountOut, [], [], tokenAmountOut, maxPairSize, stableGroupArray); if (!trade.path) return trade; let tokenOut = tokenAmountOut.token; let tokenPath = [tokenOut]; let path = trade.path; let length = path.length; let _tokenTmp = tokenOut; const _1 = new BigNumber(1); const _1000 = new BigNumber(1000); let _rate = _1; for (let i = 0; i < length; i++) { let pair = path[i]; let token0 = pair.token0; let token1 = pair.token1; if (swap.tokenEquals(_tokenTmp, token0)) { tokenPath.push(token1); _tokenTmp = token1; } else { tokenPath.push(token0); _tokenTmp = token0; } _rate = _rate.multipliedBy(_1.minus(new BigNumber(pair.feeRate).div(_1000))); } _rate = _1.minus(_rate); const pathFee = trade.tokenAmountIn.amount.multipliedBy(_rate); let tokenPathArray = tokenPath.reverse(); trade.path = tokenPathArray; trade.pathFee = pathFee.toFixed(0); return trade; }, /** * * 计算移除流动性可获得的资产数量 */ calRemoveLiquidity(liquidity, tokenA, tokenB, pair) { let _liquidity = new BigNumber(liquidity); let reserves = this.getReserves(tokenA, tokenB, pair); let reserveA = reserves[0]; let reserveB = reserves[1]; let totalSupply = new BigNumber(pair.totalSupply); // 可赎回的资产 let amountA = _liquidity.times(reserveA).dividedToIntegerBy(totalSupply); let amountB = _liquidity.times(reserveB).dividedToIntegerBy(totalSupply); if(amountA.isLessThanOrEqualTo(0) || amountB.isLessThanOrEqualTo(0)) { // INSUFFICIENT_LIQUIDITY_BURNED throw "sw_0014"; } return {amountA: amountA, amountB: amountB}; }, /** * 根据交易对中其中一个币种,计算另外一个币种可添加的流动性 */ quote(amountA, reserveA, reserveB) { let _amountA = new BigNumber(amountA); if(_amountA.isLessThanOrEqualTo(0)) { // INSUFFICIENT_AMOUNT throw "sw_0002"; } let _reserveA = new BigNumber(reserveA); let _reserveB = new BigNumber(reserveB); if(_reserveA.isLessThanOrEqualTo(0) || _reserveB.isLessThanOrEqualTo(0)) { // INSUFFICIENT_LIQUIDITY throw "sw_0003"; } return _amountA.times(_reserveB).dividedToIntegerBy(_reserveA); }, /** * 根据卖出数量,计算可买进的数量 */ getAmountOut(amountIn, reserveIn, reserveOut, feeRate) { if (typeof feeRate === 'undefined') { feeRate = 3; } let _amountIn = new BigNumber(amountIn); if(_amountIn.isLessThanOrEqualTo(0)) { // INSUFFICIENT_INPUT_AMOUNT throw "sw_0004"; } let _reserveIn = new BigNumber(reserveIn); let _reserveOut = new BigNumber(reserveOut); if(_reserveIn.isLessThanOrEqualTo(0) || _reserveOut.isLessThanOrEqualTo(0)) { // INSUFFICIENT_LIQUIDITY throw "sw_0003"; } // let amountInWithFee = _amountIn.times(997); let amountInWithFee = _amountIn.times(1000 - feeRate); let numerator = amountInWithFee.times(_reserveOut); let denominator = _reserveIn.times(1000).plus(amountInWithFee); let amountOut = numerator.dividedToIntegerBy(denominator); return amountOut; }, /** * 根据买进数量,计算可卖出数量 */ getAmountIn(amountOut, reserveIn, reserveOut, feeRate) { if (typeof feeRate === 'undefined') { feeRate = 3; } let _amountOut = new BigNumber(amountOut); if(_amountOut.isLessThanOrEqualTo(0)) { // INSUFFICIENT_OUTPUT_AMOUNT throw "sw_0005"; } let _reserveIn = new BigNumber(reserveIn); let _reserveOut = new BigNumber(reserveOut); if(_reserveOut.isLessThanOrEqualTo(_amountOut) || _reserveIn.isLessThanOrEqualTo(0) || _reserveOut.isLessThanOrEqualTo(0)) { // INSUFFICIENT_LIQUIDITY throw "sw_0003"; } let numerator = _reserveIn.times(_amountOut).times(1000); // let denominator = _reserveOut.minus(_amountOut).times(997); let denominator = _reserveOut.minus(_amountOut).times(1000 - feeRate); let amountIn = numerator.dividedToIntegerBy(denominator).plus(1); return amountIn; }, /** * * 根据卖出数量,计算可买进的数量 */ getAmountsOut(amountIn, tokenPathArray, pairsArray) { let pathLength = tokenPathArray.length; if (pathLength < 1 || pathLength > 100) { // INVALID_PATH throw "sw_0006"; } let amounts = new Array(pathLength); amounts[0] = amountIn; let reserveIn; let reserveOut; for (let i = 0; i < pathLength - 1; i++) { let reserves = this.getReserves(tokenPathArray[i], tokenPathArray[i + 1], pairsArray[i]); reserveIn = reserves[0]; reserveOut = reserves[1]; amounts[i + 1] = this.getAmountOut(amounts[i], reserveIn, reserveOut, reserves[2]); } return amounts; }, /** * * 根据买进数量,计算可卖出数量 */ getAmountsIn(amountOut, tokenPathArray, pairsArray) { let pathLength = tokenPathArray.length; if (pathLength < 1 || pathLength > 100) { // INVALID_PATH throw "sw_0006"; } let amounts = new Array(pathLength); amounts[pathLength - 1] = amountOut; let reserveIn; let reserveOut; for (let i = pathLength - 1; i > 0; i--) { let reserves = this.getReserves(tokenPathArray[i - 1], tokenPathArray[i], pairsArray[i - 1]); reserveIn = reserves[0]; reserveOut = reserves[1]; amounts[i - 1] = this.getAmountIn(amounts[i], reserveIn, reserveOut, reserves[2]); } return amounts; }, /** * * 根据交易数量,计算当前流动池数量和交易后的流动池数量 */ getAmountsReserves(amounts, tokenPathArray, pairsArray) { let pathLength = tokenPathArray.length; if (pathLength < 1 || pathLength > 100) { // INVALID_PATH throw "sw_0006"; } let amountIn = amounts[0]; let amountOut = amounts[amounts.length - 1]; let reserves = this.getReserves(tokenPathArray[0], tokenPathArray[1], pairsArray[0]); let reserveIn = new BigNumber(reserves[0]); let _reserveIn = reserveIn.plus(amountIn); let reserveOut; if (pathLength < 3) { reserveOut = new BigNumber(reserves[1]); } else { reserves = this.getReserves(tokenPathArray[pathLength - 2], tokenPathArray[pathLength - 1], pairsArray[pathLength - 2]); reserveOut = new BigNumber(reserves[1]); } let _reserveOut = reserveOut.minus(amountOut); return [reserveIn, reserveOut, _reserveIn, _reserveOut]; }, /** * * 根据交易数量计算价格影响 * * 计算规则: * 原始池: reserveIn, reserveOut * 交易后的池: _reserveIn, _reserveOut * 原始价格: reserveOut / reserveIn * 交易后的价格: _reserveOut / _reserveIn * 结果 = (原始价格 - 交易后的价格).abs() / 原始价格 */ getPriceImpact(amounts, tokenPathArray, pairsArray) { let array = this.getAmountsReserves(amounts, tokenPathArray, pairsArray); let reserveIn = array[0]; let reserveOut = array[1]; let _reserveIn = array[2]; let _reserveOut = array[3]; let priceImpact = new BigNumber(1).minus( new BigNumber ((_reserveOut.times(reserveIn).shiftedBy(78)) .div(_reserveIn.times(reserveOut)).toFixed(78)) .shiftedBy(-78) ).abs(); return priceImpact; }, /** * 组装交易: swap创建交易对 * * @param fromAddress 用户地址 * @param tokenA 资产A的类型,示例:nerve.swap.token(5,1) * @param tokenB 资产B的类型,示例:nerve.swap.token(5,6) * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async swapCreatePair(fromAddress, tokenA, tokenB, remark) { let transferInfo = { fromAddress: fromAddress, toAddress: fromAddress, fee: 0, assetsChainId: nerve.chainId(), assetsId: 1, amount: 0, }; let balance = await util.getNulsBalance(transferInfo.fromAddress, transferInfo.assetsChainId, transferInfo.assetsId); let inOrOutputs = await util.inputsOrOutputs(transferInfo, balance.data); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 61, { tokenA: tokenA, tokenB: tokenB } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: swap添加流动性 * * @param fromAddress 用户地址 * @param tokenAmountA 添加的资产A的数量,示例:nerve.swap.tokenAmount(5, 1, "140000000000") * @param tokenAmountB 添加的资产B的数量,示例:nerve.swap.tokenAmount(5, 6, "100000000") * @param amountAMin 资产A最小添加值 * @param amountBMin 资产B最小添加值 * @param deadline 过期时间,示例:nerve.swap.currentTime() + 300 (5分钟/300秒以后) * @param to 流动性份额接收地址 * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async swapAddLiquidity(fromAddress, tokenAmountA, tokenAmountB, amountAMin, amountBMin, deadline, to, remark) { let pairAddress = this.getStringPairAddress(nerve.chainId(), tokenAmountA, tokenAmountB); let inOrOutputs = await inputsOrOutputsOfSwapAddLiquidity(fromAddress, to, tokenAmountA, tokenAmountB, pairAddress); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 64, { tokenA: tokenAmountA, tokenB: tokenAmountB, to: to, deadline: deadline, amountAMin: amountAMin, amountBMin: amountBMin } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: swap移除流动性 * * @param fromAddress 用户地址 * @param tokenAmountLP 移除的资产LP的数量,示例:nerve.swap.tokenAmount(5, 18, "2698778989") * @param tokenAmountAMin 资产A最小移除值,示例:nerve.swap.tokenAmount(5, 1, "140000000000") * @param tokenAmountBMin 资产B最小移除值,示例:nerve.swap.tokenAmount(5, 6, "100000000") * @param deadline 过期时间,示例:nerve.swap.currentTime() + 300 (5分钟/300秒以后) * @param to 移除流动性份额接收地址 * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async swapRemoveLiquidity(fromAddress, tokenAmountLP, tokenAmountAMin, tokenAmountBMin, deadline, to, remark) { let pairAddress = this.getStringPairAddress(nerve.chainId(), tokenAmountAMin, tokenAmountBMin); let transferInfo = { fromAddress: fromAddress, toAddress: pairAddress, fee: 0, assetsChainId: tokenAmountLP.chainId, assetsId: tokenAmountLP.assetId, amount: tokenAmountLP.amount, }; let balance = await util.getNulsBalance(transferInfo.fromAddress, transferInfo.assetsChainId, transferInfo.assetsId); let inOrOutputs = await util.inputsOrOutputs(transferInfo, balance.data); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 65, { tokenA: tokenAmountAMin, tokenB: tokenAmountBMin, to: to, deadline: deadline, amountAMin: tokenAmountAMin.amount, amountBMin: tokenAmountBMin.amount } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: swap币币交易 * * @param fromAddress 用户地址 * @param amountIn 卖出的资产数量 * @param tokenPath 币币交换资产路径,路径中最后一个资产,是用户要买进的资产, * 如卖A买B: [A, B] or [A, C, B], * 示例: [nerve.swap.token(5, 1), nerve.swap.token(5, 6)] * @param amountOutMin 最小买进的资产数量 * @param feeTo 交易手续费取出一部分给指定的接收地址 * @param deadline 过期时间,示例:nerve.swap.currentTime() + 300 (5分钟/300秒以后) * @param to 资产接收地址 * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async swapTrade(fromAddress, amountIn, tokenPath, amountOutMin, feeTo, deadline, to, remark) { if (feeTo == null) { feeTo = ''; } let firstTokenIn = tokenPath[0]; let pairAddress = this.getStringPairAddress(nerve.chainId(), firstTokenIn, tokenPath[1]); let transferInfo = { fromAddress: fromAddress, toAddress: pairAddress, fee: 0, assetsChainId: firstTokenIn.chainId, assetsId: firstTokenIn.assetId, amount: amountIn, }; let balance = await util.getNulsBalance(transferInfo.fromAddress, transferInfo.assetsChainId, transferInfo.assetsId); let inOrOutputs = await util.inputsOrOutputs(transferInfo, balance.data); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 63, { amountOutMin: amountOutMin, to: to, feeTo: feeTo, deadline: deadline, tokenPath: tokenPath } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: swap币币交易(聚合稳定币池) * * @param fromAddress 用户地址 * @param amountIn 卖出的资产数量 * @param tokenPath 币币交换资产路径,路径中最后一个资产,是用户要买进的资产, * 如卖A买B: [A, B] or [A, C, B], * 示例: [nerve.swap.token(5, 1), nerve.swap.token(5, 6)] * @param amountOutMin 最小买进的资产数量 * @param feeTo 交易手续费取出一部分给指定的接收地址 * @param deadline 过期时间,示例:nerve.swap.currentTime() + 300 (5分钟/300秒以后) * @param to 资产接收地址 * @param stableGroupArray 支持的稳定币池信息 * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async swapTradeWithCombinedStable(fromAddress, amountIn, tokenPath, amountOutMin, feeTo, deadline, to, stableGroupArray, remark) { if (feeTo == null) { feeTo = ''; } let firstTokenIn = tokenPath[0]; let secondTokenIn = tokenPath[1]; // 检查前两个币是否稳定币池 let pairAddress; let group = isGroupStable(firstTokenIn, secondTokenIn, stableGroupArray); if (group.success) { pairAddress = group.address; } if (!pairAddress) { // 普通交易对 pairAddress = this.getStringPairAddress(nerve.chainId(), firstTokenIn, secondTokenIn); } let transferInfo = { fromAddress: fromAddress, toAddress: pairAddress, fee: 0, assetsChainId: firstTokenIn.chainId, assetsId: firstTokenIn.assetId, amount: amountIn, }; let balance = await util.getNulsBalance(transferInfo.fromAddress, transferInfo.assetsChainId, transferInfo.assetsId); let inOrOutputs = await util.inputsOrOutputs(transferInfo, balance.data); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 63, { amountOutMin: amountOutMin, to: to, feeTo: feeTo, deadline: deadline, tokenPath: tokenPath } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: stable-swap创建交易对 * * @param fromAddress 用户地址 * @param coins 资产类型列表,示例:[nerve.swap.token(5, 6), nerve.swap.token(5, 9), nerve.swap.token(5, 7), nerve.swap.token(5, 8)] * @param symbol LP名称(选填) * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async stableSwapCreatePair(fromAddress, coins, symbol, remark) { let transferInfo = { fromAddress: fromAddress, toAddress: fromAddress, fee: 0, assetsChainId: nerve.chainId(), assetsId: 1, amount: 0, }; let balance = await util.getNulsBalance(transferInfo.fromAddress, transferInfo.chainId, transferInfo.assetId); let inOrOutputs = await util.inputsOrOutputs(transferInfo, balance.data); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 71, { coins: coins, symbol: symbol } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: stable-swap添加流动性 * * @param fromAddress 用户地址 * @param stablePairAddress 交易对地址 * @param tokenAmounts 添加的资产数量列表,示例:[swap.tokenAmount(5, 6, "60000000000"), swap.tokenAmount(5, 7, "700000000"), swap.tokenAmount(5, 8, "800000000"), swap.tokenAmount(5, 9, "900000000")] * @param deadline 过期时间,示例:nerve.swap.currentTime() + 300 (5分钟/300秒以后) * @param to 流动性份额接收地址 * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async stableSwapAddLiquidity(fromAddress, stablePairAddress, tokenAmounts, deadline, to, remark) { let inOrOutputs = await inputsOrOutputsOfStableAddLiquidityOrTrade(fromAddress, stablePairAddress, tokenAmounts); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 73, { to: to } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: stable-swap移除流动性 * * @param fromAddress 用户地址 * @param stablePairAddress 交易对地址 * @param tokenAmountLP 移除的资产LP的数量,示例:nerve.swap.tokenAmount(5, 18, "2698778989") * @param receiveOrderIndexs 按币种索引顺序接收资产,示例:[4, 2, 3, 1] * @param deadline 过期时间,示例:nerve.swap.currentTime() + 300 (5分钟/300秒以后) * @param to 移除流动性份额接收地址 * @param remark 交易备注 * @returns 交易序列化hex字符串 */ async stableSwapRemoveLiquidity(fromAddress, stablePairAddress, tokenAmountLP, receiveOrderIndexs, deadline, to, remark) { let pairAddress = stablePairAddress; let transferInfo = { fromAddress: fromAddress, toAddress: pairAddress, fee: 0, assetsChainId: tokenAmountLP.chainId, assetsId: tokenAmountLP.assetId, amount: tokenAmountLP.amount, }; let balance = await util.getNulsBalance(transferInfo.fromAddress, transferInfo.assetsChainId, transferInfo.assetsId); let inOrOutputs = await util.inputsOrOutputs(transferInfo, balance.data); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 74, { indexs: Buffer.from(receiveOrderIndexs), to: to } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易:StableSwap币币交易 * * @param fromAddress 用户地址 * @param stablePairAddress 交易对地址 * @param amountIns 卖出的资产数量列表,示例:[nerve.swap.tokenAmount(5, 6, "600000000"), nerve.swap.tokenAmount(5, 9, "90000000")] * @param tokenOutIndex 买进的资产索引,示例:3 * @param feeTo 交易手续费接收地址 * @param deadline 过期时间,示例:nerve.swap.currentTime() + 300 (5分钟/300秒以后) * @param to 资产接收地址 * @param remark 交易备注 * @param feeTokenAmount 交易手续费,示例:nerve.swap.tokenAmount(5, 6, "6000000") * @returns 交易序列化hex字符串 */ async stableSwapTrade(fromAddress, stablePairAddress, amountIns, tokenOutIndex, feeTo, deadline, to, remark, feeTokenAmount) { let inOrOutputs = await inputsOrOutputsOfStableAddLiquidityOrTrade(fromAddress, stablePairAddress, amountIns, feeTo, feeTokenAmount); if (!inOrOutputs.success) { throw "inputs、outputs组装错误"; } if (feeTo == null) { feeTo = ''; } let tAssemble = nerve.transactionAssemble( inOrOutputs.data.inputs, inOrOutputs.data.outputs, remark, 72, { to: to, tokenOutIndex: Buffer.from([tokenOutIndex]), feeTo: feeTo, } ); //获取hash let hash = await tAssemble.getHash(); let txhex = tAssemble.txSerialize().toString("hex"); return {hash: hash.toString('hex'), hex: txhex}; }, /** * 组装交易: stable-lp-swap-trade * * @param fromAddress 用户地址 * @param stablePairAddress 稳定币交易对地址 * @param amountIn 卖出的资产数量 * @param tokenPath