UNPKG

@swaptoshi/dex-module

Version:

Klayr decentralized exchange (dex) on-chain module

368 lines 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.constructTokenURI = constructTokenURI; exports.tickToDecimalString = tickToDecimalString; exports.fixedPointToDecimalString = fixedPointToDecimalString; exports.feeToPercentString = feeToPercentString; exports.addressToString = addressToString; exports.generateSVGImage = generateSVGImage; exports.tokenToColorHex = tokenToColorHex; const cryptography = require("@klayr/cryptography"); const crc_1 = require("crc"); const int_1 = require("../int"); const TickMath = require("../core/tick_math"); const FullMath = require("../core/full_math"); const HexStrings = require("./hex_strings"); const NFTSVG = require("./nft_svg"); const defaultDecimalStringParams = Object.freeze({ sigfigs: '0', bufferLength: '0', sigfigIndex: '0', decimalIndex: '0', zerosStartIndex: '0', zerosEndIndex: '0', isLessThanOne: false, isPercent: false, }); const sqrt10X128 = int_1.Uint256.from('1076067327063303206878105757264492625226').toString(); function constructTokenURI(params) { const name = generateName(params, feeToPercentString(params.fee)); const descriptionPartOne = generateDescriptionPartOne(params.config, escapeQuotes(params.quoteTokenSymbol), escapeQuotes(params.baseTokenSymbol), cryptography.address.getKlayr32AddressFromAddress(params.poolAddress)); const descriptionPartTwo = generateDescriptionPartTwo(params.tokenId.toString(), escapeQuotes(params.baseTokenSymbol), addressToString(params.quoteTokenAddress), addressToString(params.baseTokenAddress), feeToPercentString(params.fee)); const image = Buffer.from(generateSVGImage(params)).toString('base64'); return [ 'data:application/json;base64,', Buffer.from(['{"name":"', name, '", "description":"', descriptionPartOne, descriptionPartTwo, '", "image": "', 'data:image/svg+xml;base64,', image, '"}'].join('')).toString('base64'), ].join(''); } function escapeQuotes(symbol) { const symbolBytes = [...symbol].map(char => char.charCodeAt(0)); let quotesCount = 0; for (let i = 0; i < symbolBytes.length; i += 1) { if (symbolBytes[i] === 34) { quotesCount += 1; } } if (quotesCount > 0) { const escapedBytes = new Array(symbolBytes.length + quotesCount); let index = 0; for (let i = 0; i < symbolBytes.length; i += 1) { if (symbolBytes[i] === 34) { escapedBytes[(index += 1)] = 92; } escapedBytes[(index += 1)] = symbolBytes[i]; } return String.fromCharCode(...escapedBytes); } return symbol; } function generateDescriptionPartOne(config, quoteTokenSymbol, baseTokenSymbol, poolAddress) { return [ `This NFT represents a liquidity position in a ${config.nftPositionMetadata.dex.name} `, quoteTokenSymbol, '-', baseTokenSymbol, ' pool. ', 'The owner of this NFT can modify or redeem the position.\\n', '\\nPool Address: ', poolAddress, '\\n', quoteTokenSymbol, ].join(''); } function generateDescriptionPartTwo(tokenId, baseTokenSymbol, quoteTokenAddress, baseTokenAddress, feeTier) { return [ ' Token ID: ', quoteTokenAddress, '\\n', baseTokenSymbol, ' Token ID: ', baseTokenAddress, '\\nFee Tier: ', feeTier, '\\nNFT ID: ', tokenId, '\\n\\n', '⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure token addresses match the expected tokens, as token symbols may be imitated.', ].join(''); } function generateName(params, feeTier) { return [ `${params.config.nftPositionMetadata.dex.name} - `, feeTier, ' - ', escapeQuotes(params.quoteTokenSymbol), '/', escapeQuotes(params.baseTokenSymbol), ' - ', tickToDecimalString(!params.flipRatio ? params.tickLower : params.tickUpper, params.tickSpacing, params.baseTokenDecimals, params.quoteTokenDecimals, params.flipRatio), '<>', tickToDecimalString(!params.flipRatio ? params.tickUpper : params.tickLower, params.tickSpacing, params.baseTokenDecimals, params.quoteTokenDecimals, params.flipRatio), ].join(''); } function generateDecimalString(params) { const buffer = new Array(params.bufferLength).fill('0'); if (params.isPercent) { buffer[buffer.length - 1] = '%'; } if (params.isLessThanOne) { buffer[0] = '0'; buffer[1] = '.'; } for (let zerosCursor = parseInt(params.zerosStartIndex, 10); zerosCursor <= parseInt(params.zerosEndIndex, 10); zerosCursor += 1) { buffer[zerosCursor] = '0'; } let { sigfigIndex } = params; while (int_1.Uint256.from(params.sigfigs).gt(0)) { if (int_1.Uint8.from(params.decimalIndex).gt(0) && sigfigIndex === params.decimalIndex) { buffer[int_1.Uint8.from(sigfigIndex).toNumber()] = '.'; sigfigIndex = int_1.Uint8.from(sigfigIndex).sub(1).toString(); } buffer[int_1.Uint8.from(sigfigIndex).toNumber()] = String.fromCharCode(48 + int_1.Uint256.from(params.sigfigs).mod(10).toNumber()); sigfigIndex = int_1.Uint8.from(sigfigIndex).sub(1).toString(); params.sigfigs = int_1.Uint256.from(params.sigfigs).div(10).toString(); } return buffer.join(''); } function tickToDecimalString(tick, tickSpacing, baseTokenDecimals, quoteTokenDecimals, flipRatio) { if (int_1.Int24.from(tick).eq(int_1.Int24.from(TickMath.MIN_TICK).div(tickSpacing).mul(tickSpacing))) { return !flipRatio ? 'MIN' : 'MAX'; } if (int_1.Int24.from(tick).eq(int_1.Int24.from(TickMath.MAX_TICK).div(tickSpacing).mul(tickSpacing))) { return !flipRatio ? 'MAX' : 'MIN'; } let sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick); if (flipRatio) { sqrtRatioX96 = int_1.Uint256.from(1).shl(192).div(sqrtRatioX96).toString(); } return fixedPointToDecimalString(sqrtRatioX96, baseTokenDecimals, quoteTokenDecimals); } function sigfigsRounded(value, digits) { let _value = value; let extraDigit = false; if (int_1.Uint8.from(digits).gt(5)) { _value = int_1.Uint256.from(_value) .div(int_1.Uint256.from(10).pow(int_1.Uint8.from(digits).sub(5))) .toString(); } const roundUp = int_1.Uint256.from(_value).mod(10).gt(4); _value = int_1.Uint256.from(_value).div(10).toString(); if (roundUp) { _value = int_1.Uint256.from(_value).add(1).toString(); } if (int_1.Uint256.from(_value).eq(100000)) { _value = int_1.Uint256.from(_value).div(10).toString(); extraDigit = true; } return [_value, extraDigit]; } function adjustForDecimalPrecision(sqrtRatioX96, baseTokenDecimals, quoteTokenDecimals) { let adjustedSqrtRatioX96; const difference = int_1.Int256.from(baseTokenDecimals).sub(quoteTokenDecimals).abs(); if (difference.gt(0) && difference.lte(18)) { if (int_1.Uint8.from(baseTokenDecimals).gt(quoteTokenDecimals)) { adjustedSqrtRatioX96 = int_1.Uint160.from(sqrtRatioX96) .mul(int_1.Uint256.from(10).pow(difference.div(2))) .toString(); if (difference.mod(2).toNumber() === 1) { adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, sqrt10X128, int_1.Uint256.from(1).shl(128).toString()); } } else { adjustedSqrtRatioX96 = int_1.Uint160.from(sqrtRatioX96) .div(int_1.Uint256.from(10).pow(difference.div(2))) .toString(); if (difference.mod(2).toNumber() === 1) { adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, int_1.Uint256.from(1).shl(128).toString(), sqrt10X128); } } } else { adjustedSqrtRatioX96 = int_1.Uint256.from(0).add(sqrtRatioX96).toString(); } return adjustedSqrtRatioX96; } function fixedPointToDecimalString(sqrtRatioX96, baseTokenDecimals, quoteTokenDecimals) { const adjustedSqrtRatioX96 = adjustForDecimalPrecision(sqrtRatioX96, baseTokenDecimals, quoteTokenDecimals); let value = FullMath.mulDiv(adjustedSqrtRatioX96, adjustedSqrtRatioX96, int_1.Uint256.from(1).shl(64).toString()); const priceBelow1 = int_1.Uint256.from(adjustedSqrtRatioX96).lt(int_1.Uint256.from(2).pow(96)); if (priceBelow1) { value = FullMath.mulDiv(value, int_1.Uint256.from(10).pow(44).toString(), int_1.Uint256.from(1).shl(128).toString()); } else { value = FullMath.mulDiv(value, int_1.Uint256.from(10).pow(5).toString(), int_1.Uint256.from(1).shl(128).toString()); } let temp = int_1.Uint256.from(value); let digits = '0'; while (!temp.eq(0)) { digits = int_1.Uint8.from(digits).add(1).toString(); temp = temp.div(10); } digits = int_1.Uint8.from(digits).sub(1).toString(); const [sigfigs, extraDigit] = sigfigsRounded(value, digits); if (extraDigit) digits = int_1.Uint8.from(digits).add(1).toString(); const params = { ...defaultDecimalStringParams }; if (priceBelow1) { params.bufferLength = int_1.Uint8.from(7).add(int_1.Uint8.from(43).sub(digits)).toString(); params.zerosStartIndex = '2'; params.zerosEndIndex = int_1.Uint8.from(0).add(int_1.Uint256.from(43).sub(digits).add(1)).toString(); params.sigfigIndex = int_1.Uint8.from(params.bufferLength).sub(1).toString(); } else if (int_1.Uint8.from(digits).gte(9)) { params.bufferLength = int_1.Uint8.from(digits).sub(4).toString(); params.zerosStartIndex = '5'; params.zerosEndIndex = int_1.Uint8.from(params.bufferLength).sub(1).toString(); params.sigfigIndex = '4'; } else { params.bufferLength = '6'; params.sigfigIndex = '5'; params.decimalIndex = int_1.Uint8.from(digits).sub(5).add(1).toString(); } params.sigfigs = sigfigs; params.isLessThanOne = priceBelow1; params.isPercent = false; return generateDecimalString(params); } function feeToPercentString(fee) { if (fee === '0') return '0%'; let temp = int_1.Uint24.from(fee); let digits = int_1.Uint256.from(0); let numSigfigs = int_1.Uint8.from(0); while (!temp.eq(0)) { if (numSigfigs.gt(0)) numSigfigs = numSigfigs.add(1); else if (!temp.mod(10).eq(0)) numSigfigs = numSigfigs.add(1); digits = digits.add(1); temp = temp.div(10); } const params = { ...defaultDecimalStringParams }; let nZeros = int_1.Uint256.from(0); if (digits.gte(5)) { const decimalPlace = digits.sub(numSigfigs).gte(4) ? 0 : 1; nZeros = digits.sub(5).lt(numSigfigs.sub(1)) ? int_1.Uint256.from(0) : digits.sub(5).sub(numSigfigs.sub(1)); params.zerosStartIndex = numSigfigs.toString(); params.zerosEndIndex = int_1.Uint8.from(0).add(params.zerosStartIndex).add(nZeros).sub(1).toString(); params.sigfigIndex = int_1.Uint8.from(0).add(params.zerosStartIndex).sub(1).add(decimalPlace).toString(); params.bufferLength = int_1.Uint8.from(0) .add(nZeros.add(numSigfigs.add(1)).add(decimalPlace)) .toString(); } else { nZeros = int_1.Uint256.from(5).sub(digits); params.zerosStartIndex = '2'; params.zerosEndIndex = int_1.Uint8.from(nZeros.add(params.zerosStartIndex).sub(1)).toString(); params.bufferLength = int_1.Uint8.from(nZeros.add(numSigfigs.add(2))).toString(); params.sigfigIndex = int_1.Uint8.from(params.bufferLength).sub(2).toString(); params.isLessThanOne = true; } params.sigfigs = int_1.Uint256.from(fee) .div(int_1.Uint256.from(10).pow(digits.sub(numSigfigs))) .toString(); params.isPercent = true; params.decimalIndex = digits.gt(4) ? int_1.Uint8.from(digits.sub(4)).toString() : '0'; return `${generateDecimalString(params)}%`; } function addressToString(addr) { return addr.toString('hex'); } function generateSVGImage(params) { const svgParams = { quoteToken: addressToString(params.quoteTokenAddress), baseToken: addressToString(params.baseTokenAddress), poolAddress: params.poolAddress, quoteTokenSymbol: params.quoteTokenSymbol, baseTokenSymbol: params.baseTokenSymbol, feeTier: feeToPercentString(params.fee), tickLower: params.tickLower, tickUpper: params.tickUpper, tickSpacing: params.tickSpacing, overRange: overRange(params.tickLower, params.tickUpper, params.tickCurrent), tokenId: params.tokenId, color0: tokenToColorHex(params.quoteTokenAddress, params.config), color1: tokenToColorHex(params.baseTokenAddress, params.config), color2: tokenToColorHex(params.quoteTokenAddress, params.config), color3: tokenToColorHex(params.baseTokenAddress, params.config), x1: scale(getCircleCoord(HexStrings.bufferToUint256String(params.quoteTokenAddress), '16', params.tokenId), '0', '255', '16', '274'), y1: scale(getCircleCoord(HexStrings.bufferToUint256String(params.baseTokenAddress), '16', params.tokenId), '0', '255', '100', '484'), x2: scale(getCircleCoord(HexStrings.bufferToUint256String(params.quoteTokenAddress), '32', params.tokenId), '0', '255', '16', '274'), y2: scale(getCircleCoord(HexStrings.bufferToUint256String(params.baseTokenAddress), '32', params.tokenId), '0', '255', '100', '484'), x3: scale(getCircleCoord(HexStrings.bufferToUint256String(params.quoteTokenAddress), '48', params.tokenId), '0', '255', '16', '274'), y3: scale(getCircleCoord(HexStrings.bufferToUint256String(params.baseTokenAddress), '48', params.tokenId), '0', '255', '100', '484'), }; return NFTSVG.generateSVG(svgParams); } function overRange(tickLower, tickUpper, tickCurrent) { if (int_1.Int24.from(tickCurrent).lt(tickLower)) { return '-1'; } if (int_1.Int24.from(tickCurrent).gt(tickUpper)) { return '1'; } return '0'; } function scale(n, inMn, inMx, outMn, outMx) { return int_1.Uint256.from(n).sub(inMn).mul(int_1.Uint256.from(outMx).sub(outMn)).div(int_1.Uint256.from(inMx).sub(inMn)).add(outMn).toString(); } function tokenToColorHex(token, config) { const hash = (0, crc_1.crc32)(token); const hue = config.nftPositionColorRange.hue[0] + (hash % (config.nftPositionColorRange.hue[1] - config.nftPositionColorRange.hue[0])); const saturation = config.nftPositionColorRange.saturation[0] + (hash % (config.nftPositionColorRange.saturation[1] - config.nftPositionColorRange.saturation[0])); const lightness = config.nftPositionColorRange.lightness[0] + (hash % (config.nftPositionColorRange.lightness[1] - config.nftPositionColorRange.lightness[0])); const color = hslToHex(hue, saturation, lightness); return color; } function hslToHex(h, s, l) { s /= 100; l /= 100; const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); const m = l - c / 2; let r = 0; let g = 0; let b = 0; if (h >= 0 && h < 60) { r = c; g = x; b = 0; } else if (h >= 60 && h < 120) { r = x; g = c; b = 0; } else if (h >= 120 && h < 180) { r = 0; g = c; b = x; } else if (h >= 180 && h < 240) { r = 0; g = x; b = c; } else if (h >= 240 && h < 300) { r = x; g = 0; b = c; } else if (h >= 300 && h < 360) { r = c; g = 0; b = x; } r = Math.round((r + m) * 255); g = Math.round((g + m) * 255); b = Math.round((b + m) * 255); return `${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; } function getCircleCoord(tokenAddress, offset, tokenId) { return int_1.Uint256.from(sliceTokenHex(tokenAddress, offset)).mul(tokenId).mod(255).toString(); } function sliceTokenHex(token, offset) { return int_1.Uint256.from(int_1.Uint8.from(0).add(int_1.Uint256.from(token).shr(offset))).toString(); } //# sourceMappingURL=nft_descriptor.js.map