@firefly-exchange/library-sui
Version:
Sui library housing helper methods, classes to interact with Bluefin protocol(s) deployed on Sui
1,081 lines (1,080 loc) • 70 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OnChainCalls = void 0;
const bcs_1 = require("@mysten/sui/bcs");
const utils_1 = require("@mysten/sui/utils");
const classes_1 = require("../classes");
const library_1 = require("../library");
const types_1 = require("../types");
const utils_2 = require("../utils");
const clmm_1 = require("./clmm");
const swap_1 = require("./clmm/swap");
const types_2 = require("./types");
const utils_3 = require("./utils");
const constants_1 = require("../constants");
const query_chain_1 = require("./query-chain");
class OnChainCalls {
constructor(_suiClient, _config, options) {
this.executeZkTransaction = async ({ tx, caller, options }) => {
tx.setSender(this.signerConfig.address);
const { bytes, signature: userSignature } = await tx.sign({
client: this.suiClient,
signer: caller
});
const zkSignature = (0, utils_2.createZkSignature)({
userSignature,
zkPayload: this.signerConfig.zkPayload
});
if (this.fastModeEnabled) {
await this.appendTipToTransaction(tx, options?.shioFastModeCustomRange);
}
return this.suiClient.executeTransactionBlock({
transactionBlock: bytes,
signature: zkSignature,
options: {
showObjectChanges: true,
showEffects: true,
showEvents: true,
showInput: true
}
});
};
this.suiClient = _suiClient;
this.config = _config;
this.queryChain = new query_chain_1.QueryChain(this.suiClient);
this.fastModeEnabled = options?.fastModeEnabled == true;
this.signerConfig = {
signer: options?.signer,
address: options?.address || options?.signer?.toSuiAddress(),
isUIWallet: options?.isUIWallet == true,
isZkLogin: options?.isZkLogin == true,
zkPayload: options?.zkPayload
};
}
/**
* Signs and executes the given transaction block
* @param txb Sui transaction block
* @param options IOnChainCallOptionalParams
* @returns Sui Transaction Block Response
*/
async signAndExecuteTxb(txb, options) {
if (this.fastModeEnabled) {
await this.appendTipToTransaction(txb, options?.shioFastModeCustomRange);
}
const signedBlock = await classes_1.SuiBlocks.buildAndSignTxBlock(txb, this.suiClient, this.signerConfig.signer, this.signerConfig.isUIWallet);
return classes_1.SuiBlocks.executeSignedTxBlock(signedBlock, this.suiClient);
}
/**
* Signs the given transaction
* @param txb Sui transaction block
* @returns Sui Transaction Block Response
*/
async signTransaction(txb) {
return classes_1.SuiBlocks.signTxBlock(txb, this.signerConfig.signer, this.signerConfig.isUIWallet);
}
/**
* Signs and executes the given transaction block
* @param txb Sui transaction block
* @returns Sui Transaction Block Response
*/
async dryRunTxb(txb) {
const builtBlock = (await classes_1.SuiBlocks.buildTxBlock(txb, this.suiClient, this.signerConfig.address, false));
return this.suiClient.dryRunTransactionBlock({
transactionBlock: builtBlock
});
}
/**
* Handles call execution
* @param txb The transaction block
* @param options IOnChainCallOptionalParams
* @returns OnChainCallResponse
*/
async handleReturn(txb, options) {
if (options?.returnTx)
return txb;
if (this.signerConfig.isZkLogin) {
return options?.dryRun == true
? await this.dryRunTxb(txb)
: this.executeZkTransaction({
caller: this.signerConfig.signer,
tx: txb,
options
});
}
else {
return options?.dryRun == true
? await this.dryRunTxb(txb)
: options?.sign
? this.signTransaction(txb)
: await this.signAndExecuteTxb(txb, options);
}
}
/**
* Allows admin to set pool creation fee
* @param coinType The fee coin type
* @param amount The amount of fee to be paid
* @param options: OnChain params call
* Returns OnChainCallResponse
*/
async setPoolCreationFee(coinType, amount, options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(this.config.AdminCap),
txb.object(this.config.GlobalConfig),
txb.pure.u64((0, library_1.bigNumber)(amount).toFixed(0))
],
target: `${this.config.CurrentPackage}::admin::set_pool_creation_fee`,
typeArguments: [coinType]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to create a pool for provided coins
* @param coinA the type of coin A
* @param coinB the type of coin B
* @param poolName the name of pool
* @param tickSpacing tick spacing
* @param feeBps The fee rate of the pool in bps
* @param price the starting price of the pool
* @param feeCoinType The coin type to be used for payment of pool creation
* @param options: OnChain params call
* Returns OnChainCallResponse
*/
async createPool(coinA, coinB, poolName, tickSpacing, feeBps, price, feeCoinType, options) {
const txb = options?.txb || new types_1.TransactionBlock();
const { supported, amount } = await this.getPoolCreationFeeInfoForCoin(feeCoinType);
if (!supported)
throw `Provided Fee coin type: ${feeCoinType} is not supported for payment of pool creation`;
const [splitCoin, mergeCoin] = await classes_1.CoinUtils.createCoinWithBalance(this.suiClient, txb, amount, feeCoinType, this.signerConfig.address);
const sqrtPriceX64 = clmm_1.TickMath.priceToSqrtPriceX64((0, clmm_1.d)((0, library_1.bigNumber)(price).toString()), coinA.decimals, coinB.decimals);
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(this.config.GlobalConfig),
txb.pure.string(poolName),
txb.pure.string(options?.iconURl || ""),
txb.pure.string(coinA.symbol),
txb.pure.u8(coinA.decimals),
txb.pure.string(coinA.url || ""),
txb.pure.string(coinB.symbol),
txb.pure.u8(coinB.decimals),
txb.pure.string(coinB.url || ""),
txb.pure.u32(tickSpacing),
txb.pure.u64((0, library_1.bigNumber)(feeBps).multipliedBy(100).toFixed(0)),
txb.pure.u128(sqrtPriceX64.toString()),
txb.object(splitCoin)
],
target: `${this.config.CurrentPackage}::gateway::create_pool_v2`,
typeArguments: [coinA.type, coinB.type, feeCoinType]
});
// transfer any lingering merge coin back to sender
if (mergeCoin) {
txb.transferObjects([mergeCoin], this.signerConfig.address);
}
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to open a position for the provided pool for given ticks
* @param pool Name or the pool or the pool for which to open the position
* @param lowerTick signed lower tick number
* @param upperTick signed upper tick number
* @param options IOnChainCallOptionalParams & { owner: optional address of the position's owner }
*/
async openPosition(pool, lowerTick, upperTick, options) {
const txBlock = options?.txb || new types_1.TransactionBlock();
const owner = options?.owner || this.signerConfig.address;
const { position, txb } = this._openPositionInternal(pool, lowerTick, upperTick, {
txb: txBlock
});
txb.transferObjects([position], owner);
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to provide liquidity to the pool
* @param pool Name or the pool itself to which liquidity is to be provided
* @param position The ID fo the position for which liquidity is being provided
* @param liquidity The amount of liquidity to provide
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async provideLiquidity(pool, position, params, options) {
const txb = await this._provideLiquidityInternal(pool, position, params, options);
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to provide liquidty to the pool by fixed amount of coins
* @param pool Name or the pool itself to which liquidity is to be provided
* @param position The ID fo the position for which liquidity is being provided
* @param amount The amount of coins to be provided
* @param isFixedA True if the amount being provided is coin A
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async provideLiquidityWithFixedAmount(pool, position, liquidityInput, options) {
const txb = await this._provideLiquidityFixedAmountInternal(pool, position, liquidityInput, options);
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to remove liquidity from the pool for provided position
* @param pool Name or the pool itself from which liquidity will be removed
* @param position The ID of the position for which liquidity is being removed from
* @param params: LiquidityInput
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async removeLiquidity(pool, position, params, options) {
const txb = await this._removeLiquidityInternal(pool, position, params, options);
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to open a position and provide liquidity to the pool in a single Tx
* @param pool Name or the pool or the pool for which to open the position
* @param params Liquidity params
* @param options: IOnChainCallOptionalParams
*/
async openPositionWithLiquidity(pool, params, options) {
let txb = options?.txb || new types_1.TransactionBlock();
const result = this._openPositionInternal(pool, params.lowerTick, params.upperTick, { ...options, txb });
txb = result.txb;
const position = result.position;
if (params.coinA && params.coinB) {
txb = await this._provideLiquidityInternalWithInputCoins(pool, position, params, {
...options,
txb
});
}
else {
txb = await this._provideLiquidityInternal(pool, position, params, {
...options,
txb
});
}
txb.transferObjects([position], this.signerConfig.address);
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to open a position and provide liquidity to the pool by fixed amount
* @param pool Name or the pool or the pool for which to open the position
* @param lowerTick The lower tick unsigned bits
* @param upperTick The upper tick unsigned bits
* @param liquidity The amount of liquidity to be provided
* @param options: IOnChainCallOptionalParams
*/
async openPositionWithFixedAmount(pool, lowerTick, upperTick, params, options) {
let txb = options?.txb || new types_1.TransactionBlock();
const result = this._openPositionInternal(pool, lowerTick, upperTick, {
...options,
txb
});
txb = result.txb;
const position = result.position;
txb = await this._provideLiquidityFixedAmountInternal(pool, position, params, {
...options,
txb
});
txb.transferObjects([position], this.signerConfig.address);
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to fully close the provided position. Any residual liquidity is withdrawn
* @param pool The pool for which the position is providing liquidity
* @param position The ID fo the position to be closed
* @param options IOnChainCallOptionalParams & { transferCoinsTo: optional address to which removed liquidity will be sent to }
* Returns OnChainCallResponse
*/
async closePosition(pool, position, options) {
let txb = options?.txb || new types_1.TransactionBlock();
const transferCoinsTo = options?.transferCoinsTo || this.signerConfig.address;
txb = await this._collectRewardInternal(pool, position, {
txb: txb
});
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(this.config.GlobalConfig),
txb.object(pool.id),
txb.object(position),
txb.pure.address(transferCoinsTo)
],
target: `${this.config.CurrentPackage}::gateway::close_position`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows pool manager or rewards manager to initialize rewards in pool
* @param pool The pool in which rewards are to be initialized
* @param startTime start time in seconds for the rewards (must be in future).
* @param activeForSeconds seconds for which the rewards are to remain active.
* @param rewardCoinType reward coin type (eg. 0x81650ac0868edf55349fa41d4323db5b8a4827bab3b672c16c14c7135abcc3df::usdc::USDC)
* @param rewardAmount reward coin amount which is to be assigned (caller must have coins of specified reward type)
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async addRewardCoinInPool(params, options) {
const txb = options?.txb || new types_1.TransactionBlock();
const [splitCoin, mergeCoin] = await classes_1.CoinUtils.createCoinWithBalance(this.suiClient, txb, params.rewardAmount.toString(), params.rewardCoinType, this.signerConfig.address);
txb.moveCall({
arguments: [
txb.object(this.config.GlobalConfig),
txb.object(params.pool.id),
txb.pure.u64(params.startTime),
txb.pure.u64(params.activeForSeconds),
txb.object(splitCoin),
txb.pure.string(params.rewardCoinSymbol),
txb.pure.u8(params.rewardCoinDecimals),
txb.pure.u64(params.rewardAmount.toString()),
txb.object(utils_1.SUI_CLOCK_OBJECT_ID)
],
target: `${this.config.CurrentPackage}::admin::initialize_pool_reward`,
typeArguments: [
params.pool.coin_a.address,
params.pool.coin_b.address,
params.rewardCoinType
]
});
// transfer any lingering merge coin back to sender
if (mergeCoin) {
txb.transferObjects([mergeCoin], this.signerConfig.address);
}
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows admin of the protocol to add rewards manager in pool
* @param address address of the reward manager
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async addRewardsManager(address, options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(this.config.AdminCap),
txb.object(this.config.GlobalConfig),
txb.pure.address(address)
],
target: `${this.config.CurrentPackage}::admin::add_reward_manager`
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows admin of the to pause/unpause a pool
* @param pool The pool for which to update pause status
* @param pause True/False the new status of the pool
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async updatePoolStatus(pool, pause, options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(this.config.AdminCap),
txb.object(this.config.GlobalConfig),
txb.object(pool.id),
txb.pure.bool(pause)
],
target: `${this.config.CurrentPackage}::admin::update_pool_pause_status`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows current pool manager to set a new pool manager
* @param pool the pool for which to set the manger
* @param address address of the new manager
* @param options IOnChainCallOptionalParams
*/
async setPoolManager(pool, address, options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(this.config.GlobalConfig),
txb.object(pool.id),
txb.pure.address(address)
],
target: `${this.config.CurrentPackage}::pool::set_manager`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* fetches pool manager of a given pool
* @param pool the pool for which to set the manger
* @param options IOnChainCallOptionalParams
*/
async getPoolManager(pool, options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [txb.object(pool.id)],
target: `${this.config.CurrentPackage}::pool::get_pool_manager`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
const result = await this.suiClient.devInspectTransactionBlock({
transactionBlock: txb,
sender: this.signerConfig.address
});
return bcs_1.bcs.Address.parse(Uint8Array.from(result.results[0].returnValues[0][0]));
}
/**
* fetches information of rewards given in a pool
* @param pool the pool for which rewards are to be fetched
* @param options IOnChainCallOptionalParams
* Returns IRewardCoinsInPool[]
*/
async getRewardCoinsInPool(pool) {
const poolObject = await this.suiClient.getObject({
id: pool.id,
options: { showContent: true }
});
const fields = (poolObject.data?.content).fields;
const rewardInfos = fields.reward_infos.map(reward_infos => reward_infos.fields);
const availableRewardCoinsInPool = [];
for (const rewardInfo of rewardInfos) {
// Temporary fix: Do not include rewards with type of blue coin
// this is to cater the pools which have been assigned blue coin which we can not give to users.
if (rewardInfo.reward_coin_type != constants_1.BLUE_COIN_TYPE) {
const rewardCoin = {
coinType: rewardInfo.reward_coin_type,
coinSymbol: rewardInfo.reward_coin_symbol,
coinDecimals: rewardInfo.reward_coin_decimals
};
availableRewardCoinsInPool.push(rewardCoin);
}
}
return availableRewardCoinsInPool;
}
/**
* Allows pool manager or rewards manager to update reward emissions of initialized rewards in pool
* @param pool The pool in which rewards are initialized
* @param activeForSeconds seconds for which the rewards are to remain active.
* @param rewardCoinType reward coin type (eg. 0x81650ac0868edf55349fa41d4323db5b8a4827bab3b672c16c14c7135abcc3df::usdc::USDC)
* @param rewardAmount reward coin amount which is to be assigned (caller must have coins of specified reward type)
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async updateRewardCoinEmission(pool, activeForSeconds, rewardCoinType, rewardAmount, options) {
const txb = options?.txb || new types_1.TransactionBlock();
const [splitCoin, mergeCoin] = await classes_1.CoinUtils.createCoinWithBalance(this.suiClient, txb, rewardAmount.toString(), rewardCoinType, this.signerConfig.address);
txb.moveCall({
arguments: [
txb.object(this.config.GlobalConfig),
txb.object(pool.id),
txb.pure.u64(activeForSeconds),
txb.object(splitCoin),
txb.pure.u64(rewardAmount.toString()),
txb.object(utils_1.SUI_CLOCK_OBJECT_ID)
],
target: `${this.config.CurrentPackage}::admin::update_pool_reward_emission`,
typeArguments: [pool.coin_a.address, pool.coin_b.address, rewardCoinType]
});
// transfer any lingering merge coin back to sender
if (mergeCoin) {
txb.transferObjects([mergeCoin], this.signerConfig.address);
}
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows pool manager or rewards manager to add seconds to the reward emissions of initialized rewards in pool
* @param pool The pool in which rewards are intialized
* @param secondsToAdd seconds to increase in reward emission.
* @param rewardCoinType reward coin type (eg. 0x81650ac0868edf55349fa41d4323db5b8a4827bab3b672c16c14c7135abcc3df::usdc::USDC)
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async addSecondsToRewardCoinEmission(pool, secondsToAdd, rewardCoinType, options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(this.config.GlobalConfig),
txb.object(pool.id),
txb.pure.u64(secondsToAdd),
txb.object(utils_1.SUI_CLOCK_OBJECT_ID)
],
target: `${this.config.CurrentPackage}::admin::add_seconds_to_reward_emission`,
typeArguments: [pool.coin_a.address, pool.coin_b.address, rewardCoinType]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows the current admin of the protocol to increase
* the supported protocol version
*/
async updateSupportedVersion(options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(this.config.AdminCap),
txb.object(this.config.GlobalConfig)
],
target: `${this.config.CurrentPackage}::admin::update_supported_version`
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Fetches the rewards accrued by the user
* by dev inspecting `collect_reward` method with provided params
* @param pool The pool for which the position is providing liquidity
* @param position id of the position against which rewards are accrued.
* @param rewardCoinType reward coin type (eg. 0x81650ac0868edf55349fa41d4323db5b8a4827bab3b672c16c14c7135abcc3df::usdc::USDC)
* @param options IOnChainCallOptionalParams & { rewardCoinsType?: string[] }
* Returns IRewardAmounts[]
*/
async getAccruedRewards(pool, position, options) {
const txb = options?.txb || new types_1.TransactionBlock();
let rewardCoinsType = options?.rewardCoinsType;
// if reward coin types are not explicitly supplied then find reward coins in Pool
if (!rewardCoinsType || rewardCoinsType?.length == 0) {
const rewardCoinsInPool = await this.getRewardCoinsInPool(pool);
rewardCoinsType = rewardCoinsInPool.map(rewardCoin => rewardCoin.coinType);
}
for (const rewardCoinType of rewardCoinsType) {
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(this.config.GlobalConfig),
txb.object(pool.id),
txb.object(position)
],
target: `${this.config.CurrentPackage}::pool::collect_reward`,
typeArguments: [pool.coin_a.address, pool.coin_b.address, rewardCoinType]
});
}
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
const tx = await this.suiClient.devInspectTransactionBlock({
transactionBlock: txb,
sender: this.signerConfig.address
});
const rewards = classes_1.Transaction.getEvents(tx, "UserRewardCollected");
return rewards.map(reward => {
return {
coinAmount: reward.reward_amount,
coinSymbol: reward.reward_symbol,
coinType: reward.reward_type,
coinDecimals: reward.reward_decimals
};
});
}
/**
* Fetches the fee and rewards accrued by the user in a given position
* by dev inspecting `collect_reward` and `get_accrued_fee` method with provided params
* @param pool The pool for which the position is providing liquidity
* @param position id of the position against which rewards and fees are accrued.
* @param rewardCoinType reward coin type (eg. 0x81650ac0868edf55349fa41d4323db5b8a4827bab3b672c16c14c7135abcc3df::usdc::USDC)
* @param options IOnChainCallOptionalParams & { rewardCoinsType?: string[] }
* Returns IFeeAndRewards
*/
async getAccruedFeeAndRewards(pool, position, options) {
const txb = options?.txb || new types_1.TransactionBlock();
const rewardCoins = await this.getRewardCoinsInPool(pool);
// get accrued rewards from contracts
for (const rewardCoin of rewardCoins) {
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(pool.id),
txb.object(position)
],
target: `${this.config.CurrentPackage}::pool::get_accrued_rewards`,
typeArguments: [
pool.coin_a.address,
pool.coin_b.address,
rewardCoin.coinType
]
});
}
// get accrued fee from contracts
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(pool.id),
txb.object(position)
],
target: `${this.config.CurrentPackage}::pool::get_accrued_fee`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
const result = await this.suiClient.devInspectTransactionBlock({
transactionBlock: txb,
sender: this.signerConfig.address
});
// Combine Fee and Rewards data
// result.results[] array will contain n rewards entries at start and 1 fee entry in last
const data = {
rewards: rewardCoins.map((reward, index) => {
return {
coinAmount: new types_1.BN(bcs_1.bcs
.u64()
.parse(Uint8Array.from(result.results[index].returnValues[0][0]))).toString(),
coinSymbol: reward.coinSymbol,
coinType: reward.coinType,
coinDecimals: reward.coinDecimals
};
}),
fee: {
coinA: new types_1.BN(bcs_1.bcs
.u64()
.parse(Uint8Array.from(result.results[rewardCoins.length].returnValues[0][0]))),
coinB: new types_1.BN(bcs_1.bcs
.u64()
.parse(Uint8Array.from(result.results[rewardCoins.length].returnValues[1][0])))
}
};
return data;
}
/**
* Allows user to collect accrued rewards.
* @param pool The pool for which the position is providing liquidity
* @param position id of the position against which rewards are accrued.
* @param options IOnChainCallOptionalParams & { rewardCoinsType?: string[] }
* Returns OnChainCallResponse
*/
async collectRewards(pool, position, options) {
let txb = options?.txb || new types_1.TransactionBlock();
txb = await this._collectRewardInternal(pool, position, {
txb: txb,
rewardCoinsType: options.rewardCoinsType
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows user to collect accrued fees and rewards from the provided position
* @param pool The pool for which the position is providing liquidity
* @param position id of the position against which fees and rewards are accrued.
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async collectFeeAndRewards(pool, position, options) {
let txb = options?.txb || new types_1.TransactionBlock();
txb = this._collectFeeInternal(pool, position, { txb: txb });
txb = await this._collectRewardInternal(pool, position, {
txb: txb
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows user to collect accrued rewards from all the provided positions
* @param userAddress The address of the user
* @param positionData positions data required for claiming rewards
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async collectRewardsForAllPositions(userAddress, positionsData, options) {
let txb = options?.txb || new types_1.TransactionBlock();
// If the positionsData is not provided , query data from chain
if (!positionsData || positionsData.length == 0) {
positionsData = [];
const positions = await this.queryChain.getUserPositions(this.config.BasePackage, userAddress);
for (const position of positions) {
const pool = await this.queryChain.getPool(position.pool_id);
positionsData.push({ pool, positionId: position.position_id });
}
}
// For each position , create claim rewards Txn
for (const position of positionsData) {
txb = await this._collectRewardInternal(position.pool, position.positionId, {
txb: txb
});
txb = this._collectFeeInternal(position.pool, position.positionId, {
txb: txb
});
}
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to perform a swap on the pool
* @param params The swap contract call parameters
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async swapAssets(params, options) {
return this._swap(params, options);
}
/**
* Dev inspects `calculate_swap_results` method with provided params
* @param params The swap contract call parameters
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async computeSwapResults(params, options) {
return this._computeSwapResults(params, options);
}
/**
* Returns the estimated amount (input or output) the user will get for the swap
* by dev inspecting `calculate_swap_results` method with provided params
* @param params The swap contract call parameters
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async getEstimatedAmount(params, options) {
const result = await this._computeSwapResults({ ...params, estimateAmount: true }, options);
const event = classes_1.Transaction.getEvents(result, "SwapResult")[0];
return Number(event.amount_calculated);
}
/**
* Allows the admin of the bluefin spot protocol to change the protocol fee share of a pool
* @param pool The pool for which protocol fee share is being updated
* @param protocolFeeShare the new protocol fee share, must be in 1e6 format. 100% is represented as 1e6
* @param options
*/
async updateProtocolFeeShare(pool, protocolFeeShare, options) {
const txb = options?.txb || new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(this.config.AdminCap),
txb.object(pool.id),
txb.pure.u64((0, library_1.bigNumber)(protocolFeeShare).toFixed(0))
],
target: `${this.config.CurrentPackage}::admin::update_protocol_fee_share`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Allows caller to execute the swap route to convert Asset A -> B -> C
*/
async executeSwapRoute(route, options) {
const txb = options?.txb || new types_1.TransactionBlock();
const recepient = route?.recepient || this.signerConfig.address;
let edgeNo = 0;
let middleStep = false;
const totalEdges = route.path.length;
let coin = classes_1.CoinUtils.zeroCoin(txb, route.fromCoin);
while (edgeNo < totalEdges) {
const edge = route.path[edgeNo];
const pool = edge.pool;
// if on the last step of the swap route, set middle step to false
if (edgeNo >= 1) {
middleStep = true;
}
const { coinA, coinB } = edge.a2b
? { coinA: coin, coinB: classes_1.CoinUtils.zeroCoin(txb, pool.coin_b.address) }
: { coinA: classes_1.CoinUtils.zeroCoin(txb, pool.coin_a.address), coinB: coin };
// if last edge, use the slippage provided by user else 95%
const slippage = (0, library_1.bigNumber)(edgeNo < totalEdges ? 95 : route.slippage);
const { amountIn, amountOut } = edge.byAmountIn
? {
amountIn: (0, library_1.bigNumber)(edge.amountIn),
amountOut: (0, library_1.bigNumber)(edge.amountOut)
}
: {
amountIn: (0, library_1.bigNumber)(edge.amountOut),
amountOut: (0, library_1.bigNumber)(edge.amountIn)
};
const amountLimit = (0, utils_3.getEstimatedAmountIncludingSlippage)(amountOut, slippage, true);
const sqrtPriceX64Limit = swap_1.SwapUtils.getDefaultSqrtPriceLimit(edge.a2b);
const coinAB = txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(this.config.GlobalConfig),
txb.object(pool.id),
txb.object(coinA),
txb.object(coinB),
txb.pure.bool(edge.a2b),
txb.pure.bool(edge.byAmountIn),
txb.pure.bool(middleStep),
txb.pure.u64(amountIn.toFixed(0)),
txb.pure.u64(amountLimit.toFixed(0)),
txb.pure.u128(sqrtPriceX64Limit.toString())
],
target: `${this.config.CurrentPackage}::gateway::route_swap`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
// if the last edge of path was executed, transfer coins to the recepient
if (edgeNo + 1 == totalEdges) {
txb.transferObjects([coinAB[0]], txb.pure.address(recepient));
txb.transferObjects([coinAB[1]], txb.pure.address(recepient));
}
else {
// we are still not at the last edge, store the output coin from the Tx
coin = edge.a2b ? coinAB[1] : coinAB[0];
// transfer the other object to recepient
txb.transferObjects([edge.a2b ? coinAB[0] : coinAB[1]], txb.pure.address(recepient));
}
// move on to next edge
edgeNo++;
}
return this.handleReturn(txb, options);
}
/**
* Allows caller to claim accrued fee from the provided position
* @param pool The name of the pool or the pool itself
* @param position The position id from which to collect fee
* @param options IOnChainCallOptionalParams
*/
async collectFee(pool, position, options) {
const txb = this._collectFeeInternal(pool, position, options);
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* Returns the fee accrued by provided position
* @param pool The complete state of the pool
* @param position The id of the position for which to get accrued fee
* @returns CoinAmounts: The fee accrued for coin A and B
*/
async getAccruedFee(pool, position) {
const txb = new types_1.TransactionBlock();
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(pool.id),
txb.object(position)
],
target: `${this.config.CurrentPackage}::pool::get_accrued_fee`,
typeArguments: [pool.coin_a.address, pool.coin_b.address]
});
const result = await this.suiClient.devInspectTransactionBlock({
transactionBlock: txb,
sender: this.signerConfig.address
});
return {
coinA: new types_1.BN(bcs_1.bcs.u64().parse(Uint8Array.from(result.results[0].returnValues[0][0]))),
coinB: new types_1.BN(bcs_1.bcs.u64().parse(Uint8Array.from(result.results[0].returnValues[1][0])))
};
}
/**
* Returns the fee accrued by provided position
* @param pool The complete state of the pool
* @param position The id of the position for which to get accrued fee
* @returns CoinAmounts: The fee accrued for coin A and B
*/
async getAccruedFeeForPositions(args) {
const txb = new types_1.TransactionBlock();
args.forEach(v => {
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(v.pool.id),
txb.object(v.position)
],
target: `${this.config.CurrentPackage}::pool::get_accrued_fee`,
typeArguments: [v.pool.coin_a.address, v.pool.coin_b.address]
});
});
const result = await this.suiClient.devInspectTransactionBlock({
transactionBlock: txb,
sender: this.signerConfig.address
});
return result.results.map(res => {
return {
coinA: new types_1.BN(bcs_1.bcs.u64().parse(Uint8Array.from(res.returnValues[0][0]))),
coinB: new types_1.BN(bcs_1.bcs.u64().parse(Uint8Array.from(res.returnValues[1][0])))
};
});
}
/**
* Returns true/false if the provided coin type is supported for pool creation along with the fee amount
*/
async getPoolCreationFeeInfoForCoin(coinType) {
const txb = new types_1.TransactionBlock();
txb.moveCall({
arguments: [txb.object(this.config.GlobalConfig)],
target: `${this.config.CurrentPackage}::config::get_pool_creation_fee_amount`,
typeArguments: [coinType]
});
const result = await this.suiClient.devInspectTransactionBlock({
transactionBlock: txb,
sender: this.signerConfig.address
});
return {
supported: bcs_1.bcs
.bool()
.parse(Uint8Array.from(result.results[0].returnValues[0][0])),
amount: Number(bcs_1.bcs.u64().parse(Uint8Array.from(result.results[0].returnValues[1][0])))
};
}
/**
* @param params The swap contract call parameters
* @param options IOnChainCallOptionalParams
* Returns OnChainCallResponse
*/
async _swap(params, options) {
const txb = options?.txb || new types_1.TransactionBlock();
const toAmount = params.byAmountIn
? (0, library_1.bigNumber)(params.amountOut)
: (0, library_1.bigNumber)(params.amountIn);
const amountLimit = (0, utils_3.getEstimatedAmountIncludingSlippage)(toAmount, (0, library_1.bigNumber)(params.slippage), params.byAmountIn);
const price = (0, utils_3.sqrtPriceX64ToPrice)(params.pool, params.pool.current_sqrt_price);
// if apply slippage to price is true, then slippage is applied to price as well
// else use an arbitrary large slippage value
params.slippage = params.applySlippageToPrice ? params.slippage : 0.2;
const sqrtPriceLimit = (0, utils_3.priceToSqrtPriceX64)(params.pool, (0, utils_3.getPercentageAmount)(price, params.slippage, !params.aToB).toFixed());
const coinAmount = params.byAmountIn
? (0, library_1.bigNumber)(params.amountIn).toFixed(0)
: (0, library_1.bigNumber)(params.amountOut).toFixed(0);
// if swap input is coinA then create required coinA or else make coinA as zero
const [splitCoinA, mergeCoinA] = params.aToB
? await classes_1.CoinUtils.createCoinWithBalance(this.suiClient, txb, coinAmount, params.pool.coin_a.address, this.signerConfig.address)
: [classes_1.CoinUtils.zeroCoin(txb, params.pool.coin_a.address), undefined];
// if swap input is coinB then create required coinB or else make coinB as zero
const [splitCoinB, mergeCoinB] = !params.aToB
? await classes_1.CoinUtils.createCoinWithBalance(this.suiClient, txb, coinAmount, params.pool.coin_b.address, this.signerConfig.address)
: [classes_1.CoinUtils.zeroCoin(txb, params.pool.coin_b.address), undefined];
txb.moveCall({
arguments: [
txb.object(utils_1.SUI_CLOCK_OBJECT_ID),
txb.object(this.config.GlobalConfig),
txb.object(params.pool.id),
txb.object(splitCoinA),
txb.object(splitCoinB),
txb.pure.bool(params.aToB),
txb.pure.bool(params.byAmountIn),
txb.pure.u64(params.byAmountIn
? (0, library_1.bigNumber)(params.amountIn).toFixed(0)
: (0, library_1.bigNumber)(params.amountOut).toFixed(0)),
txb.pure.u64(amountLimit.toFixed(0)),
txb.pure.u128(sqrtPriceLimit.toString())
],
target: `${this.config.CurrentPackage}::gateway::swap_assets`,
typeArguments: [params.pool.coin_a.address, params.pool.coin_b.address]
});
// merge the remaining coins and send them all back to user
const coins = [];
[mergeCoinA, mergeCoinB].forEach(item => {
if (item) {
coins.push(item);
}
});
if (coins.length > 0) {
txb.transferObjects(coins, this.signerConfig.address);
}
if (options?.gasBudget)
txb.setGasBudget(options.gasBudget);
if (options?.sender)
txb.setSenderIfNotSet(options.sender);
return this.handleReturn(txb, options);
}
/**
* @param params The swap contract call parameters
* @param options IOnChainCallOptionalParams
* Returns txBlock and coinOut
*/
async swapAssetsAndReturnCoin(params, options) {
const txb = options?.txb || new types_1.TransactionBlock();
const toAmount = params.byAmountIn
? (0, library_1.bigNumber)(params.amountOut)
: (0, library_1.bigNumber)(params.amountIn);
const amountLimit = (0, utils_3.getEstimatedAmountIncludingSlippage)(toAmount, (0, library_1.bigNumber)(params.slippage), params.byAmountIn);
const price = (0, utils_3.sqrtPriceX64ToPrice)(params.pool, params.pool.current_sqrt_price);
// if apply slippage to price is true, then slippage is applied to price as well
// else use an arbitrary large slippage value
params.slippage = params.applySlippageToPrice ? params.slippage : 0.2;
const sqrtPriceLimit = (0, utils_3.priceToSqrtPriceX64)(params.pool, (0, utils_3.getPercentageAmount)(price, params.slippage, !params.aToB).toFixed());
const coinAmount = params.byAmountIn
? (0, library_1.bigNumber)(params.amountIn).toFixed(0)
: (0, library_1.bigNumber)(params.amountOut).toFixed(0);
// if swap input is coinA then create required coinA or else make coinA as zero
const [splitCoinA, mergeCoinA] = params.aToB
? await classes_1.CoinUtils.createCoinWithBalance(this.suiClient, txb, coinAmount, params.pool.coin_a.address, this.signerConfig.address)
: [undefined, undefined];
// if swap input is coinB then create required coinB or else make coinB as zero
const [splitCoinB, mergeCoinB] = !params.aToB
? await classes_1.CoinUtils.createCoinWithBalance(this.suiClient, txb, coinAmount, params.pool.coin_b.address, this.signerConfig.address)
: [undefined, undefined];
let coinABalance;
let coinBBalance;
// converting coins to balances
if (params.aToB) {
coinABalance = (0, utils_3.coinIntoBalance)(txb, params.pool.coin_a.address, splitCoinA);
coinBBalance = (0, utils_3.zeroBalance)(txb, params.pool.coin_b.address);
}
else {
coinBBalance = (0, utils_3.coinIntoBalance)(txb, params.pool.coin_b.address, splitCoinB);
coinABalance = (0, utils_3.zeroBalance)(txb, params.pool.coin_a.address);
}
const [balanceOutX, balanceOutY] = txb.moveCall({
arguments: [