UNPKG

@swaptoshi/dex-module

Version:

Klayr decentralized exchange (dex) on-chain module

219 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SwapRouter = void 0; const cryptography = require("@klayr/cryptography"); const utils = require("@klayr/utils"); const int_1 = require("../library/int"); const pool_1 = require("../pool"); const constants_1 = require("../../constants"); const TickMath = require("../library/core/tick_math"); const Path = require("../library/periphery/path"); class SwapRouter { constructor(stores, config, moduleName) { this.address = constants_1.ROUTER_ADDRESS; this._DEFAULT_AMOUNT_IN_CACHED = int_1.Uint256.MAX; this.feeProtocol = 0; this._amountInCached = '0'; this.mutableDependencyReady = false; this.moduleName = moduleName; this._amountInCached = this._DEFAULT_AMOUNT_IN_CACHED; this.poolStore = stores.get(pool_1.PoolStore); this.setConfig(config); } addDependencies(context, tokenMethod) { if (this.mutableDependencyReady) { throw new Error('this instance dependencies already been configured'); } this.mutableContext = context; this.tokenMethod = tokenMethod; this.mutableDependencyReady = true; } setSender(senderAddress) { if (this.mutableContext) { this.mutableContext.senderAddress = senderAddress; } } setConfig(config) { var _a; this.feeProtocol = (_a = config.feeProtocol) !== null && _a !== void 0 ? _a : 0; this.feeProtocolPool = config.feeProtocolPool ? cryptography.address.getAddressFromKlayr32Address(config.feeProtocolPool, config.feeProtocolPool.substring(0, 3)) : undefined; this._validateFeeProtocol(); } async exactInputSingle(params) { this._checkDependencies(); this._checkDeadline(params.deadline); const amountOut = int_1.Uint256.from(await this._exactInputInternal(params.amountIn, params.recipient, params.sqrtPriceLimitX96, this._createPayload(params.tokenIn, params.tokenOut, params.fee, this.mutableContext.senderAddress))); if (amountOut.lt(params.amountOutMinimum)) throw new Error('Too little received'); await this._checkRemainingBalance(Buffer.from(this._createPayload(params.tokenIn, params.tokenOut, params.fee, this.mutableContext.senderAddress).path, 'hex')); return amountOut.toString(); } async exactInput(_params) { this._checkDependencies(); this._checkDeadline(_params.deadline); let payer = this.mutableContext.senderAddress; const params = utils.objects.cloneDeep(_params); let amountOut = int_1.Uint256.from(0); const tokenList = new Set(); while (true) { const hasMultiplePools = Path.hasMultiplePools(params.path); params.amountIn = await this._exactInputInternal(params.amountIn, hasMultiplePools ? this.address : params.recipient, '0', { path: Path.getFirstPool(params.path).toString('hex'), payer: payer.toString('hex'), }); const [tokenIn, tokenOut] = Path.decodeFirstPool(params.path); tokenList.add(tokenIn); tokenList.add(tokenOut); if (hasMultiplePools) { payer = this.address; params.path = Path.skipToken(params.path); } else { amountOut = int_1.Uint256.from(params.amountIn); break; } } if (amountOut.lt(params.amountOutMinimum)) throw new Error('Too little received'); await this._checkRemainingBalance(_params.path); return amountOut.toString(); } async exactOutputSingle(params) { this._checkDependencies(); this._checkDeadline(params.deadline); const amountIn = await this._exactOutputInternal(params.amountOut, params.recipient, params.sqrtPriceLimitX96, this._createPayload(params.tokenOut, params.tokenIn, params.fee, this.mutableContext.senderAddress)); if (int_1.Uint256.from(amountIn).gt(params.amountInMaximum)) throw new Error('Too much requested'); await this._checkRemainingBalance(Buffer.from(this._createPayload(params.tokenOut, params.tokenIn, params.fee, this.mutableContext.senderAddress).path, 'hex')); this._amountInCached = this._DEFAULT_AMOUNT_IN_CACHED; return amountIn; } async exactOutput(params) { this._checkDependencies(); this._checkDeadline(params.deadline); await this._exactOutputInternal(params.amountOut, params.recipient, '0', { path: params.path.toString('hex'), payer: this.mutableContext.senderAddress.toString('hex'), }); const amountIn = this._amountInCached; if (int_1.Uint256.from(amountIn).gt(params.amountInMaximum)) throw new Error('Too much requested'); await this._checkRemainingBalance(params.path); this._amountInCached = this._DEFAULT_AMOUNT_IN_CACHED; return amountIn; } _checkDependencies() { if (!this.mutableDependencyReady) { throw new Error('dependencies not configured'); } } _checkDeadline(deadline) { if (int_1.Uint256.from(this.mutableContext.timestamp).gt(deadline)) throw new Error('Transaction too old'); } async _pay(token, payer, recipient, value) { await this.tokenMethod.transfer(this.mutableContext.context, payer, recipient, token, BigInt(value)); } _createPayload(tokenIn, tokenOut, fee, payer) { const feeBuff = Buffer.allocUnsafe(3); feeBuff.writeUIntBE(parseInt(fee, 10), 0, 3); return { path: Buffer.concat([tokenIn, feeBuff, tokenOut]).toString('hex'), payer: payer.toString('hex'), }; } async _getPool(tokenA, tokenB, fee) { return this.poolStore.getMutablePool(this.mutableContext, tokenA, tokenB, fee); } async _swapCallback(amount0Delta, amount1Delta, _data) { if (int_1.Int256.from(amount0Delta).lte(0) && int_1.Int256.from(amount1Delta).lte(0)) throw new Error('invalid amount0Delta and/or amount1Delta'); const data = JSON.parse(_data); const [tokenIn, tokenOut, fee] = Path.decodeFirstPool(Buffer.from(data.path, 'hex')); const [isExactInput, amountToPay] = int_1.Int256.from(amount0Delta).gt(0) ? [tokenIn.compare(tokenOut) < 0, int_1.Uint256.from(0).add(amount0Delta)] : [tokenOut.compare(tokenIn) < 0, int_1.Uint256.from(0).add(amount1Delta)]; const pool = await this._getPool(tokenIn, tokenOut, fee); if (isExactInput) { await this._pay(tokenIn, Buffer.from(data.payer, 'hex'), pool.address, amountToPay.toString()); } else { if (Path.hasMultiplePools(Buffer.from(data.path, 'hex'))) { data.path = Path.skipToken(Buffer.from(data.path, 'hex')).toString('hex'); await this._exactOutputInternal(amountToPay.toString(), pool.address, '0', data); } else { this._amountInCached = amountToPay.toString(); await this._pay(tokenOut, Buffer.from(data.payer, 'hex'), pool.address, amountToPay.toString()); } } } async _exactInputInternal(amountIn, _recipient, sqrtPriceLimitX96, data) { let recipient = _recipient; if (recipient.compare(Buffer.alloc(20)) === 0) recipient = this.address; const [tokenIn, tokenOut, fee] = Path.decodeFirstPool(Buffer.from(data.path, 'hex')); const zeroForOne = tokenIn.compare(tokenOut) < 0; const pool = await this._getPool(tokenIn, tokenOut, fee); const [amount0, amount1] = await pool.swap(recipient, zeroForOne, int_1.Int256.from(amountIn).toString(), sqrtPriceLimitX96 === '0' ? (zeroForOne ? int_1.Uint160.from(TickMath.MIN_SQRT_RATIO).add(1).toString() : int_1.Uint160.from(TickMath.MAX_SQRT_RATIO).sub(1).toString()) : sqrtPriceLimitX96, JSON.stringify(data), this._swapCallback.bind(this)); return int_1.Uint256.from(0) .sub(zeroForOne ? amount1 : amount0) .toString(); } async _exactOutputInternal(amountOut, _recipient, sqrtPriceLimitX96, data) { let recipient = _recipient; if (recipient.compare(Buffer.alloc(20)) === 0) recipient = this.address; const [tokenOut, tokenIn, fee] = Path.decodeFirstPool(Buffer.from(data.path, 'hex')); const zeroForOne = tokenIn.compare(tokenOut) < 0; const pool = await this._getPool(tokenIn, tokenOut, fee); const [amount0Delta, amount1Delta] = await pool.swap(recipient, zeroForOne, int_1.Int256.from(0).sub(amountOut).toString(), sqrtPriceLimitX96 === '0' ? (zeroForOne ? int_1.Uint160.from(TickMath.MIN_SQRT_RATIO).add(1).toString() : int_1.Uint160.from(TickMath.MAX_SQRT_RATIO).sub(1).toString()) : sqrtPriceLimitX96, JSON.stringify(data), this._swapCallback.bind(this)); const [amountIn, amountOutReceived] = zeroForOne ? [int_1.Uint256.from(amount0Delta).toString(), int_1.Uint256.from(0).sub(amount1Delta).toString()] : [int_1.Uint256.from(amount1Delta).toString(), int_1.Uint256.from(0).sub(amount0Delta).toString()]; if (sqrtPriceLimitX96 === '0' && amountOutReceived !== amountOut) throw new Error('sqrtPriceLimitX96 and amountOut error'); return amountIn; } async _checkRemainingBalance(path) { let _path = path; const tokenList = new Set(); while (true) { const [tokenIn, tokenOut] = Path.decodeFirstPool(_path); tokenList.add(tokenIn); tokenList.add(tokenOut); if (Path.hasMultiplePools(_path)) { _path = Path.skipToken(_path); } else { break; } } for (const token of tokenList) { const balance = await this.tokenMethod.getAvailableBalance(this.mutableContext.context, this.address, token); if (balance > BigInt(0)) { if (this._checkFeeProtocol()) { await this.tokenMethod.transfer(this.mutableContext.context, this.address, this.feeProtocolPool, token, balance); } else { await this.tokenMethod.lock(this.mutableContext.context, this.address, this.moduleName, token, balance); } } } } _checkFeeProtocol() { return this.feeProtocol > 0 && this.feeProtocolPool && this.feeProtocolPool.length === 20; } _validateFeeProtocol() { if (this._checkFeeProtocol()) { const feeProtocol0 = int_1.Uint8.from(this.feeProtocol).mod(16); const feeProtocol1 = int_1.Uint8.from(this.feeProtocol).shr(4); if (!(int_1.Uint8.from(feeProtocol0).eq(0) || (int_1.Uint8.from(feeProtocol0).gte(4) && int_1.Uint8.from(feeProtocol0).lte(10))) || !(int_1.Uint8.from(feeProtocol1).eq(0) || (int_1.Uint8.from(feeProtocol1).gte(4) && int_1.Uint8.from(feeProtocol1).lte(10)))) { throw new Error('setFeeeProtocol failed'); } } } } exports.SwapRouter = SwapRouter; //# sourceMappingURL=swap_router.js.map