@subwallet/invariant-vara-sdk
Version:
<div align="center"> <h1>⚡Invariant protocol⚡</h1> <p> <a href="https://invariant.app/math-spec-vara.pdf">MATH SPEC 📄</a> | <a href="https://discord.gg/VzS3C9wR">DISCORD 🌐</a> | </p> </div>
381 lines (380 loc) • 20.4 kB
JavaScript
import { getWasm, integerSafeCast, unwrapResult, convertTick, convertPool, convertFeeTier, convertPosition, convertPoolKey, TransactionWrapper, convertCalculateSwapResult, decodeEvent, calculateSqrtPriceAfterSlippage, convertQuoteResult, getMaxSqrtPrice, getMinSqrtPrice, calculateTick, convertLiquidityTick, convertPositionTick, convertPositions, positionToTick, validateInvariantPairDeposit, validateInvariantPairWithdraw, validateInvariantSingleDeposit, validateInvariantSingleWithdraw, validateInvariantVaraWithdraw, validateInvariantVaraDeposit } from './utils.js';
import { CHUNK_SIZE, DEFAULT_ADDRESS, INVARIANT_GAS_LIMIT, LIQUIDITY_TICKS_LIMIT, MAX_POOL_KEYS_RETURNED, POSITIONS_ENTRIES_LIMIT } from './consts.js';
import { InvariantContract } from './invariant-contract.js';
import { InvariantEvent } from './schema.js';
import { getServiceNamePrefix, ZERO_ADDRESS, getFnNamePrefix } from 'sails-js';
export class Invariant {
contract;
gasLimit;
eventListenerStarted = false;
eventListeners = {};
constructor(contract, gasLimit) {
this.contract = contract;
this.gasLimit = gasLimit;
}
static async deploy(api, deployer, protocolFee, gasLimit = INVARIANT_GAS_LIMIT) {
const code = await getWasm('invariant');
const invariant = new InvariantContract(api);
const deployTx = await invariant
.newCtorFromCode(code, {
admin: deployer.addressRaw,
protocolFee
})
.withAccount(deployer)
.withGas(gasLimit);
{
const { response } = await deployTx.signAndSend();
response();
}
return new Invariant(invariant, gasLimit);
}
static async load(api, programId, gasLimit = INVARIANT_GAS_LIMIT) {
const invariant = new InvariantContract(api, programId);
return new Invariant(invariant, gasLimit);
}
programId() {
const id = this.contract.programId;
if (id === undefined || id === null) {
throw new Error('Program id is not set');
}
return id;
}
on(callback) {
if (!this.eventListenerStarted) {
this.listen();
}
this.eventListeners[callback.ident] = this.eventListeners[callback.ident] || [];
this.eventListeners[callback.ident]?.push(callback.callback);
}
listen() {
this.eventListenerStarted = true;
this.contract.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => {
if (!message.source.eq(this.contract.programId) || !message.destination.eq(ZERO_ADDRESS)) {
return;
}
const payload = message.payload.toHex();
if (getServiceNamePrefix(payload) === 'Service') {
const prefix = getFnNamePrefix(payload);
if (Object.values(InvariantEvent).includes(prefix)) {
const event = decodeEvent(this.contract.registry, payload, prefix);
const callbacks = this.eventListeners[prefix];
callbacks?.map(callback => callback(event));
}
}
});
}
off(callback) {
this.eventListeners[callback.ident] = this.eventListeners[callback.ident]?.filter(eventListener => {
if (callback.callback) {
return !(callback.callback === eventListener);
}
else {
return false;
}
});
}
async feeTierExists(feeTier) {
return this.contract.service.feeTierExists(feeTier, DEFAULT_ADDRESS);
}
async getFeeTiers() {
return (await this.contract.service.getFeeTiers(DEFAULT_ADDRESS)).map(convertFeeTier);
}
async getPool(token0, token1, feeTier) {
return convertPool(unwrapResult(await this.contract.service.getPool(token0, token1, feeTier, DEFAULT_ADDRESS)));
}
async getPoolKeys(size, offset) {
const response = await this.contract.service.getPoolKeys(size, offset, DEFAULT_ADDRESS);
return [response[0].map(convertPoolKey), BigInt(response[1])];
}
async getAllPoolKeys() {
const [poolKeys, poolKeysCount] = await this.getPoolKeys(MAX_POOL_KEYS_RETURNED, 0n);
const promises = [];
for (let i = 1; i < Math.ceil(Number(poolKeysCount) / Number(MAX_POOL_KEYS_RETURNED)); i++) {
promises.push(this.getPoolKeys(MAX_POOL_KEYS_RETURNED, BigInt(i) * MAX_POOL_KEYS_RETURNED));
}
const poolKeysEntries = await Promise.all(promises);
return [...poolKeys, ...poolKeysEntries.map(([poolKeys]) => poolKeys).flat(1)];
}
async getAllPoolsForPair(token0, token1) {
return unwrapResult(await this.contract.service.getAllPoolsForPair(token0, token1, DEFAULT_ADDRESS)).map((entry) => [convertFeeTier(entry[0]), convertPool(entry[1])]);
}
async getPosition(ownerId, index) {
return convertPosition(unwrapResult(await this.contract.service.getPosition(ownerId, index, DEFAULT_ADDRESS)));
}
async getAllPositions(owner, positionsCount, skipPages, positionsPerPage) {
const firstPageIndex = skipPages?.find(i => !skipPages.includes(i)) || 0;
const positionsPerPageLimit = positionsPerPage || POSITIONS_ENTRIES_LIMIT;
let pages = [];
let actualPositionsCount = positionsCount;
if (!positionsCount) {
const [positionEntries, positionsCount] = await this.getPositions(owner, positionsPerPageLimit, BigInt(firstPageIndex) * positionsPerPageLimit);
pages.push({ index: 0, entries: positionEntries });
actualPositionsCount = positionsCount;
}
const promises = [];
const pageIndexes = [];
for (let i = positionsCount ? firstPageIndex : firstPageIndex + 1; i < Math.ceil(Number(actualPositionsCount) / Number(positionsPerPageLimit)); i++) {
if (skipPages?.includes(i)) {
continue;
}
pageIndexes.push(i);
promises.push(this.getPositions(owner, positionsPerPageLimit, BigInt(i) * positionsPerPageLimit));
}
const positionsEntriesList = await Promise.all(promises);
pages = [
...pages,
...positionsEntriesList.map(([positionsEntries], index) => {
return { index: pageIndexes[index], entries: positionsEntries };
})
];
return pages;
}
async getPositionWithAssociates(owner, index) {
const result = unwrapResult(await this.contract.service.getPositionWithAssociates(owner, index, DEFAULT_ADDRESS));
const position = convertPosition(result[0]);
const pool = convertPool(result[1]);
const lowerTick = convertTick(result[2]);
const upperTick = convertTick(result[3]);
return [position, pool, lowerTick, upperTick];
}
async getPositions(ownerId, size, offset) {
const response = unwrapResult(await this.contract.service.getPositions(ownerId, size, offset, DEFAULT_ADDRESS));
const convertedList = convertPositions(response[0]);
const flattenedList = convertedList.map(([pool, positionsWithIndexes]) => {
return positionsWithIndexes.map(position => {
return [position, pool];
});
}).flat(1);
const listSortedByIndex = flattenedList.sort((a, b) => {
return a[0][1] - b[0][1];
});
const positionAndPoolList = listSortedByIndex.map(v => [v[0][0], v[1]]);
return [positionAndPoolList, BigInt(response[1])];
}
async _getAllPositions(ownerId) {
return (await this.contract.service.getAllPositions(ownerId, DEFAULT_ADDRESS)).map(val => convertPosition(val));
}
async getProtocolFee() {
return BigInt((await this.contract.service.getProtocolFee(DEFAULT_ADDRESS)));
}
async getTick(key, index) {
return convertTick(unwrapResult(await this.contract.service.getTick(key, integerSafeCast(index), DEFAULT_ADDRESS)));
}
async getTickmap(key) {
return {
bitmap: new Map((await this.contract.service.getTickmap(key, DEFAULT_ADDRESS)).map(val => [
BigInt(val[0]),
BigInt(val[1])
]))
};
}
async getLiquidityTicks(key, tickmap) {
return unwrapResult(await this.contract.service.getLiquidityTicks(key, tickmap, DEFAULT_ADDRESS)).map(convertLiquidityTick);
}
async getLiquidityTicksAmount(key) {
return BigInt(await this.contract.service.getLiquidityTicksAmount(key, DEFAULT_ADDRESS));
}
async getAllLiquidityTicks(key, tickmap) {
const tickIndexes = [];
for (const [chunkIndex, chunk] of tickmap.bitmap.entries()) {
for (let bit = 0n; bit < CHUNK_SIZE; bit++) {
const checkedBit = chunk & (1n << bit);
if (checkedBit) {
const tickIndex = positionToTick(chunkIndex, bit, key.feeTier.tickSpacing);
tickIndexes.push(tickIndex);
}
}
}
const tickLimit = integerSafeCast(LIQUIDITY_TICKS_LIMIT);
const promises = [];
for (let i = 0; i < tickIndexes.length; i += tickLimit) {
promises.push(this.getLiquidityTicks(key, tickIndexes.slice(i, i + tickLimit)));
}
const tickResults = await Promise.all(promises);
return tickResults.flat(1);
}
async isTickInitialized(key, index) {
return this.contract.service.isTickInitialized(key, integerSafeCast(index), DEFAULT_ADDRESS);
}
async getUserBalances(user) {
const result = (await this.contract.service.getUserBalances(user, DEFAULT_ADDRESS)).map(arr => {
arr[1] = BigInt(arr[1]);
return arr;
});
return new Map(result);
}
async getUserPositionAmount(key, owner) {
return BigInt(await this.contract.service.getUserPositionAmount(key, owner, DEFAULT_ADDRESS));
}
async getPositionTicks(owner, offset) {
return (await this.contract.service.getPositionTicks(owner, offset, DEFAULT_ADDRESS)).map(tick => convertPositionTick(tick));
}
async quote(poolKey, xToY, amount, byAmountIn) {
const sqrtPriceLimit = xToY
? getMinSqrtPrice(poolKey.feeTier.tickSpacing)
: getMaxSqrtPrice(poolKey.feeTier.tickSpacing);
return convertQuoteResult(unwrapResult(await this.contract.service.quote(poolKey, xToY, amount, byAmountIn, sqrtPriceLimit, DEFAULT_ADDRESS)));
}
async changeProtocolFeeTx(fee, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.changeProtocolFee(fee).withGas(gasLimit));
}
async changeProtocolFee(signer, fee, gasLimit = this.gasLimit) {
const tx = (await this.changeProtocolFeeTx(fee, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async addFeeTierTx(feeTier, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.addFeeTier(feeTier).withGas(gasLimit)).withDecode(convertFeeTier);
}
async addFeeTier(signer, feeTier, gasLimit = this.gasLimit) {
const tx = (await this.addFeeTierTx(feeTier, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async changeFeeReceiverTx(poolKey, feeReceiver, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service
.changeFeeReceiver(poolKey, feeReceiver)
.withGas(gasLimit));
}
async changeFeeReceiver(signer, poolKey, feeReceiver, gasLimit = this.gasLimit) {
const tx = (await this.changeFeeReceiverTx(poolKey, feeReceiver, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async claimFeeTx(index, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.claimFee(index).withGas(gasLimit)).withDecode(arr => arr.map(BigInt));
}
async claimFee(signer, index, gasLimit = this.gasLimit) {
const tx = (await this.claimFeeTx(index, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async createPoolTx(key, initSqrtPrice, gasLimit = this.gasLimit) {
const initTick = calculateTick(initSqrtPrice, key.feeTier.tickSpacing);
return new TransactionWrapper(await this.contract.service
.createPool(key.tokenX, key.tokenY, key.feeTier, initSqrtPrice, initTick)
.withGas(gasLimit));
}
async createPool(signer, key, initSqrtPrice, gasLimit = this.gasLimit) {
const tx = (await this.createPoolTx(key, initSqrtPrice, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async createPositionTx(key, lowerTick, upperTick, liquidityDelta, spotSqrtPrice, slippageTolerance, gasLimit = this.gasLimit) {
const slippageLimitLower = calculateSqrtPriceAfterSlippage(spotSqrtPrice, slippageTolerance, false);
const slippageLimitUpper = calculateSqrtPriceAfterSlippage(spotSqrtPrice, slippageTolerance, true);
return new TransactionWrapper(await this.contract.service
.createPosition(key, lowerTick, upperTick, liquidityDelta, slippageLimitLower, slippageLimitUpper)
.withGas(gasLimit)).withDecode(convertPosition);
}
async createPosition(signer, key, lowerTick, upperTick, liquidityDelta, spotSqrtPrice, slippageTolerance, gasLimit = this.gasLimit) {
const tx = (await this.createPositionTx(key, lowerTick, upperTick, liquidityDelta, spotSqrtPrice, slippageTolerance, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async depositSingleTokenTx(token, amount, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.depositSingleToken(token, amount).withGas(gasLimit)).withValidate(validateInvariantSingleDeposit);
}
async depositSingleToken(signer, token, amount, gasLimit = this.gasLimit) {
const tx = (await this.depositSingleTokenTx(token, amount, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async depositTokenPairTx(tokenX, tokenY, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.depositTokenPair(tokenX, tokenY).withGas(gasLimit))
.withDecode(arr => arr.map(BigInt))
.withValidate(validateInvariantPairDeposit);
}
async depositTokenPair(signer, tokenX, tokenY, gasLimit = this.gasLimit) {
const tx = (await this.depositTokenPairTx(tokenX, tokenY, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async depositVaraTx(amount, gasLimit = this.gasLimit) {
if (amount < this.contract.api.existentialDeposit.toBigInt()) {
throw new Error('Value is less than existential deposit');
}
let tx = await this.contract.service.depositVara().withGas(gasLimit);
tx = await tx.withValue(amount);
return new TransactionWrapper(tx).withValidate(validateInvariantVaraDeposit);
}
async depositVara(account, amount, gasLimit = this.gasLimit) {
const tx = (await this.depositVaraTx(amount, gasLimit)).withAccount(account);
return tx.signAndSend();
}
async removeFeeTierTx(feeTier, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.removeFeeTier(feeTier).withGas(gasLimit)).withDecode(convertFeeTier);
}
async removeFeeTier(signer, feeTier, gasLimit = this.gasLimit) {
const tx = (await this.removeFeeTierTx(feeTier, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async removePositionTx(index, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.removePosition(index).withGas(gasLimit)).withDecode(arr => arr.map(BigInt));
}
async removePosition(signer, index, gasLimit = this.gasLimit) {
const tx = (await this.removePositionTx(index, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async transferPositionTx(index, receiver, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.transferPosition(index, receiver).withGas(gasLimit));
}
async transferPosition(signer, index, receiver, gasLimit = this.gasLimit) {
const tx = (await this.transferPositionTx(index, receiver, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async swapTx(poolKey, xToY, amount, byAmountIn, sqrtPriceLimit, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service
.swap(poolKey, xToY, amount, byAmountIn, sqrtPriceLimit)
.withGas(gasLimit)).withDecode(convertCalculateSwapResult);
}
async swap(signer, poolKey, xToY, amount, byAmountIn, sqrtPriceLimit, gasLimit = this.gasLimit) {
const tx = (await this.swapTx(poolKey, xToY, amount, byAmountIn, sqrtPriceLimit, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async swapWithSlippageTx(poolKey, xToY, amount, byAmountIn, estimatedSqrtPrice, slippage, gasLimit = this.gasLimit) {
const sqrtPriceAfterSlippage = calculateSqrtPriceAfterSlippage(estimatedSqrtPrice, slippage, !xToY);
return new TransactionWrapper(await this.contract.service
.swap(poolKey, xToY, amount, byAmountIn, xToY ? sqrtPriceAfterSlippage - 1n : (sqrtPriceAfterSlippage + 1n))
.withGas(gasLimit)).withDecode(convertCalculateSwapResult);
}
async swapWithSlippage(signer, poolKey, xToY, amount, byAmountIn, estimatedSqrtPrice, slippage, gasLimit = this.gasLimit) {
const tx = (await this.swapWithSlippageTx(poolKey, xToY, amount, byAmountIn, estimatedSqrtPrice, slippage, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async swapRouteTx(amountIn, expectedAmountOut, slippage, swaps, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service
.swapRoute(amountIn, expectedAmountOut, slippage, swaps)
.withGas(gasLimit));
}
async swapRoute(signer, amountIn, expectedAmountOut, slippage, swaps, gasLimit = this.gasLimit) {
const tx = (await this.swapRouteTx(amountIn, expectedAmountOut, slippage, swaps, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async withdrawProtocolFeeTx(poolKey, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.withdrawProtocolFee(poolKey).withGas(gasLimit));
}
async withdrawProtocolFee(signer, poolKey, gasLimit = this.gasLimit) {
const tx = (await this.withdrawProtocolFeeTx(poolKey, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async withdrawSingleTokenTx(token, amount = null, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.withdrawSingleToken(token, amount).withGas(gasLimit)).withValidate(validateInvariantSingleWithdraw);
}
async withdrawSingleToken(signer, token, amount = null, gasLimit = this.gasLimit) {
const tx = (await this.withdrawSingleTokenTx(token, amount, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
async withdrawVaraTx(amount, gasLimit = this.gasLimit) {
if (amount && amount < this.contract.api.existentialDeposit.toBigInt()) {
throw new Error('Value is less than existential deposit');
}
const tx = await this.contract.service.withdrawVara(amount).withGas(gasLimit);
return new TransactionWrapper(tx).withValidate(validateInvariantVaraWithdraw);
}
async withdrawVara(account, amount, gasLimit = this.gasLimit) {
const tx = (await this.withdrawVaraTx(amount, gasLimit)).withAccount(account);
return tx.signAndSend();
}
async withdrawTokenPairTx(tokenX, tokenY, gasLimit = this.gasLimit) {
return new TransactionWrapper(await this.contract.service.withdrawTokenPair(tokenX, tokenY).withGas(gasLimit))
.withDecode(arr => arr.map(BigInt))
.withValidate(validateInvariantPairWithdraw);
}
async withdrawTokenPair(signer, tokenX, tokenY, gasLimit = this.gasLimit) {
const tx = (await this.withdrawTokenPairTx(tokenX, tokenY, gasLimit)).withAccount(signer);
return tx.signAndSend();
}
}