(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('bignumber.js')) :
typeof define === 'function' && define.amd ? define(['exports', 'bignumber.js'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Contract = {}, global.BigNumber));
})(this, (function (exports, BigNumber) { 'use strict';
var ExactType;
(function (ExactType) {
ExactType["exactIn"] = "exactIn";
ExactType["exactOut"] = "exactOut";
})(ExactType || (ExactType = {}));
var FuncType;
(function (FuncType) {
FuncType["deployPool"] = "deployPool";
FuncType["addLiq"] = "addLiq";
FuncType["swap"] = "swap";
FuncType["removeLiq"] = "removeLiq";
})(FuncType || (FuncType = {}));
function sortTickParams(_params) {
const params = _params;
if (!params.tick0 || !params.tick1) {
return params;
}
if (params.tick0 < params.tick1) {
return params;
}
else {
const ret = Object.assign(Object.assign({}, params), { tick0: params.tick1, tick1: params.tick0, amount0: params.amount1, amount1: params.amount0 });
return ret;
}
}
function getPairStr(tick0, tick1) {
const params = sortTickParams({ tick0, tick1 });
return `${params.tick0}/${params.tick1}`;
}
function need(condition, message) {
if (!condition) {
throw new Error(message || "server error");
}
}
const invalid_amount = "invalid amount";
const invalid_slippage = "invalid slippage";
function checkGtZero(amount) {
need(bn(amount).gt("0") && bnIsInteger(amount), invalid_amount);
}
function checkGteZero(amount) {
need(bn(amount).gte("0") && bnIsInteger(amount), invalid_amount);
}
function checkSlippage(slippage) {
need(bn(slippage).gte("0"), invalid_slippage);
need(bn(slippage).lte("1000"), invalid_slippage);
need(bnIsInteger(slippage), invalid_slippage);
need(slippage == bn(slippage).toString(), invalid_amount);
}
const bn = BigNumber;
bn.config({
EXPONENTIAL_AT: 1e9,
DECIMAL_PLACES: 0,
ROUNDING_MODE: bn.ROUND_DOWN,
});
function bnIsInteger(value) {
return bn(value).isInteger() && value.toString().indexOf(".") == -1;
}
function _bnCal(items, decimalPlaces) {
const _bn = bn.clone();
_bn.config({
EXPONENTIAL_AT: 1e9,
DECIMAL_PLACES: parseInt(decimalPlaces),
ROUNDING_MODE: bn.ROUND_DOWN,
});
let ret = _bn(items[0]);
need(!_bn(items[0]).isNaN());
need(_bn(items[1]).isNaN());
for (let i = 1; i < items.length; i++) {
const cur = items[i];
const next = items[i + 1];
if (cur == "add") {
need(_bn(next).gte("0"), invalid_amount);
ret = ret.plus(next);
i++;
}
else if (cur == "sub") {
need(_bn(ret).gte(next), invalid_amount);
ret = ret.minus(next);
i++;
}
else if (cur == "mul") {
need(_bn(next).gte("0"), invalid_amount);
ret = ret.times(next);
i++;
}
else if (cur == "div") {
need(_bn(next).gt("0"), invalid_amount);
ret = ret.div(next);
i++;
}
else if (cur == "pow") {
need(_bn(next).gte("0"), invalid_amount);
ret = ret.pow(next);
i++;
}
else if (cur == "sqrt") {
ret = ret.sqrt();
}
else if (!_bn(cur).isNaN()) {
need(_bn(next).isNaN());
}
}
if (decimalPlaces) {
return ret.decimalPlaces(parseInt(decimalPlaces)).toString();
}
else {
return ret.toString();
}
}
function uintCal(items) {
return _bnCal(items, "0");
}
class Brc20 {
constructor(balance, tick) {
this.balance = {};
this.balance = balance;
this.tick = tick;
this._supply = "0";
for (const address in this.balance) {
this._supply = uintCal([this._supply, "add", this.balance[address]]);
}
}
get supply() {
return this._supply;
}
balanceOf(address) {
return this.balance[address] || "0";
}
transfer(from, to, amount) {
this.checkAmount(amount);
this.checkAddress(from, amount);
this.balance[from] = uintCal([this.balance[from], "sub", amount]);
this.balance[to] = uintCal([this.balance[to] || "0", "add", amount]);
this.checkAddress(from);
this.checkAddress(to);
}
mint(address, amount) {
this.checkAmount(amount);
this.balance[address] = uintCal([
this.balance[address] || "0",
"add",
amount,
]);
this._supply = uintCal([this._supply, "add", amount]);
this.checkAddress(address);
}
burn(address, amount) {
this.checkAmount(amount);
this.checkAddress(address, amount);
this.balance[address] = uintCal([
this.balance[address] || "0",
"sub",
amount,
]);
this._supply = uintCal([this._supply, "sub", amount]);
this.checkAddress(address);
}
checkAmount(amount) {
need(bn(amount).gt("0"), "invalid amount: " + this.tick);
}
checkAddress(address, value = "0") {
need(bn(this.balance[address]).gte(value), "insufficient amount: " + this.tick);
}
}
class Assets {
constructor(map) {
this.map = {};
for (const assetType in map) {
for (const tick in map[assetType]) {
const brc20 = new Brc20(map[assetType][tick].balance, map[assetType][tick].tick);
map[assetType][tick] = brc20;
}
}
this.map = map;
}
traverseTick(assetType, cb) {
for (const tick in this.map[assetType]) {
const brc20 = this.map[assetType][tick];
cb(brc20);
}
}
tryCreate(tick) {
for (let assetType in this.map) {
if (!this.map[assetType][tick]) {
this.map[assetType][tick] = new Brc20({}, tick);
}
}
}
isExist(tick) {
return !!this.map["swap"][tick];
}
get(tick, assetType = "swap") {
return this.map[assetType][tick];
}
getBalance(address, tick, assetType = "swap") {
try {
need(!!this.map[assetType][tick]);
return this.map[assetType][tick].balanceOf(address);
}
catch (err) {
return "0";
}
}
getAggregateBalance(address, tick, assetTypes) {
let ret = "0";
assetTypes.forEach((assetType) => {
ret = uintCal([ret, "add", this.getBalance(address, tick, assetType)]);
});
return ret;
}
mint(address, tick, amount, assetType = "swap") {
this.tryCreate(tick);
this.map[assetType][tick].mint(address, amount);
}
burn(address, tick, amount, assetType = "swap") {
this.map[assetType][tick].burn(address, amount);
}
convert(address, tick, amount, fromAssetType, toAssetType) {
this.map[fromAssetType][tick].burn(address, amount);
this.map[toAssetType][tick].mint(address, amount);
}
transfer(tick, from, to, amount, fromAssetType, toAssetType) {
this.map[fromAssetType][tick].burn(from, amount);
this.map[toAssetType][tick].mint(to, amount);
}
swap(address, tickIn, tickOut, amountIn, amountOut, assetType = "swap") {
const pair = getPairStr(tickIn, tickOut);
this.map[assetType][tickIn].transfer(address, pair, amountIn);
this.map[assetType][tickOut].transfer(pair, address, amountOut);
}
dataRefer() {
return this.map;
}
}
const exceeding_slippage = "exceeding slippage";
const duplicate_tick = "duplicate tick";
const insufficient_liquidity = "insufficient liquidity for this trade";
const pool_existed = "pool existed";
const pool_not_found = "pool not found";
const feeRate = "6";
const AssetsClass = Assets;
class Contract {
constructor(assets, status, config) {
this.assets = assets;
this.status = status;
this.config = config;
}
deployPool(params) {
need(params.tick0 !== params.tick1, duplicate_tick);
const pair = getPairStr(params.tick0, params.tick1);
need(!this.assets.isExist(pair), pool_existed);
this.assets.tryCreate(pair);
return {};
}
addLiq(params) {
const { tick0, tick1, amount0, amount1, expect, slippage1000 } = sortTickParams(params);
checkGtZero(amount0);
checkGtZero(amount1);
checkGteZero(expect);
checkSlippage(slippage1000);
const pair = getPairStr(tick0, tick1);
const { address } = params;
need(!!this.assets.isExist(pair), pool_not_found);
this.mintFee({
tick0,
tick1,
});
if (this.assets.get(pair).supply == "0") {
const lp = uintCal([amount0, "mul", amount1, "sqrt"]);
const firstLP = uintCal([lp, "sub", "1000"]);
this.assets.get(pair).mint(address, firstLP);
this.assets.get(pair).mint("0", "1000");
this.assets.get(tick0).transfer(address, pair, amount0);
this.assets.get(tick1).transfer(address, pair, amount1);
checkGtZero(firstLP);
need(bn(firstLP).gte(uintCal([
expect,
"mul",
uintCal(["1000", "sub", slippage1000]),
"div",
"1000",
])), exceeding_slippage);
{
this.status.kLast[pair] = uintCal([
this.assets.get(tick0).balanceOf(pair),
"mul",
this.assets.get(tick1).balanceOf(pair),
]);
}
return { lp: firstLP, amount0, amount1 };
}
else {
let amount0Adjust;
let amount1Adjust;
const poolLp = this.assets.get(pair).supply;
const poolAmount0 = this.assets.get(tick0).balanceOf(pair);
const poolAmount1 = this.assets.get(tick1).balanceOf(pair);
amount1Adjust = uintCal([
amount0,
"mul",
poolAmount1,
"div",
poolAmount0,
]);
if (bn(amount1Adjust).lte(amount1)) {
amount0Adjust = amount0;
}
else {
amount0Adjust = uintCal([
amount1,
"mul",
poolAmount0,
"div",
poolAmount1,
]);
amount1Adjust = amount1;
}
const lp0 = uintCal([amount0Adjust, "mul", poolLp, "div", poolAmount0]);
const lp1 = uintCal([amount1Adjust, "mul", poolLp, "div", poolAmount1]);
const lp = bn(lp0).lt(lp1) ? lp0 : lp1;
this.assets.get(pair).mint(address, lp);
this.assets.get(tick0).transfer(address, pair, amount0Adjust);
this.assets.get(tick1).transfer(address, pair, amount1Adjust);
checkGtZero(lp);
need(bn(lp).gte(uintCal([
expect,
"mul",
uintCal(["1000", "sub", slippage1000]),
"div",
"1000",
])), exceeding_slippage);
need(amount1Adjust == amount1 || amount0Adjust == amount0);
{
this.status.kLast[pair] = uintCal([
this.assets.get(tick0).balanceOf(pair),
"mul",
this.assets.get(tick1).balanceOf(pair),
]);
}
return { lp, amount0: amount0Adjust, amount1: amount1Adjust };
}
}
removeLiq(params) {
const { address, lp, tick0, tick1, amount0, amount1, slippage1000 } = sortTickParams(params);
checkGtZero(lp);
checkGteZero(amount0);
checkGteZero(amount1);
checkSlippage(slippage1000);
this.mintFee({
tick0,
tick1,
});
const pair = getPairStr(tick0, tick1);
need(!!this.assets.isExist(pair), pool_not_found);
const poolLp = this.assets.get(pair).supply;
const reserve0 = this.assets.get(tick0).balanceOf(pair);
const reserve1 = this.assets.get(tick1).balanceOf(pair);
const acquire0 = uintCal([lp, "mul", reserve0, "div", poolLp]);
const acquire1 = uintCal([lp, "mul", reserve1, "div", poolLp]);
this.assets.get(pair).burn(address, lp);
this.assets.get(tick0).transfer(pair, address, acquire0);
this.assets.get(tick1).transfer(pair, address, acquire1);
need(bn(acquire0).gte(uintCal([
amount0,
"mul",
uintCal(["1000", "sub", slippage1000]),
"div",
"1000",
])), exceeding_slippage);
need(bn(acquire1).gte(uintCal([
amount1,
"mul",
uintCal(["1000", "sub", slippage1000]),
"div",
"1000",
])), exceeding_slippage);
{
this.status.kLast[pair] = uintCal([
this.assets.get(tick0).balanceOf(pair),
"mul",
this.assets.get(tick1).balanceOf(pair),
]);
}
return { tick0, tick1, amount0: acquire0, amount1: acquire1 };
}
swap(params) {
const { tickIn, tickOut, address, exactType, expect, slippage1000, amount, } = params;
checkGtZero(amount);
checkGteZero(expect);
checkSlippage(slippage1000);
const pair = getPairStr(tickIn, tickOut);
const reserveIn = this.assets.get(tickIn).balanceOf(pair);
const reserveOut = this.assets.get(tickOut).balanceOf(pair);
let amountIn;
let amountOut;
let ret;
if (exactType == ExactType.exactIn) {
amountIn = amount;
amountOut = this.getAmountOut({
amountIn,
reserveIn,
reserveOut,
});
const amountOutMin = uintCal([
expect,
"mul",
uintCal(["1000", "div", uintCal(["1000", "add", slippage1000])]),
]);
need(bn(amountOut).gte(amountOutMin), exceeding_slippage);
ret = amountOut;
}
else {
amountOut = amount;
amountIn = this.getAmountIn({
amountOut,
reserveIn,
reserveOut,
});
const amountInMax = uintCal([
expect,
"mul",
uintCal(["1000", "add", slippage1000]),
"div",
"1000",
]);
need(bn(amountIn).lte(amountInMax), exceeding_slippage);
ret = amountIn;
}
this.assets.swap(address, tickIn, tickOut, amountIn, amountOut);
return { amount: ret };
}
getAmountOut(params) {
const { amountIn, reserveIn, reserveOut } = params;
checkGtZero(amountIn);
need(bn(reserveIn).gt("0") && bn(reserveOut).gt("0"), insufficient_liquidity);
const amountInWithFee = uintCal([
amountIn,
"mul",
uintCal(["1000", "sub", this.config.swapFeeRate1000]),
]);
const numerator = uintCal([amountInWithFee, "mul", reserveOut]);
const denominator = uintCal([
reserveIn,
"mul",
"1000",
"add",
amountInWithFee,
]);
return uintCal([numerator, "div", denominator]);
}
getAmountIn(params) {
const { amountOut, reserveIn, reserveOut } = params;
checkGtZero(amountOut);
need(bn(reserveIn).gt("0") && bn(reserveOut).gt("0"), insufficient_liquidity);
const numerator = uintCal([reserveIn, "mul", amountOut, "mul", "1000"]);
const denominator = uintCal([
reserveOut,
"sub",
amountOut,
"mul",
uintCal(["1000", "sub", this.config.swapFeeRate1000]),
]);
return uintCal([numerator, "div", denominator, "add", "1"]);
}
send(params) {
const { from, to, tick, amount } = params;
checkGtZero(amount);
this.assets.get(tick).transfer(from, to, amount);
return {};
}
getFeeLp(params) {
const { tick0, tick1 } = params;
const pair = getPairStr(tick0, tick1);
const reserve0 = this.assets.get(tick0).balanceOf(pair);
const reserve1 = this.assets.get(tick1).balanceOf(pair);
{
if (bn(this.status.kLast[pair]).gt("0")) {
const rootK = uintCal([reserve0, "mul", reserve1, "sqrt"]);
const rootKLast = uintCal([this.status.kLast[pair], "sqrt"]);
if (bn(rootK).gt(rootKLast)) {
const numerator = uintCal([
this.assets.get(pair).supply,
"mul",
uintCal([rootK, "sub", rootKLast]),
]);
const scale = uintCal([feeRate, "sub", "1"]);
const denominator = uintCal([rootK, "mul", scale, "add", rootKLast]);
const liquidity = uintCal([numerator, "div", denominator]);
return liquidity;
}
}
}
return "0";
}
mintFee(params) {
const { tick0, tick1 } = params;
const pair = getPairStr(tick0, tick1);
{
const liquidity = this.getFeeLp(params);
if (bn(liquidity).gt("0")) {
this.assets.get(pair).mint(this.config.feeTo, liquidity);
}
}
}
}
exports.AssetsClass = AssetsClass;
exports.Contract = Contract;
exports.duplicate_tick = duplicate_tick;
exports.exceeding_slippage = exceeding_slippage;
exports.insufficient_liquidity = insufficient_liquidity;
exports.pool_existed = pool_existed;
exports.pool_not_found = pool_not_found;
}));