@swaptoshi/dex-module
Version:
Klayr decentralized exchange (dex) on-chain module
368 lines • 16.3 kB
JavaScript
;
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