diff --git a/README.md b/README.md index 2a561de5..7f4ee8bd 100644 --- a/README.md +++ b/README.md @@ -259,3 +259,5 @@ Here is the list of functions and their brief explanations: 59. `getUnit`: Returns the corresponding unit from the `arrUnitPrefixes` array. 60. `nFormatter`: Returns a string representation of a number in a more readable format. 61. `formatNumber`: Formats a number by adding weight prefix and considering decimal places. +62. `getClaimedReward`: Returns claimed reward amount by extrinsicHash and blockHeight that have the reward event. +62. `getUsedFee`: Returns usedfee amount by extrinsicHash and blockHeight that have the actualFee. diff --git a/package.json b/package.json index ce1904d1..139436ec 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/AstarNetwork/astar.js.git" }, "sideEffects": false, - "version": "0.2.9", + "version": "0.3.0", "workspaces": [ "packages/*", "examples/*" diff --git a/packages/sdk-core/src/modules/dapp-staking/pending-rewards/index.ts b/packages/sdk-core/src/modules/dapp-staking/pending-rewards/index.ts index d9b0a327..07c8f275 100644 --- a/packages/sdk-core/src/modules/dapp-staking/pending-rewards/index.ts +++ b/packages/sdk-core/src/modules/dapp-staking/pending-rewards/index.ts @@ -1,5 +1,5 @@ -import { StorageKey, Struct } from '@polkadot/types'; -import { Perbill } from '@polkadot/types/interfaces'; +import { StorageKey, Struct, Vec } from '@polkadot/types'; +import { EventRecord, Perbill } from '@polkadot/types/interfaces'; import { ApiPromise } from '@polkadot/api'; import { ethers } from 'ethers'; import { Codec } from '@polkadot/types/types'; @@ -85,10 +85,10 @@ const getClaimableEraRange = (era: number, currentEra: number): number[] => { const formatGeneralStakerInfo = ({ eraTvls, currentEra, generalStakerInfo }: { - currentEra: number; - eraTvls: EraTvl[]; - generalStakerInfo: [StorageKey, Codec][]; -}): { stakerInfo: StakerInfo[]; stakedEras: number[] } => { + currentEra: number; + eraTvls: EraTvl[]; + generalStakerInfo: [StorageKey, Codec][]; + }): { stakerInfo: StakerInfo[]; stakedEras: number[] } => { const stakerInfo: StakerInfo[] = []; const stakedEras: number[] = []; @@ -125,12 +125,12 @@ const estimateEraTokenIssuances = async ({ blockHeight, stakedEras, currentEra, blocksPerEra }: { - blockHeight: number; - api: ApiPromise; - stakedEras: number[]; - currentEra: number; - blocksPerEra: number; -}): Promise => { + blockHeight: number; + api: ApiPromise; + stakedEras: number[]; + currentEra: number; + blocksPerEra: number; + }): Promise => { const eraTokenIssuances: { era: number; eraTokenIssuance: number }[] = []; const block7EraAgo = blockHeight - blocksPerEra * 7; const [hash, currentIssuance] = await Promise.all([ @@ -169,12 +169,12 @@ const formatStakerPendingRewards = ({ stakerInfo, eraTokenIssuances, eraRewards, rewardsDistributionConfig }: { - stakerInfo: StakerInfo[]; - eraTvls: EraTvl[]; - eraTokenIssuances: EraTokenIssuances[]; - eraRewards: number; - rewardsDistributionConfig: DistributionConfig; -}) => { + stakerInfo: StakerInfo[]; + eraTvls: EraTvl[]; + eraTokenIssuances: EraTokenIssuances[]; + eraRewards: number; + rewardsDistributionConfig: DistributionConfig; + }) => { return stakerInfo.map((it) => { const totalStaked = eraTvls[it.era].tvlLocked; const { baseStakerPercent, adjustablePercent, idealDappsStakingTvl } = rewardsDistributionConfig; @@ -199,9 +199,9 @@ const formatStakerPendingRewards = ({ stakerInfo, // In other words, as the number of unclaimed eras increases, the difference increases (but it shouldn't be too far away). export const estimatePendingRewards = async ({ api, walletAddress }: { - api: ApiPromise; - walletAddress: string; -}): Promise<{ stakerPendingRewards: number }> => { + api: ApiPromise; + walletAddress: string; + }): Promise<{ stakerPendingRewards: number }> => { try { const [eraInfo, generalStakerInfo, blockHeight, blocksPerEra, rawBlockRewards, rewardsDistributionConfig] = await Promise.all([ @@ -251,3 +251,110 @@ export const estimatePendingRewards = async ({ api, return { stakerPendingRewards: 0 }; } }; + +/** + * Memo: + * This method returns claimed reward amount by extrinsicHash and blockHeight that have the reward event. + * After StkingV3, Two kinds of rewards exist. + * - Staker Reward : Account has claimed some stake rewards. + * When "Build&Earn" comes after the "Voting", stakers can get the reward by staking on dApps. + * - Bonus Reward : Bonus reward has been paid out to a loyal staker. + * During the "Voting" subperiod makes the staker eligible for bonus rewards. + * - Dapp Reward : Dapp reward has been paid out to a beneficiary. + */ +export const getClaimedReward = async ( + { api, extrinsicHash, height }: + { + api: ApiPromise, extrinsicHash: string, height: number + }): Promise<{ claimedRewards: number }> => { + try { + const blockHash = await api.rpc.chain.getBlockHash(height); + const signedBlock = await api.rpc.chain.getBlock(blockHash); + const apiAt = await api.at(signedBlock.block.header.hash); + const allRecords = await apiAt.query.system.events>(); + + + // Find the extrinsic index by matching the extrinsic hash + const extrinsicIndex = signedBlock.block.extrinsics.findIndex( + (ex) => ex.hash.toString() === extrinsicHash + ); + + if (extrinsicIndex === -1) { + throw new Error('Extrinsic not found in the block'); + } + + // Get events associated with the extrinsic + const extrinsicEvents = allRecords.filter((record) => + record.phase.isApplyExtrinsic && + record.phase.asApplyExtrinsic.eq(extrinsicIndex) + ); + + const methodRwd = 'Reward'; + const methodBnsRwd = 'BonusReward'; + const methodDappRwd = 'DAppReward'; + const section = 'dappStaking'; + let claimedReward = BigInt('0'); + + extrinsicEvents.map((e) => { + if ((e.event?.method === methodRwd || e.event?.method === methodBnsRwd || e.event?.method === methodDappRwd) && + e.event?.section === section) { + const amountIdx = e.event?.data.length - 1; + claimedReward = claimedReward + BigInt(e.event?.data[amountIdx].toString()); + } + }); + + return { claimedRewards: Number(claimedReward) }; + } catch (error) { + console.log(error); + throw error; + } +}; + +/** + * Memo: + * This method returns usedfee amount by extrinsicHash and blockHeight that have the actualFee. + */ +export const getUsedFee = async ( + { api, extrinsicHash, height }: + { + api: ApiPromise, extrinsicHash: string, height: number + }): Promise<{ usedFee: number }> => { + try { + const blockHash = await api.rpc.chain.getBlockHash(height); + const signedBlock = await api.rpc.chain.getBlock(blockHash); + const apiAt = await api.at(signedBlock.block.header.hash); + const allRecords = await apiAt.query.system.events>(); + + // Find the extrinsic index by matching the extrinsic hash + const extrinsicIndex = signedBlock.block.extrinsics.findIndex( + (ex) => ex.hash.toString() === extrinsicHash + ); + + if (extrinsicIndex === -1) { + throw new Error('Extrinsic not found in the block'); + } + + // Get events associated with the extrinsic + const extrinsicEvents = allRecords.filter((record) => + record.phase.isApplyExtrinsic && + record.phase.asApplyExtrinsic.eq(extrinsicIndex) + ); + + const method = 'TransactionFeePaid'; + const section = 'transactionPayment'; + + let usedFee = BigInt('0'); + + extrinsicEvents.map((e) => { + if (e.event?.method === method && e.event?.section === section) { + const actualFeeIdx = e.event?.data.length - 2; + usedFee = usedFee + BigInt(e.event?.data[actualFeeIdx].toString()); + } + }); + + return { usedFee: Number(usedFee) }; + } catch (error) { + console.log(error); + throw error; + } +};