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