client-aftermath-ts-sdk
Version:
Client Aftermath TypeScript SDK
488 lines (487 loc) • 25.6 kB
JavaScript
"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;
};