UNPKG

@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
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(); } }