UNPKG

navi-sdk

Version:

The NAVI SDK Client provides tools for interacting with the Sui blockchain networks, designed for handling transactions, accounts, and smart contracts efficiently. This documentation covers the setup, account management, and transaction handling within th

501 lines (453 loc) 15.7 kB
import { Transaction } from "@mysten/sui/transactions"; import { getConfig, pool, ProFundsPoolInfo, PriceFeedConfig, } from "../../address"; import { OptionType } from "../../types"; import { SuiClient } from "@mysten/sui/client"; import { normalizeStructTag } from "@mysten/sui/utils"; import { moveInspect, getCoinOracleInfo } from "../CallFunctions"; import { registerStructs, updateOraclePTB } from "./commonFunctions"; import { depositCoin } from "./commonFunctions"; interface Reward { asset_id: number; funds: string; available: string; reward_id: string; } /** * Retrieves the incentive pools for a given asset and option. * @param assetId - The ID of the asset. * @param option - The option type. * @param user - (Optional) The user's address. If provided, the rewards claimed by the user and the total rewards will be returned. * @returns The incentive pools information. */ export async function getIncentivePools( client: SuiClient, assetId: number, option: OptionType, user: string ) { const config = await getConfig(); const tx = new Transaction(); // await updateOraclePTB(client, tx); const result: any = await moveInspect( tx, client, user, `${config.uiGetter}::incentive_getter::get_incentive_pools`, [ tx.object("0x06"), // clock object id tx.object(config.IncentiveV2), // the incentive object v2 tx.object(config.StorageId), // object id of storage tx.pure.u8(assetId), tx.pure.u8(option), tx.pure.address(user), // If you provide your address, the rewards that have been claimed by your address and the total rewards will be returned. ], [], // type arguments is null "vector<IncentivePoolInfo>" // parse type ); return result[0]; } // Attach asset symbols interface FormattedData { [key: string]: { asset_id: number; funds: string; available: string; reward_id: string; reward_coin_type: string; asset_symbol?: string; }; } /** * Retrieves the available rewards for a given address. * * @param checkAddress - The address to check for rewards. Defaults to the current address. * @param option - The option type. Defaults to 1. * @param prettyPrint - Whether to print the rewards in a pretty format. Defaults to true. * @returns An object containing the summed rewards for each asset. * @throws If there is an error retrieving the available rewards. */ export async function getAvailableRewards( client: SuiClient, checkAddress: string, option: OptionType = 1, prettyPrint = true ) { registerStructs(); const assetIds = Array.from( { length: Number(Object.keys(pool).length) }, (_, i) => i ); try { // Fetch incentive pools for each asset ID const incentivePools = await Promise.all( assetIds.map((assetId) => getIncentivePools(client, assetId, option, checkAddress) ) ); const allPools = incentivePools.flat(); // Filter active pools with available rewards const activePools = allPools.filter( (pool) => pool.available.trim() !== "0" ); const fundIds = [...new Set(activePools.map((item) => item.funds))]; // Fetch fund details const funds = await client.multiGetObjects({ ids: fundIds, options: { showContent: true }, }); // Extract relevant data const fundDetails = funds.map((item) => ({ funds: item?.data?.objectId, reward_coin_type: (item.data?.content as any)?.fields?.coin_type?.fields ?.name, reward_coin_oracle_id: (item.data?.content as any)?.fields?.oracle_id, })); // Merge extracted data with active pools const mergedPools = activePools.map((pool) => { const matchedFund = fundDetails.find( (fund) => fund.funds === `0x${pool.funds}` ); return { ...pool, reward_coin_type: matchedFund?.reward_coin_type ?? null, reward_coin_oracle_id: matchedFund?.reward_coin_oracle_id ?? null, }; }); // Build price feed map const priceFeedMap: Record<string, number> = Object.values( PriceFeedConfig ).reduce((acc: Record<string, number>, feed) => { acc[feed.coinType] = feed.priceDecimal; return acc; }, {} as Record<string, number>); // Compute available rewards with decimal conversion interface ProcessedData { [key: string]: { asset_id: number; funds: string; available: string; reward_id: string; reward_coin_type: string; }; } const processedData: ProcessedData = mergedPools.reduce((acc, pool) => { const priceDecimal = priceFeedMap[`0x${pool.reward_coin_type}`] ?? null; const availableDecimal = priceDecimal !== null ? Number(BigInt(pool.available) / BigInt(10 ** 27)) / 10 ** priceDecimal : null; const assetId = parseInt(pool.asset_id, 10); const key = `${assetId}-${option}-${pool.reward_coin_type}`; if (acc[key]) { const existingAvailable = parseFloat(acc[key].available); const newAvailable = parseFloat(availableDecimal?.toFixed(6) ?? "0"); acc[key].available = (existingAvailable + newAvailable).toFixed(6); } else { // if not existing acc[key] = { asset_id: assetId, funds: pool.funds, available: availableDecimal?.toFixed(6) ?? "0", reward_id: pool.reward_coin_oracle_id?.toString() ?? "", reward_coin_type: pool.reward_coin_type, }; } return acc; }, {} as ProcessedData); // Map asset IDs to their symbols const assetSymbolMap: Record<string, string> = Object.values(pool).reduce( (acc: Record<string, string>, poolConfig) => { acc[poolConfig.assetId.toString()] = poolConfig.name; return acc; }, {} as Record<string, string> ); const formattedData: FormattedData = Object.keys(processedData).reduce( (acc, assetId) => { acc[assetId] = { ...processedData[assetId], asset_symbol: assetSymbolMap[processedData[assetId].asset_id] ?? null, }; return acc; }, {} as FormattedData ); // Pretty print the results if required if (prettyPrint) { console.log(`-- V2 available rewards --`); console.log(`address: ${checkAddress}`); Object.keys(formattedData).forEach((assetId) => { const assetData = formattedData[assetId]; console.log(`Asset: ${assetData.asset_symbol}`); console.log( ` ${Object.keys(formattedData).indexOf(assetId) + 1}. Reward Coin: ${ assetData.reward_coin_type }, Option: ${option}, Claimable: ${assetData.available}` ); }); } return formattedData; } catch (error) { console.error("Failed to get available rewards:", error); throw error; } } /** * Claims all available rewards for the specified account. * @returns PTB result */ export async function claimAllRewardsPTB( client: SuiClient, userToCheck: string, tx?: Transaction ) { let txb = tx || new Transaction(); const [rewardsBorrow, rewardsSupply]: [ { [key: string]: Reward }, { [key: string]: Reward } ] = await Promise.all([ getAvailableRewards(client, userToCheck, 3, false), getAvailableRewards(client, userToCheck, 1, false), ]); const borrowFunds = Object.values(rewardsBorrow).map((item) => item.funds); const supplyFunds = Object.values(rewardsSupply).map((item) => item.funds); const fundsIds = Array.from(new Set([...borrowFunds, ...supplyFunds])); let oracleIds: number[] = []; fundsIds.forEach((fundId) => { if (ProFundsPoolInfo[fundId]) { oracleIds.push(ProFundsPoolInfo[fundId].oracleId); } }); oracleIds = Array.from(new Set([...oracleIds])); const coinPrice = await getCoinOracleInfo(client, oracleIds); const coinPriceMap: { [key: number]: { price: number; decimals: number } } = {}; for (const item of coinPrice) { coinPriceMap[item.oracle_id] = { price: parseFloat(item.price) / Math.pow(10, item.decimals), decimals: item.decimals, }; } // Convert the rewards object to an array of its values const rewardsArray: Reward[] = Object.values(rewardsSupply); for (const reward of rewardsArray) { const coinInfo = coinPriceMap[Number(reward.reward_id)]; if (coinInfo) { const availableAmount = parseFloat(reward.available) * coinInfo.price; if (availableAmount >= 0.01) { await claimRewardFunction(txb, reward.funds, reward.asset_id, 1); } } } // Convert the rewards object to an array of its values const rewardsBorrowArray: Reward[] = Object.values(rewardsBorrow); for (const reward of rewardsBorrowArray) { const coinInfo = coinPriceMap[Number(reward.reward_id)]; if (coinInfo) { const availableAmount = parseFloat(reward.available) * coinInfo.price; if (availableAmount >= 0.01) { await claimRewardFunction(txb, reward.funds, reward.asset_id, 3); } } } return txb; } export async function claimRewardsByAssetIdPTB( client: SuiClient, userToCheck: string, assetId: number, tx?: Transaction ) { let txb = tx || new Transaction(); const [rewardsBorrow, rewardsSupply]: [ { [key: string]: Reward }, { [key: string]: Reward } ] = await Promise.all([ getAvailableRewards(client, userToCheck, 3, false), getAvailableRewards(client, userToCheck, 1, false), ]); // Convert the rewards object to an array of its values const rewardsArray: Reward[] = Object.values(rewardsSupply); for (const reward of rewardsArray) { if ( reward.asset_id === assetId) { await claimRewardFunction(txb, reward.funds, reward.asset_id, 1); } } // Convert the rewards object to an array of its values const rewardsBorrowArray: Reward[] = Object.values(rewardsBorrow); for (const reward of rewardsBorrowArray) { if ( reward.asset_id === assetId) { await claimRewardFunction(txb, reward.funds, reward.asset_id, 3); } } return txb; } /** * Claims the reward for a transaction block. * @param txb - The transaction block. * @param incentiveFundsPool - The incentive funds pool. * @param assetId - The asset ID. * @param option - The option type. */ export async function claimRewardFunction( txb: Transaction, incentiveFundsPool: string, assetId: number, option: OptionType ) { const config = await getConfig(); txb.moveCall({ target: `${config.ProtocolPackage}::incentive_v2::claim_reward`, arguments: [ txb.object("0x06"), txb.object(config.IncentiveV2), txb.object(`0x${incentiveFundsPool}`), txb.object(config.StorageId), txb.pure.u8(assetId), txb.pure.u8(option), ], typeArguments: [ProFundsPoolInfo[incentiveFundsPool].coinType], }); } /** * Claims all available rewards for the specified account. * @returns PTB result */ export async function claimAllRewardsResupplyPTB( client: SuiClient, userToCheck: string, tx?: Transaction ) { let txb = tx || new Transaction(); const [rewardsBorrow, rewardsSupply]: [ { [key: string]: Reward }, { [key: string]: Reward } ] = await Promise.all([ getAvailableRewards(client, userToCheck, 3, false), getAvailableRewards(client, userToCheck, 1, false), ]); const borrowFunds = Object.values(rewardsBorrow).map((item) => item.funds); const supplyFunds = Object.values(rewardsSupply).map((item) => item.funds); const fundsIds = Array.from(new Set([...borrowFunds, ...supplyFunds])); let oracleIds: number[] = []; fundsIds.forEach((fundId) => { if (ProFundsPoolInfo[fundId]) { oracleIds.push(ProFundsPoolInfo[fundId].oracleId); } }); oracleIds = Array.from(new Set([...oracleIds])); const coinPrice = await getCoinOracleInfo(client, oracleIds); const coinPriceMap: { [key: number]: { price: number; decimals: number } } = {}; for (const item of coinPrice) { coinPriceMap[item.oracle_id] = { price: parseFloat(item.price) / Math.pow(10, item.decimals), decimals: item.decimals, }; } // Convert the rewards object to an array of its values const rewardsArray: Reward[] = Object.values(rewardsSupply); for (const reward of rewardsArray) { const coinInfo = coinPriceMap[Number(reward.reward_id)]; if (coinInfo) { const availableAmount = parseFloat(reward.available) * coinInfo.price; if (availableAmount >= 0.01) { await claimRewardResupplyFunction(txb, reward.funds, reward.asset_id, 1); } } } // Convert the rewards object to an array of its values const rewardsBorrowArray: Reward[] = Object.values(rewardsBorrow); for (const reward of rewardsBorrowArray) { const coinInfo = coinPriceMap[Number(reward.reward_id)]; if (coinInfo) { const availableAmount = parseFloat(reward.available) * coinInfo.price; if (availableAmount >= 0.01) { await claimRewardResupplyFunction(txb, reward.funds, reward.asset_id, 3); } } } return txb; } /** * Claims the reward for a transaction block. * @param txb - The transaction block. * @param incentiveFundsPool - The incentive funds pool. * @param assetId - The asset ID. * @param option - The option type. */ export async function claimRewardResupplyFunction( txb: Transaction, incentiveFundsPool: string, assetId: number, option: OptionType ) { const config = await getConfig(); const reward_balance = txb.moveCall({ target: `${config.ProtocolPackage}::incentive_v2::claim_reward_non_entry`, arguments: [ txb.object("0x06"), txb.object(config.IncentiveV2), txb.object(`0x${incentiveFundsPool}`), txb.object(config.StorageId), txb.pure.u8(Number(assetId)), txb.pure.u8(option), ], typeArguments: [ProFundsPoolInfo[incentiveFundsPool].coinType], }); const [reward_coin]: any = txb.moveCall({ target: "0x2::coin::from_balance", arguments: [reward_balance], typeArguments: [ProFundsPoolInfo[incentiveFundsPool].coinType], }); const reward_coin_value = txb.moveCall({ target: "0x2::coin::value", arguments: [reward_coin], typeArguments: [ProFundsPoolInfo[incentiveFundsPool].coinType], }); const foundPoolConfig = Object.values(pool).find( (poolConfig) => normalizeStructTag(poolConfig.type) === normalizeStructTag(ProFundsPoolInfo[incentiveFundsPool].coinType) ); if (!foundPoolConfig) { throw new Error( `Pool configuration not found. incentiveFundsPool: ${incentiveFundsPool}, ProFundsPoolInfo: ${JSON.stringify( ProFundsPoolInfo?.[incentiveFundsPool] )}` ); } await depositCoin(txb, foundPoolConfig, reward_coin, reward_coin_value); } export async function getIncentivePoolsByPhase( client: SuiClient, option: OptionType, user: string ) { const config = await getConfig(); const tx = new Transaction(); // await updateOraclePTB(client, tx); const result: any = await moveInspect( tx, client, user, `${config.uiGetter}::incentive_getter::get_incentive_pools_group_by_phase`, [ tx.object("0x06"), // clock object id tx.object(config.IncentiveV2), // the incentive object v2 tx.object(config.StorageId), // object id of storage tx.pure.u8(1), tx.pure.u8(option), tx.pure.address(user), // If you provide your address, the rewards that have been claimed by your address and the total rewards will be returned. ], [], // type arguments is null "vector<IncentivePoolInfoByPhase>" // parse type ); return result[0]; }