UNPKG

client-aftermath-ts-sdk

Version:
488 lines (487 loc) 25.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.StakingApi = void 0; const transactions_1 = require("@mysten/sui/transactions"); const aftermathApi_1 = require("../../../general/providers/aftermathApi"); const stakingTypes_1 = require("../stakingTypes"); const utils_1 = require("../../../general/utils"); const eventsApiHelpers_1 = require("../../../general/apiHelpers/eventsApiHelpers"); const coin_1 = require("../../coin"); const sui_1 = require("../../sui"); const __1 = require("../.."); class StakingApi { // ========================================================================= // Constructor // ========================================================================= constructor(Provider) { this.Provider = Provider; // ========================================================================= // Public Methods // ========================================================================= // ========================================================================= // Transaction Commands // ========================================================================= // ========================================================================= // Staking Transaction Commands // ========================================================================= /** * Adds move call to tx for liquid staking of SUI for afSUI. * * @returns `Coin<AFSUI>` if `withTransfer` is `undefined` or `false` */ this.stakeTx = (inputs) => { const { tx, suiCoin, withTransfer } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "request_stake" + (withTransfer ? "_and_keep" : "")), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), tx.object(sui_1.Sui.constants.addresses.suiSystemStateId), tx.object(this.addresses.objects.referralVault), typeof suiCoin === "string" ? tx.object(suiCoin) : suiCoin, tx.pure.address(inputs.validatorAddress), ], }); }; /** * Adds move call to tx for liquid unstaking of afSUI for SUI that will be * processed at start of next epoch (end of current epoch). * * @returns () */ this.unstakeTx = (inputs) => { const { tx, afSuiCoin } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "request_unstake"), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), typeof afSuiCoin === "string" ? tx.object(afSuiCoin) : afSuiCoin, ], }); }; /** * Adds move call to tx for liquid unstaking of afSUI for SUI that will be * processed immedietly. * * @returns `Coin<SUI>` if `withTransfer` is `undefined` or `false` */ this.atomicUnstakeTx = (inputs) => { const { tx, afSuiCoin, withTransfer } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "request_unstake_atomic" + (withTransfer ? "_and_keep" : "")), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), tx.object(this.addresses.objects.referralVault), tx.object(this.addresses.objects.treasury), typeof afSuiCoin === "string" ? tx.object(afSuiCoin) : afSuiCoin, ], }); }; /** * Adds move call to tx for liquid staking of currently staked (non-liquid) * SUI objects for afSUI. * * @returns `Coin<AFSUI>` if `withTransfer` is `undefined` or `false` */ this.requestStakeStakedSuiVecTx = (inputs) => { const { tx, stakedSuiIds, withTransfer } = inputs; const stakedSuiIdsVec = tx.makeMoveVec({ elements: stakedSuiIds.map((id) => tx.object(id)), }); return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "request_stake_staked_sui_vec" + (withTransfer ? "_and_keep" : "")), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), tx.object(sui_1.Sui.constants.addresses.suiSystemStateId), tx.object(this.addresses.objects.referralVault), stakedSuiIdsVec, tx.pure.address(inputs.validatorAddress), ], }); }; this.epochWasChangedTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "epoch_was_changed"), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), tx.object(sui_1.Sui.constants.addresses.suiSystemStateId), tx.object(this.addresses.objects.referralVault), tx.object(this.addresses.objects.treasury), tx.pure.u64(BigInt(1000)), // fields_requests_per_tx ], }); }; // ========================================================================= // Inspection Transaction Commands // ========================================================================= this.afSuiToSuiExchangeRateTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "afsui_to_sui_exchange_rate"), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), // Safe ], }); }; this.suiToAfSuiExchangeRateTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "sui_to_afsui_exchange_rate"), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), // Safe ], }); }; this.totalSuiAmountTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: aftermathApi_1.AftermathApi.helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "total_sui_amount"), typeArguments: [], arguments: [tx.object(this.addresses.objects.stakedSuiVault)], }); }; this.afSuiToSuiTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "afsui_to_sui"), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), tx.pure.u64(inputs.afSuiAmount), ], }); }; this.suiToAfSuiTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "sui_to_afsui"), typeArguments: [], arguments: [ tx.object(this.addresses.objects.stakedSuiVault), tx.object(this.addresses.objects.safe), tx.pure.u64(inputs.suiAmount), ], }); }; // ========================================================================= // Validator Transaction Commands // ========================================================================= this.updateValidatorFeeTx = (inputs) => { const { tx, validatorOperationCapId } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.packages.lsd, StakingApi.constants.moduleNames.stakedSuiVault, "update_validator_fee"), typeArguments: [], arguments: [ typeof validatorOperationCapId === "string" ? tx.object(validatorOperationCapId) : validatorOperationCapId, tx.object(this.addresses.objects.stakedSuiVault), tx.pure.u64(inputs.newFee), ], }); }; // ========================================================================= // Transaction Builders // ========================================================================= /** * Builds complete PTB for liquid staking of SUI for afSUI. * * @returns Transaction Block ready for execution */ this.fetchBuildStakeTx = (inputs) => __awaiter(this, void 0, void 0, function* () { const { referrer, externalFee } = inputs; if (externalFee) StakingApi.assertValidExternalFee(externalFee); const tx = new transactions_1.Transaction(); tx.setSender(inputs.walletAddress); if (referrer) this.Provider.ReferralVault().updateReferrerTx({ tx, referrer, }); const suiCoin = yield this.Provider.Coin().fetchCoinWithAmountTx({ tx, walletAddress: inputs.walletAddress, coinType: coin_1.Coin.constants.suiCoinType, coinAmount: inputs.suiStakeAmount, isSponsoredTx: inputs.isSponsoredTx, }); if (externalFee) { const feeAmount = BigInt(Math.floor(Number(inputs.suiStakeAmount) * externalFee.feePercentage)); const suiFeeCoin = tx.splitCoins(suiCoin, [feeAmount]); tx.transferObjects([suiFeeCoin], externalFee.recipient); } const afSuiCoinId = this.stakeTx(Object.assign(Object.assign({ tx }, inputs), { suiCoin })); tx.transferObjects([afSuiCoinId], inputs.walletAddress); return tx; }); /** * Builds complete PTB for liquid unstaking of afSUI for SUI. * * @returns Transaction Block ready for execution */ this.fetchBuildUnstakeTx = (inputs) => __awaiter(this, void 0, void 0, function* () { const { referrer, externalFee } = inputs; if (externalFee) StakingApi.assertValidExternalFee(externalFee); const tx = new transactions_1.Transaction(); tx.setSender(inputs.walletAddress); if (referrer) this.Provider.ReferralVault().updateReferrerTx({ tx, referrer, }); const afSuiCoin = yield this.Provider.Coin().fetchCoinWithAmountTx({ tx, walletAddress: inputs.walletAddress, coinType: this.coinTypes.afSui, coinAmount: inputs.afSuiUnstakeAmount, }); if (externalFee) { const feeAmount = BigInt(Math.floor(Number(inputs.afSuiUnstakeAmount) * externalFee.feePercentage)); const afSuiFeeCoin = tx.splitCoins(afSuiCoin, [feeAmount]); tx.transferObjects([afSuiFeeCoin], externalFee.recipient); } if (inputs.isAtomic) { const suiCoinId = this.atomicUnstakeTx(Object.assign(Object.assign({ tx }, inputs), { afSuiCoin })); tx.transferObjects([suiCoinId], inputs.walletAddress); } else { this.unstakeTx(Object.assign(Object.assign({ tx }, inputs), { afSuiCoin })); } return tx; }); /** * Builds complete PTB for liquid staking of currently staked (non-liquid) * SUI objects for afSUI. * * @returns Transaction Block ready for execution */ this.fetchBuildStakeStakedSuiTx = (inputs) => __awaiter(this, void 0, void 0, function* () { const { referrer } = inputs; const tx = new transactions_1.Transaction(); tx.setSender(inputs.walletAddress); if (referrer) this.Provider.ReferralVault().updateReferrerTx({ tx, referrer, }); // TODO: add external fee here const afSuiCoinId = this.requestStakeStakedSuiVecTx(Object.assign({ tx }, inputs)); tx.transferObjects([afSuiCoinId], inputs.walletAddress); return tx; }); this.buildUpdateValidatorFeeTx = (inputs) => __awaiter(this, void 0, void 0, function* () { const tx = new transactions_1.Transaction(); tx.setSender(inputs.walletAddress); this.updateValidatorFeeTx(Object.assign(Object.assign({}, inputs), { tx, newFee: utils_1.Casting.numberToFixedBigInt(inputs.newFeePercentage) })); return tx; }); this.buildEpochWasChangedTx = utils_1.Helpers.transactions.createBuildTxFunc(this.epochWasChangedTx); // ========================================================================= // Private Methods // ========================================================================= // ========================================================================= // Event Types // ========================================================================= this.stakedEventType = () => eventsApiHelpers_1.EventsApiHelpers.createEventType(this.addresses.packages.events, StakingApi.constants.moduleNames.events, StakingApi.constants.eventNames.staked); this.unstakeRequestedEventType = () => eventsApiHelpers_1.EventsApiHelpers.createEventType(this.addresses.packages.events, StakingApi.constants.moduleNames.events, StakingApi.constants.eventNames.unstakeRequested); this.unstakedEventType = () => eventsApiHelpers_1.EventsApiHelpers.createEventType(this.addresses.packages.events, StakingApi.constants.moduleNames.events, StakingApi.constants.eventNames.unstaked); this.epochWasChangedEventType = () => eventsApiHelpers_1.EventsApiHelpers.createEventType(this.addresses.packages.events, StakingApi.constants.moduleNames.events, StakingApi.constants.eventNames.epochWasChanged); if (!this.Provider.addresses.staking) throw new Error("not all required addresses have been set in provider"); this.addresses = this.Provider.addresses.staking; this.eventTypes = { staked: this.stakedEventType(), unstakeRequested: this.unstakeRequestedEventType(), unstaked: this.unstakedEventType(), epochWasChanged: this.epochWasChangedEventType(), }; this.coinTypes = { afSui: `${this.addresses.packages.afsui}::afsui::AFSUI`, }; this.objectTypes = { unverifiedValidatorOperationCap: `${this.addresses.packages.events}::validator::UnverifiedValidatorOperationCap`, }; this.moveErrors = { [this.addresses.packages.lsd]: { [StakingApi.constants.moduleNames.stakedSuiVault]: { /// The admin calls `migrate` on an outdated package. 0: "Version Incompatibility", /// A user tries to interact with the `StakedSuiVault` through an outdated package. 1: "Wrong Package Version", /// One tries to call deprecated function. 2: "Deprecated", }, [StakingApi.constants.moduleNames.sort]: { /// One provided keys and values vectors of different lengths. 1: "Different Inputs Length", /// Error for tests. 2: "Dummy Error", }, [StakingApi.constants.moduleNames.calculations]: { /// User provided a percentage value larger than 10^18 = 1 = 100%. 0: "Invalid Percentage", }, [StakingApi.constants.moduleNames.actions]: { /// Epoch advancement has not yet been processed. 0: "Epoch Change Has Not Been Treated", /// Epoch advancement has already been processed. 1: "Epoch Change Has Already Been Treated", /// User tried to delegate stake to a validator that is inactive. 2: "Validator Is Not Active", /// User tried to delegate stake with value less than the minimum staking threshold. 3: "Less Than Minimum Staking Threshold", /// User tried to delegate stake to a validator whose history of exchange rates is too short. 4: "Insufficient Validator History", /// User provided an empty vector as input. 5: "Empty Vector", /// User requested to unstake more SUI than held in the `atomic_unstake_sui_reserves`. 6: "Insufficient Sui Reserves", /// User provided afSUI coin with insufficient balance. 7: "Insufficient Balance afSUI Coin Provided", }, [StakingApi.constants.moduleNames.receipt]: { 0: "Not Enough Amount In Receipt", 1: "Try To Burn Non Zero Receipt", }, [StakingApi.constants.moduleNames.stakedSuiVaultState]: { /// One provided value larger than 1 (100%) when opposite is supposed. 1: "Invalid Percentage", /// One provided min atomic unstake fee value larger than max atomic unstake fee value. 2: "Invalid Atomic Unstake Fees Values", /// A `validator` address - that isn't recognized by the afSUI framework - is provided to a function /// that requests a `ValidatorConfig`. 3: "Invalid Validator", /// An address tries to create a `UnverifiedValidatorOperationCap` without being an active validator. 4: "Validator Is Not Active", /// An authorized owner of an `UnverifiedValidatorOperationCap` object tries to perform a permissioned /// function for another validator. 5: "Invalid Operation Cap", /// An authorized owner of an `UnverifiedValidatorOperationCap` object tries to set a `validator_fee` /// that is greater than the maximum allowed validator fee. 6: "Invalid Validator Fee", }, }, }; } } exports.StakingApi = StakingApi; _a = StakingApi; // ========================================================================= // Constants // ========================================================================= StakingApi.constants = { moduleNames: { actions: "actions", events: "events", stakedSuiVault: "staked_sui_vault", stakedSuiVaultState: "staked_sui_vault_state", routerWrapper: "router", sort: "sort", receipt: "receipt", calculations: "calculations", }, eventNames: { staked: "StakedEvent", unstaked: "UnstakedEvent", unstakeRequested: "UnstakeRequestedEvent", epochWasChanged: "EpochWasChangedEvent", }, }; // ========================================================================= // Public Static Methods // ========================================================================= // ========================================================================= // Staking Positions Updating // ========================================================================= // NOTE: should these functions be on FE only ? StakingApi.updateStakingPositionsFromEvent = (inputs) => { const positions = inputs.stakingPositions; const event = inputs.event; let newPositions = []; // TODO: use bifilter const unstakePositions = positions.filter(stakingTypes_1.isUnstakePosition); const newUnstakes = (0, stakingTypes_1.isUnstakeEvent)(event) ? _a.updateUnstakePositionsFromEvent({ event, unstakePositions, }) : unstakePositions; const stakePositions = positions.filter(stakingTypes_1.isStakePosition); const newStakes = (0, stakingTypes_1.isStakeEvent)(event) ? [...stakePositions, event] : stakePositions; newPositions = [...newUnstakes, ...newStakes]; return newPositions.sort((a, b) => { var _b, _c; return ((_b = b.timestamp) !== null && _b !== void 0 ? _b : 0) - ((_c = a.timestamp) !== null && _c !== void 0 ? _c : 0); }); }; // ========================================================================= // Private Static Methods // ========================================================================= StakingApi.assertValidExternalFee = (externalFee) => { if (externalFee.feePercentage >= __1.Staking.constants.bounds.maxExternalFeePercentage) throw new Error(`external fee percentage exceeds max of ${__1.Staking.constants.bounds.maxExternalFeePercentage * 100}%`); if (externalFee.feePercentage <= 0) throw new Error(`external fee percentage must be greater than 0`); }; // ========================================================================= // Unstake Event Processing // ========================================================================= StakingApi.updateUnstakePositionsFromEvent = (inputs) => { const foundPositionIndex = inputs.unstakePositions.findIndex((pos) => pos.afSuiId === inputs.event.afSuiId); if (foundPositionIndex < 0) { if (inputs.event.type.includes(_a.constants.eventNames.unstakeRequested)) return [ Object.assign(Object.assign({}, inputs.event), { state: "REQUEST" }), ...inputs.unstakePositions, ]; // unstaked event return [ Object.assign(Object.assign({}, inputs.event), { state: "SUI_MINTED" }), ...inputs.unstakePositions, ]; } const foundStakePosition = inputs.unstakePositions[foundPositionIndex]; let position = undefined; if (inputs.event.type.includes(_a.constants.eventNames.unstaked)) position = Object.assign(Object.assign({}, inputs.event), { state: "SUI_MINTED", epoch: foundStakePosition.epoch }); if (inputs.event.type.includes(_a.constants.eventNames.unstakeRequested)) position = Object.assign(Object.assign({}, inputs.event), { state: "REQUEST", epoch: foundStakePosition.epoch }); if (!position) return inputs.unstakePositions; let newStakePositions = [...inputs.unstakePositions]; newStakePositions[foundPositionIndex] = position; return newStakePositions; };