blub-sdk
Version:
A modular SDK for interacting with the BLUB ecosystem on the Sui blockchain.
230 lines (198 loc) • 6.38 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
// src/queries/stakingRepository.ts
import { Transaction } from "@mysten/sui/transactions";
import { StakingBuilder } from "../../builder/StakingBuilder";
import type {
StakePosition,
PendingReward,
PreCalculatePendingRewardParams,
RewardInfo,
RewardManager,
} from "../../types";
import { completionCoin, getObjectFields } from "../../utils/sui";
import { SuiClient } from "@mysten/sui/client";
import { defaultSuiClient } from "../../utils/client";
/**
* Fetches all stake position IDs registered for the given user.
*/
export async function _queryUserPositionIds(
client: SuiClient,
userPositionRecordId: string,
wallet: string
): Promise<string[]> {
const resq = await client.getDynamicFieldObject({
parentId: userPositionRecordId,
name: {
type: "address",
value: wallet,
},
});
const fields = getObjectFields(resq);
if (!fields) {
throw new Error("fields is null");
}
const tableId = fields.value.fields.id.id;
const positionFields = await client.getDynamicFields({ parentId: tableId });
return positionFields.data.map((item) => item.name.value as string);
}
/**
* Fetches and parses all stake positions for the given IDs.
*/
export async function _getPositions(
positionIds: string[],
client: SuiClient = defaultSuiClient
): Promise<StakePosition[]> {
//
const resq = await client.multiGetObjects({
ids: positionIds,
options: { showContent: true },
});
const positions: StakePosition[] = [];
for (const item of resq) {
const fields = getObjectFields(item);
if (!fields) {
throw new Error("fields is null");
}
const position = await parseUserPosition(client, fields);
positions.push(position);
}
return positions;
}
/**
* Parses a single stake position object into a StakePosition structure.
*/
export async function parseUserPosition(
client: SuiClient,
fields: any
): Promise<StakePosition> {
const waitingClaim: Map<string, bigint> = new Map();
const rewardDebt: Map<string, bigint> = new Map();
const waitClaimRewardTableId = fields.waiting_claim_reward.fields.id.id;
const waitClaimRewardFields = await client.getDynamicFields({
parentId: waitClaimRewardTableId,
});
for (const item of waitClaimRewardFields.data) {
const object = await client.getObject({
id: item.objectId,
options: { showContent: true },
});
const f = getObjectFields(object);
if (!f) throw new Error("fields is null");
waitingClaim.set(f.name.fields.name as string, BigInt(f.value));
}
const rewardDebtTableId = fields.reward_debt.fields.id.id;
const rewardDebtFields = await client.getDynamicFields({
parentId: rewardDebtTableId,
});
for (const item of rewardDebtFields.data) {
const object = await client.getObject({
id: item.objectId,
options: { showContent: true },
});
const f = getObjectFields(object);
if (!f) throw new Error("fields is null");
rewardDebt.set(f.name.fields.name as string, BigInt(f.value));
}
return {
id: fields.id.id,
stakedAmount: BigInt(fields.staked_amount),
rewardDebt,
waitingClaimReward: waitingClaim,
};
}
/**
* Calculates the total staked amount across all user positions.
*/
export async function _getUserTotalStaked(
client: SuiClient,
userPositionRecordId: string,
wallet: string
): Promise<bigint> {
const positionIds = await _queryUserPositionIds(
client,
userPositionRecordId,
wallet
);
if (!positionIds || positionIds.length === 0) return BigInt(0);
const positions = await _getPositions(positionIds);
if (!positions || positions.length === 0) return BigInt(0);
return positions.reduce(
(total, p) => (p.stakedAmount > BigInt(0) ? total + p.stakedAmount : total),
BigInt(0)
);
}
/**
* Simulates a reward calculation for a position using `devInspectTransactionBlock`,
* without executing a real on-chain transaction.
*/
export async function _calculatePendingReward(
owner: string,
{ position, coinType }: PreCalculatePendingRewardParams,
packageId: string,
client: SuiClient = defaultSuiClient
): Promise<PendingReward[]> {
const builder = new StakingBuilder();
const txb = builder.calculatePendingReward(
{ position, coinType },
new Transaction()
);
const inspection = await client.devInspectTransactionBlock({
transactionBlock: txb,
sender: owner,
});
const events = inspection.events ?? [];
const pending: PendingReward[] = [];
for (const e of events) {
if (e.type === `${packageId}::events::CalculatePendingRewardEvent`) {
const dat = (e.parsedJson as any).reward_info;
pending.push({
coinType: dat.coin_type.name,
pendingReward: BigInt(dat.pending_reward_amount),
});
}
}
return pending;
}
export async function queryRewardManager(
client: SuiClient,
rewardManagerId: string
): Promise<RewardManager | null> {
const resq = await client.getObject({
id: rewardManagerId,
options: { showContent: true },
});
const fields = getObjectFields(resq);
if (!fields) throw new Error("fields is null");
const rewardsInfos = new Map<string, RewardInfo>();
const contents = fields.rewards_infos?.fields?.contents ?? [];
contents.forEach((item: any) => {
const rewardCoinType = completionCoin(item.fields.key.fields.name);
const rewardInfo = parseRewardInfo(item.fields.value.fields);
rewardsInfos.set(rewardCoinType, rewardInfo);
});
return {
id: rewardManagerId,
totalStakedAmount: BigInt(fields.total_staked_amount),
rewardsInfos,
userPositionsRecordId: fields.user_positions_record.fields.id.id,
};
}
function parseRewardInfo(fields: any): RewardInfo {
const rewardInfo: RewardInfo = {
rewardCoinType: completionCoin(fields.reward_coin_type.fields.name),
accRewardPerShare: BigInt(fields.acc_reward_per_share),
lastRewardTime: BigInt(fields.last_reward_time),
};
return rewardInfo;
}
export async function queryRewardInfo(
client: SuiClient,
rewardManagerId: string,
rewardCoinType: string
): Promise<RewardInfo | null> {
const rewardManager = await queryRewardManager(client, rewardManagerId);
if (!rewardManager) {
return null;
}
return rewardManager.rewardsInfos.get(completionCoin(rewardCoinType)) ?? null;
}