UNPKG

aftermath-ts-sdk

Version:
938 lines (937 loc) 58.3 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LeveragedStakingApi = void 0; const utils_1 = require("../../../general/utils"); const eventsApiHelpers_1 = require("../../../general/apiHelpers/eventsApiHelpers"); const coin_1 = require("../../coin"); const sui_1 = require("../../sui"); const dayjs_1 = __importDefault(require("dayjs")); // import BigNumber from "bignumber.js"; // import { // BalanceSheet, // BorrowIndex, // InterestModel, // ScallopTxBlock, // SupportPoolCoins, // } from "@scallop-io/sui-scallop-sdk"; /** * Represents the API for interacting with the Leveraged Staking module. */ class LeveragedStakingApi { // ========================================================================= // Constructor // ========================================================================= /** * Creates an instance of the LeveragedStakingApi class. * @param {AftermathApi} Provider - The AftermathApi instance. * @param {ScallopProviders} ScallopProviders - The ScallopProviders instance. * @throws {Error} If not all required addresses have been set in AfSdk */ constructor(Provider, ScallopProviders // ScallopProviders ) { this.Provider = Provider; this.ScallopProviders = ScallopProviders; // ========================================================================= // Objects // ========================================================================= /** * Fetches the leveraged stake position for a given wallet address. * @param inputs - The input parameters for fetching the leveraged stake position. * @returns A promise that resolves to the leveraged stake position response. */ this.fetchLeveragedStakePosition = (inputs) => __awaiter(this, void 0, void 0, function* () { const { walletAddress } = inputs; const leveragedAfSuiPositions = yield this.Provider.Objects().fetchCastObjectsOwnedByAddressOfType({ walletAddress, objectType: this.objectTypes.leveragedAfSuiPosition, objectFromSuiObjectResponse: utils_1.Casting.leveragedStaking .leveragedAfSuiPositionFromSuiObjectResponse, }); if (leveragedAfSuiPositions.length <= 0) return "none"; return leveragedAfSuiPositions[0]; }); /** * Fetches the SUI market pool. * @returns A promise that resolves to the ScallopMarketPool object representing the SUI market pool. * @throws An error if the SUI market pool is not found. */ this.fetchSuiMarketPool = () => __awaiter(this, void 0, void 0, function* () { // ScallopMarketPool if (!this.ScallopProviders) throw new Error("Scallop providers not set"); const suiMarketPool = yield this.ScallopProviders.Query.getMarketPool("sui"); if (!suiMarketPool) throw new Error("sui market pool not found"); return suiMarketPool; }); /** * Fetches the LeveragedAfSuiState. * @returns A promise that resolves to the LeveragedAfSuiState. */ this.fetchLeveragedAfSuiState = () => __awaiter(this, void 0, void 0, function* () { return this.Provider.Objects().fetchCastObject({ objectId: this.addresses.leveragedStaking.objects.leveragedAfSuiState, objectFromSuiObjectResponse: utils_1.Casting.leveragedStaking .leveragedAfSuiStateFromSuiObjectResponse, }); }); /** * Fetches the market collateral for the AfSui market. * @returns A promise that resolves to the ScallopMarketCollateral object. * @throws An error if the Sui market pool is not found. */ this.fetchAfSuiMarketCollateral = () => __awaiter(this, void 0, void 0, function* () { // ScallopMarketCollateral if (!this.ScallopProviders) throw new Error("Scallop providers not set"); const afSuiMarketCollateral = yield this.ScallopProviders.Query.getMarketCollateral("afsui"); if (!afSuiMarketCollateral) throw new Error("sui market pool not found"); return afSuiMarketCollateral; }); this.fetchLeveragedAfSuiPosition = (inputs) => __awaiter(this, void 0, void 0, function* () { if (!this.ScallopProviders) throw new Error("Scallop providers not set"); // ia. Obtain the owned `LeveragedAfSuiPosition` object. const leveragedAfSuiPosition = yield this.Provider.Objects().fetchCastObject({ objectId: inputs.leveragedAfSuiPositionId, objectFromSuiObjectResponse: utils_1.Casting.leveragedStaking .leveragedAfSuiPositionFromSuiObjectResponse, }); // ib. Obtain the shared `Obligation` object. const obligation = yield this.ScallopProviders.Query.queryObligation(inputs.obligationId.toString()); // ic. Obtain Scallop's SUI Market. const marketData = yield this.getMarketData(Object.assign(Object.assign({}, inputs), { poolCoinName: "sui" })); const oldBorrowIndex = Math.ceil(Number(marketData.borrowIndex)); // ii. Update the position's Borrow Index to account for increase in the SUI Borrow Rate. // new_borrow_index = old_borrow_index + (old_borrow_index * interest_rate * time_delta) const currentTimestamp = Math.ceil((0, dayjs_1.default)().valueOf() / 1000); const lastUpdated = Number(marketData.lastUpdated); const timeDelta = currentTimestamp - lastUpdated; const interestRate = Number(marketData.interestRate.value); const interestRateScale = Number(marketData.interestRateScale) * 100; const borrowIndexDelta = (oldBorrowIndex * interestRate * timeDelta) / interestRateScale; const newBorrowIndex = oldBorrowIndex + borrowIndexDelta; // iii. Increase the Position's debt. const positionBorrowIndex = Number(obligation.debts[0].borrowIndex || 0); const increasedRate = newBorrowIndex / positionBorrowIndex - 1; // const positionSuiDebt = BigNumber(obligation.debts[0].amount || 0); // const availableRepayAmount = positionSuiDebt // .multipliedBy(increasedRate + 1) // // .multipliedBy(1.01) // .toNumber(); // const positionSuiDebtUpdated = BigInt(Math.ceil(availableRepayAmount)); const positionSuiDebtUpdated = BigInt(0); return Object.assign(Object.assign({}, leveragedAfSuiPosition), { suiDebt: positionSuiDebtUpdated }); }); // ========================================================================= // Transaction Commands // ========================================================================= this.newLeveragedAfSuiPositionTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames .leveragedAfSuiPosition, "new_leveraged_afsui_position"), typeArguments: [], arguments: [ tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState utils_1.Helpers.addTxObject(tx, inputs.obligationKeyId), // ObligationKey ], }); }; this.initiateLeverageStakeTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "initiate_leverage_stake"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState utils_1.Helpers.addTxObject(tx, inputs.afSuiCoinId), // Coin ], }); }; this.initiateLeverageUnstakeTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "initiate_leverage_unstake"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState tx.pure.u64(inputs.unstakeAmount), // withdraw_amount ], }); }; this.initiateChangeLeverageTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "initiate_change_leverage"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState ], }); }; this.completeActionTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "complete_action"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedActionCapId), // LeveragedActionCap utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState utils_1.Helpers.addTxObject(tx, inputs.obligationId), // Obligation, tx.object(this.addresses.staking.objects.stakedSuiVault), // StakedSuiVault tx.object(this.addresses.staking.objects.safe), // Safe<TreasuryCap<AFSUI>> ], }); }; this.depositAfSuiCollateralTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "deposit_afsui_collateral"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedActionCapId), // LeveragedActionCap utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState tx.object(this.addresses.scallop.objects.version), // Version utils_1.Helpers.addTxObject(tx, inputs.obligationId), // Obligation tx.object(this.addresses.scallop.objects.afSuiMarket), // Market utils_1.Helpers.addTxObject(tx, inputs.afSuiCoinId), // Coin ], }); }; this.withdrawAfSuiCollateralTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "withdraw_afsui_collateral"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedActionCapId), // LeveragedActionCap utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState tx.object(this.addresses.scallop.objects.version), // Version utils_1.Helpers.addTxObject(tx, inputs.obligationId), // Obligation tx.object(this.addresses.scallop.objects.afSuiMarket), // Market tx.object(this.addresses.scallop.objects.coinDecimalsRegistry), // CoinDecimalsRegistry tx.pure.u64(inputs.withdrawAmount), // withdraw_amount tx.object(this.addresses.scallop.objects.xOracle), // XOracle tx.object(sui_1.Sui.constants.addresses.suiClockId), // Clock ], }); }; this.borrowSuiTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "borrow_sui"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedActionCapId), // LeveragedActionCap utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState tx.object(this.addresses.scallop.objects.version), // Version utils_1.Helpers.addTxObject(tx, inputs.obligationId), // Obligation tx.object(this.addresses.scallop.objects.afSuiMarket), // Market tx.object(this.addresses.scallop.objects.coinDecimalsRegistry), // CoinDecimalsRegistry tx.pure.u64(inputs.borrowAmount), // borrow_amount tx.object(this.addresses.scallop.objects.xOracle), // XOracle tx.object(sui_1.Sui.constants.addresses.suiClockId), // Clock ], }); }; this.repaySuiTx = (inputs) => { const { tx } = inputs; return tx.moveCall({ target: utils_1.Helpers.transactions.createTxTarget(this.addresses.leveragedStaking.packages.leveragedAfSui, LeveragedStakingApi.constants.moduleNames.interface, "repay_sui"), typeArguments: [], arguments: [ utils_1.Helpers.addTxObject(tx, inputs.leveragedActionCapId), // LeveragedActionCap utils_1.Helpers.addTxObject(tx, inputs.leveragedAfSuiPositionId), // LeveragedAfSuiPosition tx.object(this.addresses.leveragedStaking.objects.leveragedAfSuiState), // LeveragedAfSuiState tx.object(this.addresses.scallop.objects.version), // Version utils_1.Helpers.addTxObject(tx, inputs.obligationId), // Obligation tx.object(this.addresses.scallop.objects.afSuiMarket), // Market utils_1.Helpers.addTxObject(tx, inputs.suiCoinId), // Coin tx.object(sui_1.Sui.constants.addresses.suiClockId), // Clock ], }); }; // ========================================================================= // Transaction Builders // ========================================================================= // ERROR Notes: // > [ScallopLeveragedAfSui] [interface] 0: EInvalidLeveragedAfSuiPosition // > [ScallopLeveragedAfSui] [leveraged_afsui_state] 0: EInvalidProtocolVersion // > [Scallop] 513 -> 0x201: version_mismatch_error // > [Scallop] 1025 -> 0x401: oracle_stale_price_error // > [Scallop] [borrow] 1281 -> 0x501: borrow_too_much_error // > [Scallop] [reserve] 1283 -> 0x503: flash_loan_repay_not_enough_error // > [Scallop] [deposit_collateral] 1793 -> 0x701: max_collateral_reached_error // > [Scallop] [withdraw_collateral] 1795 -> 0x703: withdraw_collateral_too_much_error // > [afSUI] [actions] 3: ELessThanMinimumStakingThreshold // > [Pyth] [pyth_adaptor] 70146: assert_price_not_stale // { address: 910f30cbc7f601f75a5141a01265cd47c62d468707c5e1aecb32a18f448cb25a}} // > [SUI] [dynamic_field] 1: EFieldDoesNotExist (The object does not have a dynamic field with // this name (with the value and type specified)) this.fetchBuildWithdrawAfSuiCollateralTx = (inputs) => __awaiter(this, void 0, void 0, function* () { const { scallopTx } = inputs; // i. Update Scallop's price feeds for SUI and afSUI. yield scallopTx.updateAssetPricesQuick(["sui", "afsui"]); // ii. Withdraw `withdrawAmount` worth of afSUI collateral. return this.withdrawAfSuiCollateralTx(Object.assign(Object.assign({}, inputs), { // @ts-ignore tx: scallopTx.txBlock, leveragedActionCapId: inputs.leveragedActionCapId, withdrawAmount: inputs.withdrawAmount })); }); // TODO(kevin): Documentation this.fetchBuildOpenLeveragedStakeTx = (inputs) => __awaiter(this, void 0, void 0, function* () { if (!this.ScallopProviders) throw new Error("Scallop providers not set"); const scallopTx = this.ScallopProviders.Builder.createTxBlock(); const tx = scallopTx.txBlock; tx.setSender(inputs.walletAddress); // i. Create an `Obligation` on Scallop. const [obligationId, obligationKeyId, obligationHotPotatoId] = scallopTx.openObligation(); // ii. Open a new `LeveragedAfSuiPosition` position. const [leveragedAfSuiPositionId] = this.newLeveragedAfSuiPositionTx({ // @ts-ignore tx, obligationKeyId, }); // iii. Leverage stake. yield this.buildLeveragedStakeTx(Object.assign(Object.assign({}, inputs), { scallopTx, leveragedAfSuiPositionId, obligationId, baseAfSuiCollateral: BigInt(0), totalAfSuiCollateral: BigInt(0), totalSuiDebt: BigInt(0) })); // iv. Return the `LeveragedAfSuiPosition` to the sender. tx.transferObjects([leveragedAfSuiPositionId], inputs.walletAddress); // v. Share the associated `Obligation` object. scallopTx.returnObligation(obligationId, obligationHotPotatoId); // @ts-ignore return tx; }); // TODO(kevin): Documentation this.fetchBuildLeveragedStakeTx = (inputs) => __awaiter(this, void 0, void 0, function* () { if (!this.ScallopProviders) throw new Error("Scallop providers not set"); const scallopTx = this.ScallopProviders.Builder.createTxBlock(); const tx = scallopTx.txBlock; tx.setSender(inputs.walletAddress); // i. Leverage stake. yield this.buildLeveragedStakeTx(Object.assign(Object.assign({}, inputs), { scallopTx })); // @ts-ignore return tx; }); // TODO(kevin): Documentation this.buildLeveragedStakeTx = (inputs) => __awaiter(this, void 0, void 0, function* () { const { scallopTx, referrer, walletAddress, leveragedAfSuiPositionId, obligationId, stakeAmount, isSponsoredTx, } = inputs; const tx = scallopTx.txBlock; // TODO(Collin/Kevin): assert that `leverage` is less than or equal to `1 / (1 - collateralWeight)`. if (referrer) this.Provider.ReferralVault().updateReferrerTx({ // @ts-ignore tx, referrer, }); let newBaseAfSuiCollateral; let afSuiCoinId; // i. Obtain the amount and ID of the afSUI collateral to be deposited. The user can choose to // leverage stake starting in SUI in which case their SUI needs to be staked to afSUI. if (inputs.stakeCoinType === "sui") { // ia. If the input was denominated in SUI, stake to afSUI. const suiCoin = yield this.Provider.Coin().fetchCoinWithAmountTx({ // @ts-ignore tx, walletAddress, isSponsoredTx, coinType: coin_1.Coin.constants.suiCoinType, coinAmount: stakeAmount, }); const swapOrStakeResult = yield this.swapOrStakeSuiToAfSui({ // @ts-ignore tx, suiAmount: stakeAmount, suiCoinId: suiCoin, }); newBaseAfSuiCollateral = swapOrStakeResult.minAmountOut; afSuiCoinId = swapOrStakeResult.afSuiCoinId; } else { // ib. Obtain afSUI coin with `stakeAmount` value. newBaseAfSuiCollateral = stakeAmount; afSuiCoinId = yield this.Provider.Coin().fetchCoinWithAmountTx({ // @ts-ignore tx, walletAddress, isSponsoredTx, coinType: this.Provider.Staking().coinTypes.afSui, coinAmount: stakeAmount, }); } // ii. Initiate Stake tx. const leveragedActionCapId = this.initiateLeverageStakeTx({ // @ts-ignore tx, leveragedAfSuiPositionId, afSuiCoinId, }); // iii. Deposit afSUI as collateral on Scallop. this.depositAfSuiCollateralTx(Object.assign(Object.assign({}, inputs), { // @ts-ignore tx, leveragedActionCapId, afSuiCoinId, obligationId: inputs.obligationId })); if (inputs.leverage > 1) { // iv. Increase the leverage to the desired leverage ratio. yield this.fetchBuildIncreaseLeverageTx(Object.assign(Object.assign({}, inputs), { baseAfSuiCollateral: inputs.baseAfSuiCollateral + newBaseAfSuiCollateral, totalAfSuiCollateral: inputs.totalAfSuiCollateral + newBaseAfSuiCollateral, totalSuiDebt: inputs.totalSuiDebt, newLeverage: inputs.leverage, leveragedActionCapId })); } // v. Complete the Stake transaction and emit an event. this.completeActionTx({ // @ts-ignore tx, leveragedActionCapId, leveragedAfSuiPositionId, obligationId, }); }); // TODO(Kevin): Documentation. this.fetchBuildLeveragedUnstakeTx = (inputs) => __awaiter(this, void 0, void 0, function* () { throw new Error("TODO"); // if (!this.ScallopProviders) // throw new Error("Scallop providers not set"); // const { // referrer, // walletAddress, // leveragedAfSuiPositionId, // obligationId, // unstakeAmount, // desiredUnstakeCoinType, // slippage, // } = inputs; // const scallopTx = this.ScallopProviders.Builder.createTxBlock(); // const tx = scallopTx.txBlock; // tx.setSender(walletAddress); // // i. Set the users referrer address. // if (referrer) // this.Provider.ReferralVault().updateReferrerTx({ // // @ts-ignore // tx, // referrer, // }); // // ii. Obtain the user's `LeveragedAfSuiPosition`. // const leveragedAfSuiPosition = await this.fetchLeveragedAfSuiPosition({ // leveragedAfSuiPositionId, // obligationId, // }); // // iii. Initiate Unstake tx. // const leveragedActionCapId = this.initiateLeverageUnstakeTx({ // // @ts-ignore // tx, // leveragedAfSuiPositionId, // unstakeAmount, // }); // const afSuiToSuiExchangeRate = // await this.Provider.Staking().fetchAfSuiToSuiExchangeRate(); // // iv. Calculate current leverage ratio. // const currentLeverageRatio = LeveragedStaking.calcLeverage({ // totalSuiDebt: leveragedAfSuiPosition.suiDebt, // totalAfSuiCollateral: leveragedAfSuiPosition.afSuiCollateral, // afSuiToSuiExchangeRate, // }); // let unstakedCoinId; // let unstakedCoinType; // // [Edge Case] Position has no debt. // if (leveragedAfSuiPosition.suiDebt === BigInt(0)) { // // va. Withdraw `unstakeAmount` worth of afSUI collateral. // const [unstakedAfSuiCollateral] = // await this.fetchBuildWithdrawAfSuiCollateralTx({ // ...inputs, // scallopTx, // leveragedActionCapId, // withdrawAmount: unstakeAmount, // }); // unstakedCoinId = unstakedAfSuiCollateral; // unstakedCoinType = "afsui"; // } /* (leveragedAfSuiPosition.suiDebt > BigInt(0)) */ else { // // vb. Decrease the leverage to the desired leverage ratio. // const remainingSuiCoinId = await this.fetchBuildDecreaseLeverageTx({ // scallopTx, // leveragedActionCapId, // leveragedAfSuiPositionId, // obligationId, // totalSuiDebt: leveragedAfSuiPosition.suiDebt, // totalAfSuiCollateral: leveragedAfSuiPosition.afSuiCollateral, // // REVIEW(Kevin): should we be subtracting from here too? // // // // totalAfSuiCollateral: // // inputs.totalAfSuiCollateral - unstakeAmount, // newLeverage: currentLeverageRatio, // baseAfSuiCollateral: inputs.baseAfSuiCollateral - unstakeAmount, // slippage, // }); // unstakedCoinId = remainingSuiCoinId; // unstakedCoinType = "sui"; // } // // vi. Return the unstaked coin to the user in their desired coin (SUI or afSUI). // if (unstakedCoinType === inputs.desiredUnstakeCoinType) { // // via. Unstaked coin already in desired coin type; no extra work needed. // tx.transferObjects([unstakedCoinId], walletAddress); // } else if (desiredUnstakeCoinType === "sui") { // // vib. Swap withdrawn afSUI into SUI and return to the user. // const poolObject = await this.Provider.Pools().fetchPool({ // objectId: // this.addresses.leveragedStaking.objects.afSuiSuiPoolId, // }); // const pool = new Pool(poolObject); // const swappedSuiCoinId = // await this.Provider.Pools().fetchAddTradeTx({ // // @ts-ignore // tx, // pool, // coinInAmount: unstakeAmount, // coinInId: unstakedCoinId, // coinInType: this.Provider.Staking().coinTypes.afSui, // coinOutType: Coin.constants.suiCoinType, // slippage, // }); // tx.transferObjects([swappedSuiCoinId], walletAddress); // } /* if (desiredUnstakeCoinType === "afsui") */ else { // // vic. Stake the withdrawn SUI for afSUI and return to the user. // let [unstakedAfSuiCollateral] = this.Provider.Staking().stakeTx({ // // @ts-ignore // tx, // validatorAddress: // this.addresses.staking.objects.aftermathValidator, // suiCoin: unstakedCoinId, // }); // tx.transferObjects([unstakedAfSuiCollateral], walletAddress); // } // // vii. Complete Unstake tx. // this.completeActionTx({ // // @ts-ignore // tx, // leveragedActionCapId, // leveragedAfSuiPositionId, // obligationId, // }); // // @ts-ignore // return tx; }); // TODO(Kevin): Documentation. this.fetchBuildChangeLeverageTx = (inputs) => __awaiter(this, void 0, void 0, function* () { if (!this.ScallopProviders) throw new Error("Scallop providers not set"); const { referrer, walletAddress, leveragedAfSuiPositionId, obligationId, } = inputs; // TODO(Collin/Kevin): assert that `leverage` is less than or equal to `1 / (1 - collateralWeight)`. // If leverage is greater than this then the user's collateral will not be enough to support the amount // of SUI that must be borrowed to reach that leverage ratio. const scallopTx = this.ScallopProviders.Builder.createTxBlock(); const tx = scallopTx.txBlock; tx.setSender(walletAddress); if (referrer) this.Provider.ReferralVault().updateReferrerTx({ // @ts-ignore tx, referrer, }); // i. Initiate Change Leverage tx. const leveragedActionCapId = this.initiateChangeLeverageTx({ // @ts-ignore tx, leveragedAfSuiPositionId, }); // ii. Update the leverage in the desired direction. if (inputs.newLeverage < inputs.currentLeverage) { // iia. Remove afSUI Collateral and repay debt to reach desired leverage. const remainingSuiCoinId = yield this.fetchBuildDecreaseLeverageTx(Object.assign(Object.assign({}, inputs), { scallopTx, leveragedActionCapId })); // iib. Use remaining SUI to pay off SUI debt on Scallop. this.repaySuiTx(Object.assign(Object.assign({}, inputs), { // @ts-ignore tx, leveragedActionCapId, suiCoinId: remainingSuiCoinId })); } else { // iic. Borrow SUI and deposit more afSUI Collateral to reach desired leverage. yield this.fetchBuildIncreaseLeverageTx(Object.assign(Object.assign({}, inputs), { scallopTx, leveragedActionCapId })); } // iii. Complete Change Leverage tx. this.completeActionTx({ // @ts-ignore tx, leveragedActionCapId, leveragedAfSuiPositionId, obligationId, }); return tx; }); // TODO(Kevin): Documentation. this.fetchBuildIncreaseLeverageTx = (inputs) => __awaiter(this, void 0, void 0, function* () { throw new Error("TODO"); // const { scallopTx } = inputs; // const tx = scallopTx.txBlock; // const afSuiToSuiExchangeRate = // await this.Provider.Staking().fetchAfSuiToSuiExchangeRate(); // const newTotalAfSuiCollateral = BigInt( // Math.floor(Number(inputs.baseAfSuiCollateral) * inputs.newLeverage) // ); // // ia. Calculate the extra amount of afSUI collateral that must be deposited to reach the new // // desired leverage. // const increaseInTotalAfSuiCollateral = // newTotalAfSuiCollateral - inputs.totalAfSuiCollateral; // // ib. Calculate amount of SUI that must be flash loaned to account for // // `increaseInAfSuiCollateral`. // const flashLoanAmount: Balance = BigInt( // Math.floor( // Number(increaseInTotalAfSuiCollateral) * afSuiToSuiExchangeRate // ) // ); // // ii. Flash loan the required amount of SUI from Scallop to increase the position by // // `increaseInAfSuiCollateral` afSUI. // const [flashLoanedSuiCoinId, loan] = scallopTx.borrowFlashLoan( // flashLoanAmount, // "sui" // ); // const { afSuiCoinId } = await this.swapOrStakeSuiToAfSui({ // // @ts-ignore // tx, // suiAmount: flashLoanAmount, // suiCoinId: flashLoanedSuiCoinId, // }); // // iv. Deposit the staked afSUI as collateral on Scallop. // this.depositAfSuiCollateralTx({ // ...inputs, // // @ts-ignore // tx, // afSuiCoinId, // obligationId: inputs.obligationId, // }); // // REVIEW(Kevin): check if both assets need to be updated. // // // await scallopTx.updateAssetPricesQuick(["sui", "afsui"]); // // v. Borrow amount of SUI required to pay off flash loan. // const [borrowedSuiCoinId] = this.borrowSuiTx({ // ...inputs, // // @ts-ignore // tx, // borrowAmount: flashLoanAmount + BigInt(/*0__0*/ 50_000_000), // // borrowAmount: flashLoanAmount, // }); // const repayLoanSuiCoinId = tx.splitCoins(borrowedSuiCoinId, [ // flashLoanAmount, // ]); // // vi. Repay flash loan on Scallop. // scallopTx.repayFlashLoan( // // flashLoanedSuiCoinId, // repayLoanSuiCoinId, // // borrowedSuiCoinId, // loan, // "sui" // ); // // Leftover SUI is used to repay SUI debt. // this.repaySuiTx({ // ...inputs, // // @ts-ignore // tx, // suiCoinId: borrowedSuiCoinId, // }); // // REVIEW(kevin): will there even be any leftover SUI to repay? // // // // // vii. [Potentially] Use remaining SUI to repay debt. // // this.repaySuiTx({ // // tx, // // leveragedActionCapId, // // obligationId, // // suiCoinId: borrowedSuiCoinId, // // }); }); // TODO(Kevin): Documentation. // // To decrease leverage, a user needs to withdraw afSUI collateral. To withdraw collateral, a user must first // repay some or all of their SUI debt. The decrease leverage flow is as follows: // 1. Calculate how much SUI debt must be repayed to allow withdrawing desired afSUI collateral. // 2. Flash loan SUI. // 3. Use SUI to repay debt on Scallop. // 4. Withdraw afSUI collateral on Scallop. // 5. Convert afSUI to SUI. // 6. Repay flash loan. this.fetchBuildDecreaseLeverageTx = (inputs) => __awaiter(this, void 0, void 0, function* () { throw new Error("TODO"); // const { scallopTx, slippage } = inputs; // const tx = scallopTx.txBlock; // const afSuiToSuiExchangeRate = // await this.Provider.Staking().fetchAfSuiToSuiExchangeRate(); // let decreaseInAfSuiCollateral; // let decreaseInSuiDebt; // // [Edge Case] User wants to unstake their entire position. // if (inputs.baseAfSuiCollateral === BigInt(0)) { // // TODO: [edge case] handle closing of position. // // // decreaseInAfSuiCollateral = BigInt(inputs.totalAfSuiCollateral); // // decreaseInAfSuiCollateral = inputs.totalAfSuiCollateral; // decreaseInSuiDebt = inputs.totalSuiDebt; // } else { // // ia. Calculate the amount of afSUI collateral that must be withdrawn to reach // // a leverage ratio of `newLeverage`. // const newTotalAfSuiCollateral = BigInt( // Math.floor( // Number(inputs.baseAfSuiCollateral) * inputs.newLeverage // ) // ); // decreaseInAfSuiCollateral = // inputs.totalAfSuiCollateral - newTotalAfSuiCollateral; // // ib. Calculate the amount of SUI debt that must be repayed to allow withdrawing // // `decreaseInAfSuiCollateral` worth of afSUI collateral. // const newSuiDebt = BigInt( // Math.floor( // Number( // newTotalAfSuiCollateral - inputs.baseAfSuiCollateral // ) * afSuiToSuiExchangeRate // ) // ); // decreaseInSuiDebt = inputs.totalSuiDebt - newSuiDebt; // } // // ii. Flash loan `decreaseInSuiDebt` worth of SUI from Scallop. // const [flashLoanedSuiCoinId, loan] = scallopTx.borrowFlashLoan( // decreaseInSuiDebt, // "sui" // ); // // iii. Repay `decreaseInSuiDebt` of SUI debt. // this.repaySuiTx({ // ...inputs, // // @ts-ignore // tx, // suiCoinId: flashLoanedSuiCoinId, // }); // // iv. Withdraw `decreaseInCollateralAmount` worth of afSUI collateral. // const [afSuiId] = await this.fetchBuildWithdrawAfSuiCollateralTx({ // ...inputs, // withdrawAmount: decreaseInAfSuiCollateral, // }); // // v. Convert `decreaseInCollateralAmount` of withdrawn collateral into SUI. // const poolObject = await this.Provider.Pools().fetchPool({ // objectId: this.addresses.leveragedStaking.objects.afSuiSuiPoolId, // }); // const pool = new Pool(poolObject); // const swappedSuiCoinId = await this.Provider.Pools().fetchAddTradeTx({ // // @ts-ignore // tx, // pool, // coinInAmount: BigInt( // Math.floor(Number(decreaseInSuiDebt) * afSuiToSuiExchangeRate) // ), // coinInId: afSuiId, // coinInType: this.Provider.Staking().coinTypes.afSui, // coinOutType: Coin.constants.suiCoinType, // slippage, // }); // const repayLoanSuiCoinId = tx.splitCoins(swappedSuiCoinId, [ // decreaseInSuiDebt, // ]); // // vi. Repay flash loan with converted SUI. // scallopTx.repayFlashLoan(repayLoanSuiCoinId, loan, "sui"); // return swappedSuiCoinId; }); // ========================================================================= // Helpers // ========================================================================= this.swapOrStakeSuiToAfSui = (inputs) => __awaiter(this, void 0, void 0, function* () { throw new Error("TODO"); // const { tx, suiAmount, suiCoinId } = inputs; // const estimatedSlippageLowerBound = 0.0001; // 0.01% // let afSuiCoinId: TransactionObjectArgument; // let minAmountOut: Balance; // if (suiAmount >= Staking.constants.bounds.minStake) { // // Stake SUI into afSUI. // afSuiCoinId = this.Provider.Staking().stakeTx({ // tx, // validatorAddress: // this.addresses.staking.objects.aftermathValidator, // suiCoin: suiCoinId, // }); // const afSuiToSuiExchangeRate = // await this.Provider.Staking().fetchAfSuiToSuiExchangeRate(); // minAmountOut = BigInt( // Math.floor(Number(suiAmount) * afSuiToSuiExchangeRate) // ); // } else { // const poolObject = await this.Provider.Pools().fetchPool({ // objectId: // this.addresses.leveragedStaking.objects.afSuiSuiPoolId, // }); // const pool = new Pool(poolObject); // minAmountOut = BigInt( // Math.floor( // Number( // pool.getTradeAmountOut({ // coinInAmount: suiAmount, // coinInType: Coin.constants.suiCoinType, // coinOutType: // this.Provider.Staking().coinTypes.afSui, // }) // ) * // (1 - estimatedSlippageLowerBound) // ) // ); // afSuiCoinId = await this.Provider.Pools().fetchAddTradeTx({ // tx, // pool, // slippage: 1, // 100% // coinInAmount: suiAmount, // coinInId: suiCoinId, // coinInType: Coin.constants.suiCoinType, // coinOutType: this.Provider.Staking().coinTypes.afSui, // }); // } // return { afSuiCoinId, minAmountOut }; }); // REVIEW: this is only needed if a user's Obligation can have its debt increased by another address. // If that isn't possible, then obligation's debt amount + borrow index will always be accurate after // a call to `completeActionTx`. -- needed because `liquidate` and `repay` don't require `ObligationKey` // to be called. // this.updateTotalSuiDebt = (inputs) => __awaiter(this, void 0, void 0, function* () { if (!this.ScallopProviders) throw new Error("Scallop providers not set"); const suiMarket = yield this.ScallopProviders.Query.getMarketPool("sui"); const newBorrowIndex = suiMarket.borrowIndex; const obligationAccount = yield this.ScallopProviders.Query.queryObligation(inputs.obligationId.toString()); const [positionSuiDebt, positionBorrowIndex] = obligationAccount.debts ? [ BigInt(obligationAccount.debts[0].amount), Number(obligationAccount.debts[0].borrowIndex), ] : [BigInt(0), 0]; if (positionBorrowIndex === newBorrowIndex) return { totalSuiDebt: positionSuiDebt }; return { // REVIEW: calc. (seems to be undershooting/off by a little) totalSuiDebt: BigInt(Math.floor((Number(positionSuiDebt) * newBorrowIndex) / positionBorrowIndex)), }; }); // ========================================================================= // Event Types // ========================================================================= this.leveragedStakedEventType = () => eventsApiHelpers_1.EventsApiHelpers.createEventType(this.addresses.staking.packages.events, LeveragedStakingApi.constants.moduleNames.events, LeveragedStakingApi.constants.eventNames.leveragedStaked); this.leveragedUnstakedEventType = () => eventsApiHelpers_1.EventsApiHelpers.createEventType(this.addresses.staking.packages.events, LeveragedStakingApi.constants.moduleNames.events, LeveragedStakingApi.constants.eventNames.leveragedUnstaked); this.leverageChangedEventType = () => eventsApiHelpers_1.EventsApiHelpers.createEventType(this.addresses.staking.packages.events, LeveragedStakingApi.constants.moduleNames.events, LeveragedStakingApi.constants.eventNames.leverageChanged); const leveragedStaking = this.Provider.addresses.leveragedStaking; const staking = this.Provider.addresses.staking; const pools = this.Provider.addresses.pools; const scallop = this.Provider.addresses.scallop; if (!leveragedStaking || !staking || !pools || !scallop) throw new Error("not all required addresses have been set in provider"); this.addresses = { leveragedStaking, staking, pools, scallop, }; this.eventTypes = { leveragedStaked: this.leveragedStakedEventType(), leveragedUnstaked: this.leveragedUnstakedEventType(), leverageChanged: this.leverageChangedEventType(), }; this.objectTypes = { leveragedAfSuiPosition: `${leveragedStaking.packages.leveragedAfSuiInitial}::leveraged_afsui_position::LeveragedAfSuiPosition`, }; } // ========================================================================= // Events // ========================================================================= /** * Fetches events for a specific user. * @param inputs - The input parameters for fetching events. * @returns A promise that resolves to an object containing the fetched events and a cursor for pagination. */ fetchEventsForUser(inputs) { return __awaiter(this, void 0, void 0, function* () { throw new Error("TODO"); // const { walletAddress, cursor, limit } = inputs; // return this.Provider.indexerCaller.fetchIndexerEvents( // `leveraged-staking/${walletAddress}/events`, // { // cursor, // limit, // }, // (event) => { // const eventType = (event as EventOnChain<any>).type; // return eventType.includes(this.eventTypes.leveragedStaked) // ? Casting.leveragedStaking.leveragedStakedEventFromOnChain( // event as LeveragedStakedEventOnChain // ) // : eventType.includes(this.eventTypes.leveragedUnstaked) // ? Casting.leveragedStaking.leveragedUnstakedEventFromOnChain( // event as LeveragedUnstakedEventOnChain // ) // : Casting.leveragedStaking.leveragedStakeChangedEventFromOnChain( // event as LeveragedStakeChangedLeverageEventOnChain // ); // } // ); }); } // ========================================================================= // Graph Data // ========================================================================= /** * Fetches the performance data for leveraged staking. * @param inputs - The inputs for fetching performance data. * @returns A promise that resolves to an array of LeveragedStakingPerformanceDataPoint objects. */ fetchPerformanceData(inputs) { return __awaiter(this, void 0, void 0, function* () { // const { timeframe, borrowRate, maxLeverage } = inputs; // dayjs.extend(duration); // const limit = // days ~ epochs // dayjs // .duration( // LeveragedStakingApi.dataTimeframesToDays[timeframe], // "days" // ) // // + 2 to account for apy being calculated from events delta // // (and possible initial 0 afsui supply) // .asDays() + 2; // // TODO: fetch borrow rate historically once scallop implements // const [recentEpochChanges] = await Promise.all([ // this.Provider.Staking().fetchEpochWasChangedEvents({ // limit, // }), // ]); // if (recentEpochChanges.events.length <= 2) return []; // const daysInYear = 365; // const timeData = recentEpochChanges.events // .slice(2) // .map((event, index) => { // const currentRate = Number(event.totalAfSuiSupply) // ? Number(event.totalSuiAmount) / // Number(event.totalAfSuiSupply) // : 1; // const pastEvent = recentEpochChanges.events[index + 1]; // const pastRate = Number(pastEvent.totalAfSuiSupply) // ? Number(pastEvent.totalSuiAmount) / // Number(pastEvent.totalAfSuiSupply) // : 1; // const afSuiApy = // ((currentRate - pastRate) / pastRate) * daysInYear; // return { // time: event.timestamp ?? 0, // sui: 0, // afSui: afSuiApy,