diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index 8d819bd974b..35c0f48182c 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -2301,18 +2301,9 @@ export interface AddressBalanceResult { // Use stringify to communicate, pure boolean value will error with case 'false' value export interface KoniRequestSignatures { // Bonding functions - 'pri(staking.submitTuringCancelCompound)': [RequestTuringCancelStakeCompound, SWTransactionResponse]; - 'pri(staking.submitTuringCompound)': [RequestTuringStakeCompound, SWTransactionResponse]; - 'pri(staking.submitClaimReward)': [RequestStakeClaimReward, SWTransactionResponse]; - 'pri(staking.submitCancelWithdrawal)': [RequestStakeCancelWithdrawal, SWTransactionResponse]; - 'pri(unbonding.submitTransaction)': [RequestUnbondingSubmit, SWTransactionResponse]; - 'pri(bonding.submitBondingTransaction)': [RequestBondingSubmit, SWTransactionResponse]; + // todo: Check UI if it's still using this subscription to handle logic. If not, remove 'pri(bonding.subscribeChainStakingMetadata)': [null, ChainStakingMetadata[], ChainStakingMetadata[]]; 'pri(bonding.subscribeNominatorMetadata)': [null, NominatorMetadata[], NominatorMetadata[]]; - 'pri(bonding.getBondingOptions)': [BondingOptionParams, ValidatorInfo[]]; - 'pri(bonding.getNominationPoolOptions)': [string, NominationPoolInfo[]]; - 'pri(bonding.nominationPool.submitBonding)': [RequestYieldStepSubmit, SWTransactionResponse]; - 'pri(bonding.nominationPool.submitUnbonding)': [RequestStakePoolingUnbonding, SWTransactionResponse]; // Chains, assets functions 'pri(chainService.subscribeChainInfoMap)': [null, Record, Record]; diff --git a/packages/extension-base/src/koni/api/staking/bonding/amplitude.ts b/packages/extension-base/src/koni/api/staking/bonding/amplitude.ts deleted file mode 100644 index 2bb7d6147df..00000000000 --- a/packages/extension-base/src/koni/api/staking/bonding/amplitude.ts +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { ChainStakingMetadata, NominationInfo, NominatorMetadata, StakingType, UnstakingInfo, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { BlockHeader, getBondedValidators, getEarningStatusByNominations, isUnstakeAll, ParachainStakingStakeOption } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; -import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; -import { EarningStatus, UnstakingStatus } from '@subwallet/extension-base/types'; -import { parseRawNumber, reformatAddress } from '@subwallet/extension-base/utils'; - -import { Codec } from '@polkadot/types/types'; -import { BN, BN_ZERO } from '@polkadot/util'; -import { isEthereumAddress } from '@polkadot/util-crypto'; - -interface InflationConfig { - collator: { - maxRate: string, - rewardRate: { - annual: string, - perBlock: string - } - }, - delegator: { - maxRate: string, - rewardRate: { - annual: string, - perBlock: string - } - } -} - -interface CollatorInfo { - id: string, - stake: string, - delegators: any[], - total: string, - status: string | Record -} - -export function subscribeAmplitudeStakingMetadata (chain: string, substrateApi: _SubstrateApi, callback: (chain: string, rs: ChainStakingMetadata) => void) { - return substrateApi.api.query.parachainStaking.round((_round: Codec) => { - const roundObj = _round.toHuman() as Record; - const round = parseRawNumber(roundObj.current); - const maxDelegations = substrateApi.api.consts.parachainStaking.maxDelegationsPerRound.toString(); - const minDelegatorStake = substrateApi.api.consts.parachainStaking.minDelegatorStake.toString(); - const unstakingDelay = substrateApi.api.consts.parachainStaking.stakeDuration.toString(); - const _blockPerRound = substrateApi.api.consts.parachainStaking.defaultBlocksPerRound.toString(); - const blockPerRound = parseFloat(_blockPerRound); - - const blockDuration = (_STAKING_ERA_LENGTH_MAP[chain] || _STAKING_ERA_LENGTH_MAP.default) / blockPerRound; - const unstakingPeriod = blockDuration * parseInt(unstakingDelay); - - callback(chain, { - chain, - type: StakingType.NOMINATED, - era: round, - minStake: minDelegatorStake, - maxValidatorPerNominator: parseInt(maxDelegations), - maxWithdrawalRequestPerValidator: 1, // by default - allowCancelUnstaking: true, - unstakingPeriod - }); - }); -} - -export async function getAmplitudeStakingMetadata (chain: string, substrateApi: _SubstrateApi): Promise { - const chainApi = await substrateApi.isReady; - - const _round = (await chainApi.api.query.parachainStaking.round()).toHuman() as Record; - const round = parseRawNumber(_round.current); - const maxDelegations = chainApi.api.consts.parachainStaking.maxDelegationsPerRound.toString(); - const minDelegatorStake = chainApi.api.consts.parachainStaking.minDelegatorStake.toString(); - const unstakingDelay = chainApi.api.consts.parachainStaking.stakeDuration.toString(); - const _blockPerRound = chainApi.api.consts.parachainStaking.defaultBlocksPerRound.toString(); - const blockPerRound = parseFloat(_blockPerRound); - - const blockDuration = (_STAKING_ERA_LENGTH_MAP[chain] || _STAKING_ERA_LENGTH_MAP.default) / blockPerRound; // in hours - const unstakingPeriod = blockDuration * parseInt(unstakingDelay); - - return { - chain, - type: StakingType.NOMINATED, - era: round, - minStake: minDelegatorStake, - maxValidatorPerNominator: parseInt(maxDelegations), - maxWithdrawalRequestPerValidator: 1, // by default - allowCancelUnstaking: true, - unstakingPeriod - } as ChainStakingMetadata; -} - -export async function subscribeAmplitudeNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi, delegatorState: ParachainStakingStakeOption[], unstakingInfo: Record) { - const nominationList: NominationInfo[] = []; - const unstakingList: UnstakingInfo[] = []; - const minDelegatorStake = substrateApi.api.consts.parachainStaking.minDelegatorStake.toString(); - const hasUnstakingInfo = unstakingInfo && Object.values(unstakingInfo).length > 0; - - let activeStake = '0'; - - if (delegatorState) { // delegatorState can be null while unstaking all - const identityPromises = delegatorState.map((delegate) => parseIdentity(substrateApi, delegate.owner)); - const identities = await Promise.all(identityPromises); - - for (let i = 0; i < delegatorState.length; i++) { - const delegate = delegatorState[i]; - const [identity] = identities[i]; - - activeStake = delegate.amount.toString(); - const bnActiveStake = new BN(activeStake); - let delegationStatus = EarningStatus.NOT_EARNING; - - if (bnActiveStake.gt(BN_ZERO) && bnActiveStake.gte(new BN(minDelegatorStake))) { - delegationStatus = EarningStatus.EARNING_REWARD; - } - - nominationList.push({ - status: delegationStatus, - chain: chainInfo.slug, - validatorAddress: delegate.owner, - activeStake: delegate.amount.toString(), - validatorMinStake: '0', - hasUnstaking: hasUnstakingInfo, - validatorIdentity: identity - }); - } - - if (hasUnstakingInfo) { - const _currentBlockInfo = await substrateApi.api.rpc.chain.getHeader(); - - const currentBlockInfo = _currentBlockInfo.toPrimitive() as unknown as BlockHeader; - const currentBlockNumber = currentBlockInfo.number; - - const _blockPerRound = substrateApi.api.consts.parachainStaking.defaultBlocksPerRound.toString(); - const blockPerRound = parseFloat(_blockPerRound); - - for (const [unstakingBlock, unstakingAmount] of Object.entries(unstakingInfo)) { - const blockDuration = (_STAKING_ERA_LENGTH_MAP[chainInfo.slug] || _STAKING_ERA_LENGTH_MAP.default) / blockPerRound; // in hours - - const isClaimable = parseInt(unstakingBlock) - currentBlockNumber < 0; - const remainingBlock = parseInt(unstakingBlock) - currentBlockNumber; - const waitingTime = remainingBlock * blockDuration; - - unstakingList.push({ - chain: chainInfo.slug, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: unstakingAmount.toString(), - waitingTime, - validatorAddress: undefined - }); - } - } - } - - const stakingStatus = getEarningStatusByNominations(new BN(activeStake), nominationList); - - return { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - status: stakingStatus, - address: address, - activeStake: activeStake, - nominations: nominationList, - unstakings: unstakingList - } as NominatorMetadata; -} - -/** - * Deprecated - * */ -export async function getAmplitudeNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi): Promise { - if (isEthereumAddress(address)) { - return; - } - - const chain = chainInfo.slug; - const chainApi = await substrateApi.isReady; - - const nominationList: NominationInfo[] = []; - const unstakingList: UnstakingInfo[] = []; - - const [_delegatorState, _unstakingInfo] = await Promise.all([ - chainApi.api.query.parachainStaking.delegatorState(address), - chainApi.api.query.parachainStaking.unstaking(address) - ]); - - const minDelegatorStake = chainApi.api.consts.parachainStaking.minDelegatorStake.toString(); - const delegatorState = _delegatorState.toPrimitive() as unknown as ParachainStakingStakeOption; - const unstakingInfo = _unstakingInfo.toPrimitive() as unknown as Record; - - if (!delegatorState && !unstakingInfo) { - return { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - address, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata; - } - - let activeStake = '0'; - - if (delegatorState) { // delegatorState can be null while unstaking all - const [identity] = await parseIdentity(substrateApi, delegatorState.owner); - - activeStake = delegatorState.amount.toString(); - const bnActiveStake = new BN(activeStake); - let delegationStatus: EarningStatus = EarningStatus.NOT_EARNING; - - if (bnActiveStake.gt(BN_ZERO) && bnActiveStake.gte(new BN(minDelegatorStake))) { - delegationStatus = EarningStatus.EARNING_REWARD; - } - - nominationList.push({ - status: delegationStatus, - chain, - validatorAddress: delegatorState.owner, - activeStake: delegatorState.amount.toString(), - validatorMinStake: '0', - hasUnstaking: !!unstakingInfo && Object.values(unstakingInfo).length > 0, - validatorIdentity: identity - }); - } - - if (unstakingInfo && Object.values(unstakingInfo).length > 0) { - const _currentBlockInfo = await chainApi.api.rpc.chain.getHeader(); - - const currentBlockInfo = _currentBlockInfo.toPrimitive() as unknown as BlockHeader; - const currentBlockNumber = currentBlockInfo.number; - - const _blockPerRound = chainApi.api.consts.parachainStaking.defaultBlocksPerRound.toString(); - const blockPerRound = parseFloat(_blockPerRound); - - const nearestUnstakingBlock = Object.keys(unstakingInfo)[0]; - const nearestUnstakingAmount = Object.values(unstakingInfo)[0]; - - const blockDuration = (_STAKING_ERA_LENGTH_MAP[chain] || _STAKING_ERA_LENGTH_MAP.default) / blockPerRound; // in hours - - const isClaimable = parseInt(nearestUnstakingBlock) - currentBlockNumber < 0; - const remainingBlock = parseInt(nearestUnstakingBlock) - currentBlockNumber; - const waitingTime = remainingBlock * blockDuration; - - unstakingList.push({ - chain, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: nearestUnstakingAmount.toString(), - waitingTime, - validatorAddress: delegatorState?.owner || undefined - }); - } - - if (nominationList.length === 0 && unstakingList.length === 0) { - return; - } - - const stakingStatus = getEarningStatusByNominations(new BN(activeStake), nominationList); - - return { - chain, - type: StakingType.NOMINATED, - status: stakingStatus, - address: address, - activeStake: activeStake, - nominations: nominationList, - unstakings: unstakingList - } as NominatorMetadata; -} - -export async function getAmplitudeCollatorsInfo (chain: string, substrateApi: _SubstrateApi): Promise { - const chainApi = await substrateApi.isReady; - - // Noted: Krest do not have reward - if (_STAKING_CHAIN_GROUP.krest_network.includes(chain)) { - const _allCollators = await chainApi.api.query.parachainStaking.candidatePool.entries(); - const maxDelegatorsPerCollator = chainApi.api.consts.parachainStaking.maxDelegatorsPerCollator.toString(); - const allCollators: ValidatorInfo[] = []; - - for (const _collator of _allCollators) { - const collatorInfo = _collator[1].toPrimitive() as unknown as CollatorInfo; - - const bnTotalStake = new BN(collatorInfo.total); - const bnOwnStake = new BN(collatorInfo.stake); - const bnOtherStake = bnTotalStake.sub(bnOwnStake); - - allCollators.push({ - address: collatorInfo.id, - totalStake: bnTotalStake.toString(), - ownStake: bnOwnStake.toString(), - otherStake: bnOtherStake.toString(), - nominatorCount: collatorInfo.delegators.length, - commission: 0, - blocked: false, - isVerified: false, - minBond: '0', - chain, - isCrowded: collatorInfo.delegators.length >= parseInt(maxDelegatorsPerCollator) - }); - } - - return allCollators; - } else { - const [_allCollators, _inflationConfig] = await Promise.all([ - chainApi.api.query.parachainStaking.candidatePool.entries(), - chainApi.api.query.parachainStaking.inflationConfig() - ]); - - const maxDelegatorsPerCollator = chainApi.api.consts.parachainStaking.maxDelegatorsPerCollator.toString(); - const inflationConfig = _inflationConfig.toHuman() as unknown as InflationConfig; - const rawDelegatorReturn = inflationConfig.delegator.rewardRate.annual; - const delegatorReturn = parseFloat(rawDelegatorReturn.split('%')[0]); - const allCollators: ValidatorInfo[] = []; - - for (const _collator of _allCollators) { - const collatorInfo = _collator[1].toPrimitive() as unknown as CollatorInfo; - - const bnTotalStake = new BN(collatorInfo.total); - const bnOwnStake = new BN(collatorInfo.stake); - const bnOtherStake = bnTotalStake.sub(bnOwnStake); - - allCollators.push({ - address: collatorInfo.id, - totalStake: bnTotalStake.toString(), - ownStake: bnOwnStake.toString(), - otherStake: bnOtherStake.toString(), - nominatorCount: collatorInfo.delegators.length, - commission: 0, - expectedReturn: delegatorReturn, - blocked: false, - isVerified: false, - minBond: '0', - chain, - isCrowded: collatorInfo.delegators.length >= parseInt(maxDelegatorsPerCollator) - }); - } - - return allCollators; - } -} - -export async function getAmplitudeBondingExtrinsic (substrateApi: _SubstrateApi, amount: string, selectedValidatorInfo: ValidatorInfo, nominatorMetadata?: NominatorMetadata) { - const chainApi = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - if (!nominatorMetadata) { - return chainApi.api.tx.parachainStaking.joinDelegators(selectedValidatorInfo.address, binaryAmount); - } - - const { bondedValidators } = getBondedValidators(nominatorMetadata.nominations); - - if (!bondedValidators.includes(reformatAddress(selectedValidatorInfo.address, 0))) { - return chainApi.api.tx.parachainStaking.joinDelegators(selectedValidatorInfo.address, binaryAmount); - } else { - const _params = chainApi.api.tx.parachainStaking.delegatorStakeMore.toJSON() as Record; - const paramsCount = (_params.args as any[]).length; - - if (paramsCount === 2) { // detect number of params - return chainApi.api.tx.parachainStaking.delegatorStakeMore(selectedValidatorInfo.address, binaryAmount); - } else { - return chainApi.api.tx.parachainStaking.delegatorStakeMore(binaryAmount); - } - } -} - -export async function getAmplitudeUnbondingExtrinsic (substrateApi: _SubstrateApi, amount: string, nominatorMetadata: NominatorMetadata, collatorAddress: string) { - const chainApi = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - const unstakeAll = isUnstakeAll(collatorAddress, nominatorMetadata.nominations, amount); - - if (!unstakeAll) { - const _params = chainApi.api.tx.parachainStaking.delegatorStakeMore.toJSON() as Record; - const paramsCount = (_params.args as any[]).length; - - if (paramsCount === 2) { - return chainApi.api.tx.parachainStaking.delegatorStakeLess(collatorAddress, binaryAmount); - } else { - return chainApi.api.tx.parachainStaking.delegatorStakeLess(binaryAmount); - } - } else { - return chainApi.api.tx.parachainStaking.leaveDelegators(); - } -} - -export async function getAmplitudeWithdrawalExtrinsic (substrateApi: _SubstrateApi, address: string) { - const chainApi = await substrateApi.isReady; - - return chainApi.api.tx.parachainStaking.unlockUnstaked(address); -} - -export async function getAmplitudeClaimRewardExtrinsic (substrateApi: _SubstrateApi) { - const chainApi = await substrateApi.isReady; - - return chainApi.api.tx.utility.batch([ - chainApi.api.tx.parachainStaking.incrementDelegatorRewards(), - chainApi.api.tx.parachainStaking.claimRewards() - ]); -} diff --git a/packages/extension-base/src/koni/api/staking/bonding/astar.ts b/packages/extension-base/src/koni/api/staking/bonding/astar.ts deleted file mode 100644 index cc308c5a09d..00000000000 --- a/packages/extension-base/src/koni/api/staking/bonding/astar.ts +++ /dev/null @@ -1,452 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { ChainStakingMetadata, NominationInfo, NominatorMetadata, StakingType, UnstakingInfo, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getEarningStatusByNominations, PalletDappsStakingAccountLedger, PalletDappsStakingDappInfo } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; -import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { EarningStatus, UnstakingStatus } from '@subwallet/extension-base/types'; -import { isUrl, parseRawNumber } from '@subwallet/extension-base/utils'; - -import { SubmittableExtrinsic } from '@polkadot/api/promise/types'; -import { Codec } from '@polkadot/types/types'; -import { BN, BN_ZERO } from '@polkadot/util'; -import { isEthereumAddress } from '@polkadot/util-crypto'; - -const convertAddress = (address: string) => { - return isEthereumAddress(address) ? address.toLowerCase() : address; -}; - -const fetchDApps = async (network: string) => { - return new Promise(function (resolve) { - fetch(`https://api.astar.network/api/v1/${network}/dapps-staking/dappssimple`, { - method: 'GET' - }).then((resp) => { - resolve(resp.json()); - }).catch(console.error); - }); -}; - -export function subscribeAstarStakingMetadata (chain: string, substrateApi: _SubstrateApi, callback: (chain: string, rs: ChainStakingMetadata) => void) { - return substrateApi.api.query.dappsStaking.currentEra((_currentEra: Codec) => { - const era = _currentEra.toString(); - const minDelegatorStake = substrateApi.api.consts.dappsStaking.minimumStakingAmount.toString(); - const unstakingDelay = substrateApi.api.consts.dappsStaking.unbondingPeriod.toString(); - - const unstakingPeriod = parseInt(unstakingDelay) * _STAKING_ERA_LENGTH_MAP[chain]; - - callback(chain, { - chain, - type: StakingType.NOMINATED, - era: parseInt(era), - minStake: minDelegatorStake, - maxValidatorPerNominator: 100, // temporary fix for Astar, there's no limit for now - maxWithdrawalRequestPerValidator: 1, // by default - allowCancelUnstaking: false, - unstakingPeriod - }); - }); -} - -export async function getAstarStakingMetadata (chain: string, substrateApi: _SubstrateApi): Promise { - const aprPromise = new Promise(function (resolve) { - fetch(`https://api.astar.network/api/v1/${chain}/dapps-staking/apr`, { - method: 'GET' - }).then((resp) => { - resolve(resp.json()); - }).catch(console.error); - }); - - const timeout = new Promise((resolve) => { - const id = setTimeout(() => { - clearTimeout(id); - resolve(null); - }, 8000); - }); - - const aprRacePromise = Promise.race([ - timeout, - aprPromise - ]); // need race because API often timeout - - const [aprInfo, chainApi] = await Promise.all([ - aprRacePromise, - substrateApi.isReady - ]); - - const era = (await chainApi.api.query.dappsStaking.currentEra()).toString(); - const minDelegatorStake = chainApi.api.consts.dappsStaking.minimumStakingAmount.toString(); - const unstakingDelay = chainApi.api.consts.dappsStaking.unbondingPeriod.toString(); - - const unstakingPeriod = parseInt(unstakingDelay) * _STAKING_ERA_LENGTH_MAP[chain]; - - return { - chain, - type: StakingType.NOMINATED, - expectedReturn: aprInfo !== null ? aprInfo as number : undefined, - era: parseInt(era), - minStake: minDelegatorStake, - maxValidatorPerNominator: 100, // temporary fix for Astar, there's no limit for now - maxWithdrawalRequestPerValidator: 1, // by default - allowCancelUnstaking: false, - unstakingPeriod - } as ChainStakingMetadata; -} - -export async function subscribeAstarNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi, ledger: PalletDappsStakingAccountLedger) { - const nominationList: NominationInfo[] = []; - const unstakingList: UnstakingInfo[] = []; - - const allDappsReq = fetchDApps(chainInfo.slug); - - const [_allDapps, _era, _stakerInfo] = await Promise.all([ - allDappsReq, - substrateApi.api.query.dappsStaking.currentEra(), - substrateApi.api.query.dappsStaking.generalStakerInfo.entries(address) - ]); - - const currentEra = _era.toString(); - const minDelegatorStake = substrateApi.api.consts.dappsStaking.minimumStakingAmount.toString(); - const allDapps = _allDapps as PalletDappsStakingDappInfo[]; - - let bnTotalActiveStake = BN_ZERO; - - if (_stakerInfo.length > 0) { - const dAppInfoMap: Record = {}; - - allDapps.forEach((dappInfo) => { - dAppInfoMap[convertAddress(dappInfo.address)] = dappInfo; - }); - - for (const item of _stakerInfo) { - const data = item[0].toHuman() as unknown as any[]; - const stakedDapp = data[1] as Record; - const stakeData = item[1].toPrimitive() as Record[]>; - const stakeList = stakeData.stakes; - - const _dappAddress = stakedDapp.Evm ? stakedDapp.Evm.toLowerCase() : stakedDapp.Wasm; - const dappAddress = convertAddress(_dappAddress); - const currentStake = stakeList.slice(-1)[0].staked.toString() || '0'; - - const bnCurrentStake = new BN(currentStake); - - if (bnCurrentStake.gt(BN_ZERO)) { - const dappStakingStatus = bnCurrentStake.gt(BN_ZERO) && bnCurrentStake.gte(new BN(minDelegatorStake)) ? EarningStatus.EARNING_REWARD : EarningStatus.NOT_EARNING; - - bnTotalActiveStake = bnTotalActiveStake.add(bnCurrentStake); - const dappInfo = dAppInfoMap[dappAddress]; - - nominationList.push({ - status: dappStakingStatus, - chain: chainInfo.slug, - validatorAddress: dappAddress, - activeStake: currentStake, - validatorMinStake: '0', - validatorIdentity: dappInfo?.name, - hasUnstaking: false // cannot get unstaking info by dapp - }); - } - } - } - - const unlockingChunks = ledger.unbondingInfo.unlockingChunks; - - if (unlockingChunks.length > 0) { - for (const unlockingChunk of unlockingChunks) { - const isClaimable = unlockingChunk.unlockEra - parseInt(currentEra) < 0; - const remainingEra = unlockingChunk.unlockEra - parseInt(currentEra); - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chainInfo.slug]; - - unstakingList.push({ - chain: chainInfo.slug, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: unlockingChunk.amount.toString(), - waitingTime - }); - } - } - - if (nominationList.length === 0 && unstakingList.length === 0) { - return { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - address, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata; - } - - const stakingStatus = getEarningStatusByNominations(bnTotalActiveStake, nominationList); - - return { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - address: address, - activeStake: bnTotalActiveStake.toString(), - nominations: nominationList, - unstakings: unstakingList, - status: stakingStatus - } as NominatorMetadata; -} - -/** - * Deprecated - * */ -export async function getAstarNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi): Promise { - if (isEthereumAddress(address)) { - return; - } - - const chain = chainInfo.slug; - const chainApi = await substrateApi.isReady; - - const nominationList: NominationInfo[] = []; - const unstakingList: UnstakingInfo[] = []; - - const allDappsReq = fetchDApps(chain); - - const [_ledger, _era, _stakerInfo] = await Promise.all([ - chainApi.api.query.dappsStaking.ledger(address), - chainApi.api.query.dappsStaking.currentEra(), - chainApi.api.query.dappsStaking.generalStakerInfo.entries(address) - ]); - - const ledger = _ledger.toPrimitive() as unknown as PalletDappsStakingAccountLedger; - const currentEra = _era.toString(); - const minDelegatorStake = chainApi.api.consts.dappsStaking.minimumStakingAmount.toString(); - - let bnTotalActiveStake = BN_ZERO; - - if (_stakerInfo.length > 0) { - const dAppInfoMap: Record = {}; - const allDapps = await allDappsReq as PalletDappsStakingDappInfo[]; - - allDapps.forEach((dappInfo) => { - const address = isEthereumAddress(dappInfo.address) ? dappInfo.address.toLowerCase() : dappInfo.address; - - dAppInfoMap[address] = dappInfo; - }); - - for (const item of _stakerInfo) { - const data = item[0].toHuman() as unknown as any[]; - const stakedDapp = data[1] as Record; - const stakeData = item[1].toPrimitive() as Record[]>; - const stakeList = stakeData.stakes; - - const dappAddress = convertAddress(stakedDapp.Evm); - const currentStake = stakeList.slice(-1)[0].staked.toString() || '0'; - - const bnCurrentStake = new BN(currentStake); - - if (bnCurrentStake.gt(BN_ZERO)) { - const dappStakingStatus = bnCurrentStake.gt(BN_ZERO) && bnCurrentStake.gte(new BN(minDelegatorStake)) ? EarningStatus.EARNING_REWARD : EarningStatus.NOT_EARNING; - - bnTotalActiveStake = bnTotalActiveStake.add(bnCurrentStake); - const dappInfo = dAppInfoMap[dappAddress]; - - nominationList.push({ - status: dappStakingStatus, - chain, - validatorAddress: dappAddress, - activeStake: currentStake, - validatorMinStake: '0', - validatorIdentity: dappInfo?.name, - hasUnstaking: false // cannot get unstaking info by dapp - }); - } - } - } - - const unlockingChunks = ledger.unbondingInfo.unlockingChunks; - - if (unlockingChunks.length > 0) { - for (const unlockingChunk of unlockingChunks) { - const isClaimable = unlockingChunk.unlockEra - parseInt(currentEra) < 0; - const remainingEra = unlockingChunk.unlockEra - parseInt(currentEra); - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chain]; - - unstakingList.push({ - chain, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: unlockingChunk.amount.toString(), - waitingTime - }); - } - } - - if (nominationList.length === 0 && unstakingList.length === 0) { - return { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - address, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata; - } - - const stakingStatus = getEarningStatusByNominations(bnTotalActiveStake, nominationList); - - return { - chain, - type: StakingType.NOMINATED, - address: address, - activeStake: bnTotalActiveStake.toString(), - nominations: nominationList, - unstakings: unstakingList, - status: stakingStatus - } as NominatorMetadata; -} - -export async function getAstarDappsInfo (networkKey: string, substrateApi: _SubstrateApi) { - const chainApi = await substrateApi.isReady; - const rawMaxStakerPerContract = (chainApi.api.consts.dappsStaking.maxNumberOfStakersPerContract).toHuman() as string; - - const allDappsInfo: ValidatorInfo[] = []; - const maxStakerPerContract = parseRawNumber(rawMaxStakerPerContract); - - const allDappsReq = fetchDApps(networkKey); - - const [_era, _allDapps] = await Promise.all([ - chainApi.api.query.dappsStaking.currentEra(), - allDappsReq - ]); - - const era = parseRawNumber(_era.toHuman() as string); - const allDapps = _allDapps as Record[]; - - await Promise.all(allDapps.map(async (dapp) => { - const dappName = dapp.name as string; - const dappAddress = dapp.address as string; - const dappIcon = isUrl(dapp.iconUrl as string) ? dapp.iconUrl as string : undefined; - const contractParam = isEthereumAddress(dappAddress) ? { Evm: dappAddress } : { Wasm: dappAddress }; - const _contractInfo = await chainApi.api.query.dappsStaking.contractEraStake(contractParam, era); - const contractInfo = _contractInfo.toPrimitive() as Record; - let totalStake = '0'; - let stakerCount = 0; - - if (contractInfo !== null) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - totalStake = contractInfo?.total?.toString(); - stakerCount = contractInfo.numberOfStakers as number; - } - - allDappsInfo.push({ - commission: 0, - expectedReturn: 0, - address: convertAddress(dappAddress), - totalStake: totalStake, - ownStake: '0', - otherStake: totalStake.toString(), - nominatorCount: stakerCount, - blocked: false, - isVerified: false, - minBond: '0', - icon: dappIcon, - identity: dappName, - chain: networkKey, - isCrowded: stakerCount >= maxStakerPerContract - }); - })); - - return allDappsInfo; -} - -export async function getAstarBondingExtrinsic (substrateApi: _SubstrateApi, amount: string, dappInfo: ValidatorInfo) { - const chainApi = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - const dappParam = isEthereumAddress(dappInfo.address) ? { Evm: dappInfo.address } : { Wasm: dappInfo.address }; - - return chainApi.api.tx.dappsStaking.bondAndStake(dappParam, binaryAmount); -} - -export async function getAstarUnbondingExtrinsic (substrateApi: _SubstrateApi, amount: string, dappAddress: string) { - const apiPromise = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - const dappParam = isEthereumAddress(dappAddress) ? { Evm: dappAddress } : { Wasm: dappAddress }; - - return apiPromise.api.tx.dappsStaking.unbondAndUnstake(dappParam, binaryAmount); -} - -export async function getAstarWithdrawalExtrinsic (substrateApi: _SubstrateApi) { - const chainApi = await substrateApi.isReady; - - return chainApi.api.tx.dappsStaking.withdrawUnbonded(); -} - -export async function getAstarClaimRewardExtrinsic (substrateApi: _SubstrateApi, address: string) { - const apiPromise = await substrateApi.isReady; - - const [_stakedDapps, _currentEra] = await Promise.all([ - apiPromise.api.query.dappsStaking.generalStakerInfo.entries(address), - apiPromise.api.query.dappsStaking.currentEra() - ]); - - const currentEra = parseRawNumber(_currentEra.toHuman() as string); - const transactions: SubmittableExtrinsic[] = []; - - for (const item of _stakedDapps) { - const data = item[0].toHuman() as any[]; - const stakedDapp = data[1] as Record; - const stakeData = item[1].toHuman() as Record[]>; - const stakes = stakeData.stakes; - const dappAddress = isEthereumAddress(stakedDapp.Evm) ? stakedDapp.Evm.toLowerCase() : stakedDapp.Evm; - - let numberOfUnclaimedEra = 0; - const maxTx = 50; - - for (let i = 0; i < stakes.length; i++) { - const { era, staked } = stakes[i]; - const bnStaked = new BN(staked.replaceAll(',', '')); - const parsedEra = parseRawNumber(era); - - if (bnStaked.eq(new BN(0))) { - continue; - } - - const nextEraData = stakes[i + 1] ?? null; - const nextEra = nextEraData && parseRawNumber(nextEraData.era); - const isLastEra = i === stakes.length - 1; - const eraToClaim = isLastEra ? currentEra - parsedEra : nextEra - parsedEra; - - numberOfUnclaimedEra += eraToClaim; - } - - const dappParam = isEthereumAddress(dappAddress) ? { Evm: dappAddress } : { Wasm: dappAddress }; - - for (let i = 0; i < Math.min(numberOfUnclaimedEra, maxTx); i++) { - const tx = apiPromise.api.tx.dappsStaking.claimStaker(dappParam); - - transactions.push(tx); - } - } - - return apiPromise.api.tx.utility.batch(transactions); -} - -export function getAstarWithdrawable (nominatorMetadata: NominatorMetadata): UnstakingInfo { - const unstakingInfo: UnstakingInfo = { - chain: nominatorMetadata.chain, - status: UnstakingStatus.CLAIMABLE, - claimable: '0', - waitingTime: 0 - }; - - let bnWithdrawable = BN_ZERO; - - for (const unstaking of nominatorMetadata.unstakings) { - if (unstaking.status === UnstakingStatus.CLAIMABLE) { - bnWithdrawable = bnWithdrawable.add(new BN(unstaking.claimable)); - } - } - - unstakingInfo.claimable = bnWithdrawable.toString(); - - return unstakingInfo; -} diff --git a/packages/extension-base/src/koni/api/staking/bonding/index.ts b/packages/extension-base/src/koni/api/staking/bonding/index.ts deleted file mode 100644 index fa8d42c154f..00000000000 --- a/packages/extension-base/src/koni/api/staking/bonding/index.ts +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; -import { ChainStakingMetadata, NominatorMetadata, StakingType, UnstakingInfo, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getAmplitudeBondingExtrinsic, getAmplitudeClaimRewardExtrinsic, getAmplitudeCollatorsInfo, getAmplitudeNominatorMetadata, getAmplitudeStakingMetadata, getAmplitudeUnbondingExtrinsic, getAmplitudeWithdrawalExtrinsic, subscribeAmplitudeStakingMetadata } from '@subwallet/extension-base/koni/api/staking/bonding/amplitude'; -import { getAstarBondingExtrinsic, getAstarClaimRewardExtrinsic, getAstarDappsInfo, getAstarNominatorMetadata, getAstarStakingMetadata, getAstarUnbondingExtrinsic, getAstarWithdrawalExtrinsic, subscribeAstarStakingMetadata } from '@subwallet/extension-base/koni/api/staking/bonding/astar'; -import { getParaBondingExtrinsic, getParaCancelWithdrawalExtrinsic, getParachainCollatorsInfo, getParaChainNominatorMetadata, getParaChainStakingMetadata, getParaUnbondingExtrinsic, getParaWithdrawalExtrinsic, subscribeParaChainStakingMetadata, validateParaChainBondingCondition, validateParaChainUnbondingCondition } from '@subwallet/extension-base/koni/api/staking/bonding/paraChain'; -import { getPoolingClaimRewardExtrinsic, getPoolingWithdrawalExtrinsic, getRelayBondingExtrinsic, getRelayCancelWithdrawalExtrinsic, getRelayChainNominatorMetadata, getRelayChainStakingMetadata, getRelayPoolsInfo, getRelayUnbondingExtrinsic, getRelayValidatorsInfo, getRelayWithdrawalExtrinsic, subscribeRelayChainStakingMetadata, validateRelayBondingCondition, validateRelayUnbondingCondition } from '@subwallet/extension-base/koni/api/staking/bonding/relayChain'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; - -// all addresses must be converted to its chain format - -export function validateUnbondingCondition (nominatorMetadata: NominatorMetadata, amount: string, chain: string, chainStakingMetadata: ChainStakingMetadata, selectedValidator?: string): TransactionError[] { - if (nominatorMetadata.type === StakingType.LIQUID_STAKING) { - return []; - } - - if (_STAKING_CHAIN_GROUP.relay.includes(chain)) { - return validateRelayUnbondingCondition(amount, chainStakingMetadata, nominatorMetadata); - } - - return validateParaChainUnbondingCondition(amount, nominatorMetadata, chainStakingMetadata, selectedValidator as string); -} - -export function validateBondingCondition (chainInfo: _ChainInfo, amount: string, selectedValidators: ValidatorInfo[], address: string, chainStakingMetadata: ChainStakingMetadata, nominatorMetadata?: NominatorMetadata): TransactionError[] { - if (_STAKING_CHAIN_GROUP.relay.includes(chainInfo.slug)) { - return validateRelayBondingCondition(chainInfo, amount, selectedValidators, address, chainStakingMetadata, nominatorMetadata); - } - - return validateParaChainBondingCondition(chainInfo, amount, selectedValidators, address, chainStakingMetadata, nominatorMetadata); -} - -/** Deprecated */ -export async function getChainStakingMetadata (chainInfo: _ChainInfo, substrateApi: _SubstrateApi): Promise { - if (_STAKING_CHAIN_GROUP.astar.includes(chainInfo.slug)) { - return getAstarStakingMetadata(chainInfo.slug, substrateApi); - } else if (_STAKING_CHAIN_GROUP.para.includes(chainInfo.slug)) { - return getParaChainStakingMetadata(chainInfo.slug, substrateApi); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chainInfo.slug)) { - return getAmplitudeStakingMetadata(chainInfo.slug, substrateApi); - } - - return getRelayChainStakingMetadata(chainInfo, substrateApi); -} - -/** - * Deprecated - * */ -export async function getNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi): Promise { - if (_STAKING_CHAIN_GROUP.astar.includes(chainInfo.slug)) { - return getAstarNominatorMetadata(chainInfo, address, substrateApi); - } else if (_STAKING_CHAIN_GROUP.para.includes(chainInfo.slug)) { - return getParaChainNominatorMetadata(chainInfo, address, substrateApi); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chainInfo.slug)) { - return getAmplitudeNominatorMetadata(chainInfo, address, substrateApi); - } - - return getRelayChainNominatorMetadata(chainInfo, address, substrateApi); -} - -export async function getValidatorsInfo (networkKey: string, substrateApi: _SubstrateApi, decimals: number, chainStakingMetadata: ChainStakingMetadata): Promise { - if (_STAKING_CHAIN_GROUP.para.includes(networkKey)) { - return getParachainCollatorsInfo(networkKey, substrateApi); - } else if (_STAKING_CHAIN_GROUP.astar.includes(networkKey)) { - return getAstarDappsInfo(networkKey, substrateApi); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(networkKey)) { - return getAmplitudeCollatorsInfo(networkKey, substrateApi); - } - - return getRelayValidatorsInfo(networkKey, substrateApi, decimals, chainStakingMetadata); -} - -export async function getNominationPoolsInfo (chain: string, substrateApi: _SubstrateApi) { - return getRelayPoolsInfo(chain, substrateApi); -} - -export async function getBondingExtrinsic (chainInfo: _ChainInfo, amount: string, selectedValidators: ValidatorInfo[], substrateApi: _SubstrateApi, address: string, nominatorMetadata?: NominatorMetadata) { - if (_STAKING_CHAIN_GROUP.para.includes(chainInfo.slug)) { - return getParaBondingExtrinsic(chainInfo, substrateApi, amount, selectedValidators[0], nominatorMetadata); // only select 1 validator at a time - } else if (_STAKING_CHAIN_GROUP.astar.includes(chainInfo.slug)) { - return getAstarBondingExtrinsic(substrateApi, amount, selectedValidators[0]); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chainInfo.slug)) { - return getAmplitudeBondingExtrinsic(substrateApi, amount, selectedValidators[0], nominatorMetadata); - } - - return getRelayBondingExtrinsic(substrateApi, amount, selectedValidators, chainInfo, address, nominatorMetadata); -} - -export async function getUnbondingExtrinsic (nominatorMetadata: NominatorMetadata, amount: string, chain: string, substrateApi: _SubstrateApi, selectedValidator?: string) { - if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - return getParaUnbondingExtrinsic(substrateApi, amount, nominatorMetadata, selectedValidator as string); - } else if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - return getAstarUnbondingExtrinsic(substrateApi, amount, selectedValidator as string); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - return getAmplitudeUnbondingExtrinsic(substrateApi, amount, nominatorMetadata, selectedValidator as string); - } - - return getRelayUnbondingExtrinsic(substrateApi, amount, nominatorMetadata); -} - -export async function getWithdrawalExtrinsic (substrateApi: _SubstrateApi, chain: string, nominatorMetadata: NominatorMetadata, validatorAddress?: string) { - if (nominatorMetadata.type === StakingType.POOLED) { - return getPoolingWithdrawalExtrinsic(substrateApi, nominatorMetadata); - } - - if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - return getParaWithdrawalExtrinsic(substrateApi, nominatorMetadata.address, validatorAddress as string); - } else if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - return getAstarWithdrawalExtrinsic(substrateApi); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - return getAmplitudeWithdrawalExtrinsic(substrateApi, nominatorMetadata.address); - } - - return getRelayWithdrawalExtrinsic(substrateApi, nominatorMetadata.address); -} - -export async function getClaimRewardExtrinsic (substrateApi: _SubstrateApi, chain: string, address: string, stakingType: StakingType, bondReward = true) { - if (stakingType === StakingType.POOLED) { - return getPoolingClaimRewardExtrinsic(substrateApi, bondReward); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - return getAmplitudeClaimRewardExtrinsic(substrateApi); - } - - return getAstarClaimRewardExtrinsic(substrateApi, address); -} - -export async function getCancelWithdrawalExtrinsic (substrateApi: _SubstrateApi, chain: string, selectedUnstaking: UnstakingInfo) { - if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - return getParaCancelWithdrawalExtrinsic(substrateApi, selectedUnstaking); - } - - return getRelayCancelWithdrawalExtrinsic(substrateApi, selectedUnstaking); -} - -export function subscribeEssentialChainStakingMetadata (substrateApiMap: Record, chainInfoMap: Record, callback: (chain: string, rs: ChainStakingMetadata) => void) { - const unsubList: VoidFunction[] = []; - - // TODO: replace with for of to improve performance - // eslint-disable-next-line @typescript-eslint/no-misused-promises - Object.values(chainInfoMap).forEach(async (chainInfo: _ChainInfo) => { - if (!substrateApiMap[chainInfo.slug]) { - return; - } - - const substrateApi = await substrateApiMap[chainInfo.slug].isReady; - - if (_STAKING_CHAIN_GROUP.astar.includes(chainInfo.slug)) { - const unsub = await subscribeAstarStakingMetadata(chainInfo.slug, substrateApi, callback); - - // @ts-ignore - unsubList.push(unsub); - } else if (_STAKING_CHAIN_GROUP.para.includes(chainInfo.slug)) { - const unsub = await subscribeParaChainStakingMetadata(chainInfo.slug, substrateApi, callback); - - // @ts-ignore - unsubList.push(unsub); - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chainInfo.slug)) { - const unsub = await subscribeAmplitudeStakingMetadata(chainInfo.slug, substrateApi, callback); - - // @ts-ignore - unsubList.push(unsub); - } else if (_STAKING_CHAIN_GROUP.relay.includes(chainInfo.slug)) { - const unsub = await subscribeRelayChainStakingMetadata(chainInfo, substrateApi, callback); - - // @ts-ignore - unsubList.push(unsub); - } - }); - - return () => { - unsubList.forEach((unsub) => { - unsub && unsub(); - }); - }; -} diff --git a/packages/extension-base/src/koni/api/staking/bonding/paraChain.ts b/packages/extension-base/src/koni/api/staking/bonding/paraChain.ts deleted file mode 100644 index 2a0983fb043..00000000000 --- a/packages/extension-base/src/koni/api/staking/bonding/paraChain.ts +++ /dev/null @@ -1,539 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; -import { ChainStakingMetadata, NominationInfo, NominatorMetadata, StakingType, UnstakingInfo, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getBondedValidators, getEarningStatusByNominations, getExistUnstakeErrorMessage, getMaxValidatorErrorMessage, getMinStakeErrorMessage, getParaCurrentInflation, InflationConfig, isUnstakeAll, PalletParachainStakingDelegationRequestsScheduledRequest, PalletParachainStakingDelegator, ParachainStakingCandidateMetadata, TuringOptimalCompoundFormat } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; -import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; -import { parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; -import { BasicTxErrorType, EarningStatus, StakingTxErrorType, UnstakingStatus } from '@subwallet/extension-base/types'; -import { isSameAddress, parseRawNumber, reformatAddress } from '@subwallet/extension-base/utils'; - -import { Codec } from '@polkadot/types/types'; -import { BN, BN_ZERO } from '@polkadot/util'; -import { isEthereumAddress } from '@polkadot/util-crypto'; - -interface CollatorExtraInfo { - active: boolean, - identity?: string, - isVerified: boolean, -} - -export function validateParaChainUnbondingCondition (amount: string, nominatorMetadata: NominatorMetadata, chainStakingMetadata: ChainStakingMetadata, selectedCollator: string): TransactionError[] { - const errors: TransactionError[] = []; - let targetNomination: NominationInfo | undefined; - - for (const nomination of nominatorMetadata.nominations) { - if (isSameAddress(nomination.validatorAddress, selectedCollator)) { - targetNomination = nomination; - - break; - } - } - - if (!targetNomination) { - errors.push(new TransactionError(BasicTxErrorType.INTERNAL_ERROR)); - - return errors; - } - - const bnActiveStake = new BN(targetNomination.activeStake); - const bnRemainingStake = bnActiveStake.sub(new BN(amount)); - - const bnChainMinStake = new BN(chainStakingMetadata.minStake || '0'); - const bnCollatorMinStake = new BN(targetNomination.validatorMinStake || '0'); - const bnMinStake = BN.max(bnCollatorMinStake, bnChainMinStake); - const existUnstakeErrorMessage = getExistUnstakeErrorMessage(chainStakingMetadata.chain, nominatorMetadata?.type); - - if (targetNomination.hasUnstaking) { - errors.push(new TransactionError(StakingTxErrorType.EXIST_UNSTAKING_REQUEST, existUnstakeErrorMessage)); - } - - if (!(bnRemainingStake.isZero() || bnRemainingStake.gte(bnMinStake))) { - errors.push(new TransactionError(StakingTxErrorType.INVALID_ACTIVE_STAKE)); - } - - return errors; -} - -export function validateParaChainBondingCondition (chainInfo: _ChainInfo, amount: string, selectedCollators: ValidatorInfo[], address: string, chainStakingMetadata: ChainStakingMetadata, nominatorMetadata?: NominatorMetadata): TransactionError[] { - const errors: TransactionError[] = []; - const selectedCollator = selectedCollators[0]; - let bnTotalStake = new BN(amount); - const bnChainMinStake = new BN(chainStakingMetadata.minStake || '0'); - const bnCollatorMinStake = new BN(selectedCollator.minBond || '0'); - const bnMinStake = bnCollatorMinStake > bnChainMinStake ? bnCollatorMinStake : bnChainMinStake; - const minStakeErrorMessage = getMinStakeErrorMessage(chainInfo, bnMinStake); - const maxValidatorErrorMessage = getMaxValidatorErrorMessage(chainInfo, chainStakingMetadata.maxValidatorPerNominator); - const existUnstakeErrorMessage = getExistUnstakeErrorMessage(chainInfo.slug, nominatorMetadata?.type, true); - - if (!nominatorMetadata || nominatorMetadata.status === EarningStatus.NOT_STAKING) { - if (!bnTotalStake.gte(bnMinStake)) { - errors.push(new TransactionError(StakingTxErrorType.NOT_ENOUGH_MIN_STAKE, minStakeErrorMessage)); - } - - return errors; - } - - const { bondedValidators } = getBondedValidators(nominatorMetadata.nominations); - const parsedSelectedCollatorAddress = reformatAddress(selectedCollator.address, 0); - - if (!bondedValidators.includes(parsedSelectedCollatorAddress)) { // new delegation - if (!bnTotalStake.gte(bnMinStake)) { - errors.push(new TransactionError(StakingTxErrorType.NOT_ENOUGH_MIN_STAKE, minStakeErrorMessage)); - } - - const delegationCount = nominatorMetadata.nominations.length + 1; - - if (delegationCount > chainStakingMetadata.maxValidatorPerNominator) { - errors.push(new TransactionError(StakingTxErrorType.EXCEED_MAX_NOMINATIONS, maxValidatorErrorMessage)); - } - } else { - let currentDelegationAmount = '0'; - let hasUnstaking = false; - - for (const delegation of nominatorMetadata.nominations) { - if (reformatAddress(delegation.validatorAddress, 0) === parsedSelectedCollatorAddress) { - currentDelegationAmount = delegation.activeStake; - hasUnstaking = !!delegation.hasUnstaking && delegation.hasUnstaking; - - break; - } - } - - bnTotalStake = bnTotalStake.add(new BN(currentDelegationAmount)); - - if (!bnTotalStake.gte(bnMinStake)) { - errors.push(new TransactionError(StakingTxErrorType.NOT_ENOUGH_MIN_STAKE, minStakeErrorMessage)); - } - - if (hasUnstaking) { - errors.push(new TransactionError(StakingTxErrorType.EXIST_UNSTAKING_REQUEST, existUnstakeErrorMessage)); - } - } - - return errors; -} - -export function subscribeParaChainStakingMetadata (chain: string, substrateApi: _SubstrateApi, callback: (chain: string, rs: ChainStakingMetadata) => void) { - return substrateApi.api.query.parachainStaking.round((_round: Codec) => { - const roundObj = _round.toHuman() as Record; - const round = parseRawNumber(roundObj.current); - const maxDelegations = substrateApi.api.consts?.parachainStaking?.maxDelegationsPerDelegator?.toString(); - const unstakingDelay = substrateApi.api.consts.parachainStaking.delegationBondLessDelay.toString(); - const unstakingPeriod = parseInt(unstakingDelay) * (_STAKING_ERA_LENGTH_MAP[chain] || _STAKING_ERA_LENGTH_MAP.default); - const minDelegatorStake = substrateApi.api.consts?.parachainStaking?.minDelegatorStk?.toString(); - - callback(chain, { - chain, - type: StakingType.NOMINATED, - era: round, - minStake: minDelegatorStake || '0', - maxValidatorPerNominator: parseInt(maxDelegations), - maxWithdrawalRequestPerValidator: 1, // by default - allowCancelUnstaking: true, - unstakingPeriod - }); - }); -} - -export async function getParaChainStakingMetadata (chain: string, substrateApi: _SubstrateApi): Promise { - const chainApi = await substrateApi.isReady; - - const _round = (await chainApi.api.query.parachainStaking.round()).toHuman() as Record; - const round = parseRawNumber(_round.current); - const maxDelegations = chainApi.api.consts.parachainStaking.maxDelegationsPerDelegator.toString(); - const unstakingDelay = chainApi.api.consts.parachainStaking.delegationBondLessDelay.toString(); - const minDelegatorStake = chainApi.api.consts.parachainStaking?.minDelegatorStk?.toString(); - - let _unvestedAllocation; - - if (chainApi.api.query.vesting && chainApi.api.query.vesting.totalUnvestedAllocation) { - _unvestedAllocation = await chainApi.api.query.vesting.totalUnvestedAllocation(); - } - - const [_totalStake, _totalIssuance, _inflation] = await Promise.all([ - chainApi.api.query.parachainStaking.staked(round), - chainApi.api.query.balances.totalIssuance(), - chainApi.api.query.parachainStaking.inflationConfig() - ]); - - let unvestedAllocation; - - if (_unvestedAllocation) { - const rawUnvestedAllocation = _unvestedAllocation.toString(); - - unvestedAllocation = new BN(rawUnvestedAllocation); - } - - const totalStake = _totalStake ? new BN(_totalStake.toString()) : BN_ZERO; - const totalIssuance = new BN(_totalIssuance.toString()); - - if (unvestedAllocation) { - totalIssuance.add(unvestedAllocation); // for Turing network, read more at https://hackmd.io/@sbAqOuXkRvyiZPOB3Ryn6Q/Sypr3ZJh5 - } - - const inflationConfig = _inflation.toHuman() as unknown as InflationConfig; - const inflation = getParaCurrentInflation(parseRawNumber(totalStake.toString()), inflationConfig); - const unstakingPeriod = parseInt(unstakingDelay) * _STAKING_ERA_LENGTH_MAP[chain]; - - return { - chain, - type: StakingType.NOMINATED, - era: round, - inflation, - minStake: minDelegatorStake || '0', - maxValidatorPerNominator: parseInt(maxDelegations), - maxWithdrawalRequestPerValidator: 1, // by default - allowCancelUnstaking: true, - unstakingPeriod - } as ChainStakingMetadata; -} - -export async function subscribeParaChainNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi, delegatorState: PalletParachainStakingDelegator) { - const nominationList: NominationInfo[] = []; - const unstakingMap: Record = {}; - - let bnTotalActiveStake = BN_ZERO; - - const _roundInfo = await substrateApi.api.query.parachainStaking.round(); - const roundInfo = _roundInfo.toPrimitive() as Record; - const currentRound = roundInfo.current; - - await Promise.all(delegatorState.delegations.map(async (delegation) => { - const [_delegationScheduledRequests, [identity], _collatorInfo] = await Promise.all([ - substrateApi.api.query.parachainStaking.delegationScheduledRequests(delegation.owner), - parseIdentity(substrateApi, delegation.owner), - substrateApi.api.query.parachainStaking.candidateInfo(delegation.owner) - ]); - - const collatorInfo = _collatorInfo.toPrimitive() as unknown as ParachainStakingCandidateMetadata; - const minDelegation = collatorInfo?.lowestTopDelegationAmount.toString(); - const delegationScheduledRequests = _delegationScheduledRequests.toPrimitive() as unknown as PalletParachainStakingDelegationRequestsScheduledRequest[]; - - let hasUnstaking = false; - let delegationStatus: EarningStatus = EarningStatus.NOT_EARNING; - - // parse unstaking info - if (delegationScheduledRequests) { - for (const scheduledRequest of delegationScheduledRequests) { - if (reformatAddress(scheduledRequest.delegator, 0) === reformatAddress(address, 0)) { // add network prefix - const isClaimable = scheduledRequest.whenExecutable - currentRound < 0; - const remainingEra = scheduledRequest.whenExecutable - currentRound; - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chainInfo.slug]; - const claimable = Object.values(scheduledRequest.action)[0]; - - unstakingMap[delegation.owner] = { - chain: chainInfo.slug, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - validatorAddress: delegation.owner, - claimable: claimable.toString(), - waitingTime - } as UnstakingInfo; - - hasUnstaking = true; - break; // only handle 1 scheduledRequest per collator - } - } - } - - const bnStake = new BN(delegation.amount); - const bnUnstakeBalance = unstakingMap[delegation.owner] ? new BN(unstakingMap[delegation.owner].claimable) : BN_ZERO; - - const bnActiveStake = bnStake.sub(bnUnstakeBalance); - - if (bnActiveStake.gt(BN_ZERO) && bnActiveStake.gte(new BN(minDelegation))) { - delegationStatus = EarningStatus.EARNING_REWARD; - } - - bnTotalActiveStake = bnTotalActiveStake.add(bnActiveStake); - - nominationList.push({ - chain: chainInfo.slug, - status: delegationStatus, - validatorAddress: delegation.owner, - validatorIdentity: identity, - activeStake: bnActiveStake.toString(), - hasUnstaking, - validatorMinStake: collatorInfo.lowestTopDelegationAmount.toString() - }); - })); - - // await Promise.all(nominationList.map(async (nomination) => { - // const _collatorInfo = await substrateApi.api.query.parachainStaking.candidateInfo(nomination.validatorAddress); - // const collatorInfo = _collatorInfo.toPrimitive() as unknown as ParachainStakingCandidateMetadata; - // - // nomination.validatorMinStake = collatorInfo.lowestTopDelegationAmount.toString(); - // })); - - const stakingStatus = getEarningStatusByNominations(bnTotalActiveStake, nominationList); - - return { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - status: stakingStatus, - address: address, - activeStake: bnTotalActiveStake.toString(), - - nominations: nominationList, - unstakings: Object.values(unstakingMap) - } as NominatorMetadata; -} - -/** - * Deprecated - * */ -export async function getParaChainNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi): Promise { - if (_isChainEvmCompatible(chainInfo) && !isEthereumAddress(address)) { - return; - } - - const chain = chainInfo.slug; - const chainApi = await substrateApi.isReady; - - const nominationList: NominationInfo[] = []; - const unstakingMap: Record = {}; - - const _delegatorState = await chainApi.api.query.parachainStaking.delegatorState(address); - const delegatorState = _delegatorState.toPrimitive() as unknown as PalletParachainStakingDelegator; - - if (!delegatorState) { - return { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - address, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata; - } - - let bnTotalActiveStake = BN_ZERO; - - await Promise.all(delegatorState.delegations.map(async (delegation) => { - const [_delegationScheduledRequests, [identity], _roundInfo, _collatorInfo] = await Promise.all([ - chainApi.api.query.parachainStaking.delegationScheduledRequests(delegation.owner), - parseIdentity(substrateApi, delegation.owner), - chainApi.api.query.parachainStaking.round(), - chainApi.api.query.parachainStaking.candidateInfo(delegation.owner) - ]); - - const rawCollatorInfo = _collatorInfo.toHuman() as Record; - const minDelegation = (rawCollatorInfo?.lowestTopDelegationAmount as string).replaceAll(',', ''); - const roundInfo = _roundInfo.toPrimitive() as Record; - const delegationScheduledRequests = _delegationScheduledRequests.toPrimitive() as unknown as PalletParachainStakingDelegationRequestsScheduledRequest[]; - - const currentRound = roundInfo.current; - let hasUnstaking = false; - let delegationStatus: EarningStatus = EarningStatus.NOT_EARNING; - - // parse unstaking info - if (delegationScheduledRequests) { - for (const scheduledRequest of delegationScheduledRequests) { - if (reformatAddress(scheduledRequest.delegator, 0) === reformatAddress(address, 0)) { // add network prefix - const isClaimable = scheduledRequest.whenExecutable - currentRound < 0; - const remainingEra = scheduledRequest.whenExecutable - (currentRound + 1); - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chain]; - const claimable = Object.values(scheduledRequest.action)[0]; - - unstakingMap[delegation.owner] = { - chain, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - validatorAddress: delegation.owner, - claimable: claimable.toString(), - waitingTime: waitingTime - } as UnstakingInfo; - - hasUnstaking = true; - break; // only handle 1 scheduledRequest per collator - } - } - } - - const bnStake = new BN(delegation.amount); - const bnUnstakeBalance = unstakingMap[delegation.owner] ? new BN(unstakingMap[delegation.owner].claimable) : BN_ZERO; - - const bnActiveStake = bnStake.sub(bnUnstakeBalance); - - if (bnActiveStake.gt(BN_ZERO) && bnActiveStake.gte(new BN(minDelegation))) { - delegationStatus = EarningStatus.EARNING_REWARD; - } - - bnTotalActiveStake = bnTotalActiveStake.add(bnActiveStake); - - nominationList.push({ - chain, - status: delegationStatus, - validatorAddress: delegation.owner, - validatorIdentity: identity, - activeStake: bnActiveStake.toString(), - hasUnstaking - }); - })); - - await Promise.all(nominationList.map(async (nomination) => { - const _collatorInfo = await chainApi.api.query.parachainStaking.candidateInfo(nomination.validatorAddress); - const collatorInfo = _collatorInfo.toPrimitive() as unknown as ParachainStakingCandidateMetadata; - - nomination.validatorMinStake = collatorInfo.lowestTopDelegationAmount.toString(); - })); - - const stakingStatus = getEarningStatusByNominations(bnTotalActiveStake, nominationList); - - return { - chain, - type: StakingType.NOMINATED, - status: stakingStatus, - address: address, - activeStake: bnTotalActiveStake.toString(), - - nominations: nominationList, - unstakings: Object.values(unstakingMap) - } as NominatorMetadata; -} - -export async function getParachainCollatorsInfo (chain: string, substrateApi: _SubstrateApi): Promise { - const apiProps = await substrateApi.isReady; - - const allCollators: ValidatorInfo[] = []; - - const [_allCollators, _collatorCommission] = await Promise.all([ - apiProps.api.query.parachainStaking.candidateInfo.entries(), - apiProps.api.query.parachainStaking.collatorCommission() - ]); - - const maxDelegationPerCollator = apiProps.api.consts.parachainStaking.maxTopDelegationsPerCandidate.toString(); - const rawCollatorCommission = _collatorCommission.toHuman() as string; - const collatorCommission = parseFloat(rawCollatorCommission.split('%')[0]); - - for (const collator of _allCollators) { - const _collatorAddress = collator[0].toHuman() as string[]; - const collatorAddress = _collatorAddress[0]; - const collatorInfo = collator[1].toPrimitive() as unknown as ParachainStakingCandidateMetadata; - - const bnTotalStake = new BN(collatorInfo.totalCounted); - const bnOwnStake = new BN(collatorInfo.bond); - const bnOtherStake = bnTotalStake.sub(bnOwnStake); - const bnMinBond = new BN(collatorInfo.lowestTopDelegationAmount); - - allCollators.push({ - commission: 0, - expectedReturn: 0, - address: collatorAddress, - totalStake: bnTotalStake.toString(), - ownStake: bnOwnStake.toString(), - otherStake: bnOtherStake.toString(), - nominatorCount: collatorInfo.delegationCount, - blocked: false, - isVerified: false, - minBond: bnMinBond.toString(), - chain, - isCrowded: parseInt(maxDelegationPerCollator) > 0 - }); - } - - const extraInfoMap: Record = {}; - - await Promise.all(allCollators.map(async (collator) => { - const [_info, [identity, isReasonable]] = await Promise.all([ - apiProps.api.query.parachainStaking.candidateInfo(collator.address), - parseIdentity(apiProps, collator.address) - ]); - - const rawInfo = _info.toHuman() as Record; - - const active = rawInfo?.status === 'Active'; - - extraInfoMap[collator.address] = { - identity, - isVerified: isReasonable, - active - } as CollatorExtraInfo; - })); - - for (const validator of allCollators) { - validator.blocked = !extraInfoMap[validator.address].active; - validator.identity = extraInfoMap[validator.address].identity; - validator.isVerified = extraInfoMap[validator.address].isVerified; - // @ts-ignore - validator.commission = collatorCommission; - } - - return allCollators; -} - -export async function getParaBondingExtrinsic (chainInfo: _ChainInfo, substrateApi: _SubstrateApi, amount: string, selectedCollatorInfo: ValidatorInfo, nominatorMetadata?: NominatorMetadata) { - const apiPromise = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - if (!nominatorMetadata) { - return apiPromise.api.tx.parachainStaking.delegate(selectedCollatorInfo.address, binaryAmount, new BN(selectedCollatorInfo.nominatorCount), 0); - } - - const { bondedValidators, nominationCount } = getBondedValidators(nominatorMetadata.nominations); - const parsedSelectedCollatorAddress = reformatAddress(selectedCollatorInfo.address, 0); - - if (!bondedValidators.includes(parsedSelectedCollatorAddress)) { - return apiPromise.api.tx.parachainStaking.delegate(selectedCollatorInfo.address, binaryAmount, new BN(selectedCollatorInfo.nominatorCount), nominationCount); - } else { - return apiPromise.api.tx.parachainStaking.delegatorBondMore(selectedCollatorInfo.address, binaryAmount); - } -} - -export async function getParaUnbondingExtrinsic (substrateApi: _SubstrateApi, amount: string, nominatorMetadata: NominatorMetadata, selectedValidator: string) { - const apiPromise = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - const unstakeAll = isUnstakeAll(selectedValidator, nominatorMetadata.nominations, amount); - - if (!unstakeAll) { - return apiPromise.api.tx.parachainStaking.scheduleDelegatorBondLess(selectedValidator, binaryAmount); - } else { - return apiPromise.api.tx.parachainStaking.scheduleRevokeDelegation(selectedValidator); - } -} - -export async function getParaWithdrawalExtrinsic (substrateApi: _SubstrateApi, address: string, collatorAddress: string) { - const chainApi = await substrateApi.isReady; - - return chainApi.api.tx.parachainStaking.executeDelegationRequest(address, collatorAddress); -} - -export async function getTuringCompoundExtrinsic (substrateApi: _SubstrateApi, address: string, collatorAddress: string, accountMinimum: string, bondedAmount: string) { - const apiPromise = await substrateApi.isReady; - - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const _optimalCompounding = await apiPromise.api.rpc.automationTime.calculateOptimalAutostaking(bondedAmount, collatorAddress); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const optimalCompounding = _optimalCompounding.toHuman() as TuringOptimalCompoundFormat; - const compoundingPeriod = parseInt(optimalCompounding.period); // in days - - const frequency = compoundingPeriod * 24 * 60 * 60; // in seconds - const timestamp = new Date(); - - timestamp.setDate(timestamp.getDate() + compoundingPeriod); - timestamp.setHours(timestamp.getHours() + Math.round(timestamp.getMinutes() / 60)); - timestamp.setMinutes(0, 0, 0); - - const startTime = Math.floor(timestamp.valueOf() / 1000); // must be in seconds - - return apiPromise.api.tx.automationTime.scheduleAutoCompoundDelegatedStakeTask(startTime.toString(), frequency.toString(), collatorAddress, accountMinimum); -} - -export async function getTuringCancelCompoundingExtrinsic (substrateApi: _SubstrateApi, taskId: string) { - const apiPromise = await substrateApi.isReady; - - return apiPromise.api.tx.automationTime.cancelTask(taskId); -} - -export async function getParaCancelWithdrawalExtrinsic (substrateApi: _SubstrateApi, selectedUnstaking: UnstakingInfo) { - const chainApi = await substrateApi.isReady; - - return chainApi.api.tx.parachainStaking.cancelDelegationRequest(selectedUnstaking.validatorAddress); -} diff --git a/packages/extension-base/src/koni/api/staking/bonding/relayChain.ts b/packages/extension-base/src/koni/api/staking/bonding/relayChain.ts deleted file mode 100644 index a0d41caf640..00000000000 --- a/packages/extension-base/src/koni/api/staking/bonding/relayChain.ts +++ /dev/null @@ -1,1037 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; -import { ChainStakingMetadata, NominationInfo, NominatorMetadata, StakingType, UnstakingInfo, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { MAX_NOMINATIONS } from '@subwallet/extension-base/constants'; -import { PalletNominationPoolsPoolMember } from '@subwallet/extension-base/core/substrate/types'; -import { calculateAlephZeroValidatorReturn, calculateChainStakedReturn, calculateInflation, calculateTernoaValidatorReturn, calculateValidatorStakedReturn, getCommission, getExistUnstakeErrorMessage, getMaxValidatorErrorMessage, getMinStakeErrorMessage, parsePoolStashAddress } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; -import { _EXPECTED_BLOCK_TIME, _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _getChainSubstrateAddressPrefix } from '@subwallet/extension-base/services/chain-service/utils'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; -import { EarningStatus, NominationPoolInfo, PalletNominationPoolsBondedPoolInner, PalletStakingExposure, StakingTxErrorType, TernoaStakingRewardsStakingRewardsData, UnstakingStatus, ValidatorExtraInfo } from '@subwallet/extension-base/types'; -import { reformatAddress } from '@subwallet/extension-base/utils'; -import BigN from 'bignumber.js'; -import { t } from 'i18next'; - -import { Bytes } from '@polkadot/types'; -import { Codec } from '@polkadot/types/types'; -import { BN, BN_ZERO, hexToString, isHex } from '@polkadot/util'; -import { isEthereumAddress } from '@polkadot/util-crypto'; - -export interface PalletStakingNominations { - targets: string[], - submittedIn: number, - suppressed: boolean -} - -export interface UnlockingChunk { - value: number, - era: number -} - -export interface PalletStakingStakingLedger { - stash: string, - total: number, - active: number, - unlocking: UnlockingChunk[], - claimedRewards: number[] -} - -export function validateRelayUnbondingCondition (amount: string, chainStakingMetadata: ChainStakingMetadata, nominatorMetadata: NominatorMetadata): TransactionError[] { - const errors: TransactionError[] = []; - const bnActiveStake = new BN(nominatorMetadata.activeStake); - const bnRemainingStake = bnActiveStake.sub(new BN(amount)); - - const minStake = new BN(chainStakingMetadata.minJoinNominationPool || '0'); - - if (!(bnRemainingStake.isZero() || bnRemainingStake.gte(minStake))) { - errors.push(new TransactionError(StakingTxErrorType.INVALID_ACTIVE_STAKE)); - } - - if (nominatorMetadata.unstakings.length > chainStakingMetadata.maxWithdrawalRequestPerValidator) { - errors.push(new TransactionError(StakingTxErrorType.EXCEED_MAX_UNSTAKING, t('bg.EARNING.koni.api.staking.bonding.relayChain.maxUnstakeTimes', { replace: { number: chainStakingMetadata.maxWithdrawalRequestPerValidator } }))); - } - - return errors; -} - -export function validatePoolBondingCondition (chainInfo: _ChainInfo, amount: string, selectedPool: NominationPoolInfo, address: string, chainStakingMetadata: ChainStakingMetadata, nominatorMetadata?: NominatorMetadata): TransactionError[] { - // cannot stake when unstake all - // amount >= min stake - const errors: TransactionError[] = []; - let bnTotalStake = new BN(amount); - const bnMinStake = new BN(chainStakingMetadata.minJoinNominationPool || '0'); - const minStakeErrorMessage = getMinStakeErrorMessage(chainInfo, bnMinStake); - const existUnstakeErrorMessage = getExistUnstakeErrorMessage(chainInfo.slug, nominatorMetadata?.type, true); - - if (selectedPool.state !== 'Open') { - errors.push(new TransactionError(StakingTxErrorType.INACTIVE_NOMINATION_POOL)); - } - - if (nominatorMetadata) { - const bnCurrentActiveStake = new BN(nominatorMetadata.activeStake); - - bnTotalStake = bnTotalStake.add(bnCurrentActiveStake); - - if (nominatorMetadata.unstakings.length > 0 && bnCurrentActiveStake.isZero()) { - errors.push(new TransactionError(StakingTxErrorType.EXIST_UNSTAKING_REQUEST, existUnstakeErrorMessage)); - } - } - - if (!bnTotalStake.gte(bnMinStake)) { - errors.push(new TransactionError(StakingTxErrorType.NOT_ENOUGH_MIN_STAKE, minStakeErrorMessage)); - } - - return errors; -} - -export function validateRelayBondingCondition (chainInfo: _ChainInfo, amount: string, selectedValidators: ValidatorInfo[], address: string, chainStakingMetadata: ChainStakingMetadata, nominatorMetadata?: NominatorMetadata) { - const errors: TransactionError[] = []; - let bnTotalStake = new BN(amount); - const bnMinStake = new BN(chainStakingMetadata.minStake); - const minStakeErrorMessage = getMinStakeErrorMessage(chainInfo, bnMinStake); - const maxValidatorErrorMessage = getMaxValidatorErrorMessage(chainInfo, chainStakingMetadata.maxValidatorPerNominator); - - if (!nominatorMetadata || nominatorMetadata.status === EarningStatus.NOT_STAKING) { - if (!bnTotalStake.gte(bnMinStake)) { - errors.push(new TransactionError(StakingTxErrorType.NOT_ENOUGH_MIN_STAKE, minStakeErrorMessage)); - } - - if (selectedValidators.length > chainStakingMetadata.maxValidatorPerNominator) { - errors.push(new TransactionError(StakingTxErrorType.EXCEED_MAX_NOMINATIONS, maxValidatorErrorMessage)); - } - - return errors; - } - - const bnCurrentActiveStake = new BN(nominatorMetadata.activeStake); - - bnTotalStake = bnTotalStake.add(bnCurrentActiveStake); - - if (!bnTotalStake.gte(bnMinStake)) { - errors.push(new TransactionError(StakingTxErrorType.NOT_ENOUGH_MIN_STAKE, minStakeErrorMessage)); - } - - if (selectedValidators.length > chainStakingMetadata.maxValidatorPerNominator) { - errors.push(new TransactionError(StakingTxErrorType.EXCEED_MAX_NOMINATIONS, maxValidatorErrorMessage)); - } - - return errors; -} - -export function subscribeRelayChainStakingMetadata (chainInfo: _ChainInfo, substrateApi: _SubstrateApi, callback: (chain: string, rs: ChainStakingMetadata) => void) { - return substrateApi.api.query.staking.currentEra(async (_currentEra: Codec) => { - const currentEra = _currentEra.toString(); - const maxNominations = substrateApi.api.consts.staking?.maxNominations?.toString() || MAX_NOMINATIONS; // TODO - const maxUnlockingChunks = substrateApi.api.consts.staking.maxUnlockingChunks.toString(); - const unlockingEras = substrateApi.api.consts.staking.bondingDuration.toString(); - - const [_totalEraStake, _totalIssuance, _auctionCounter, _minNominatorBond, _minPoolJoin, _minimumActiveStake] = await Promise.all([ - substrateApi.api.query.staking.erasTotalStake(parseInt(currentEra)), - substrateApi.api.query.balances.totalIssuance(), - substrateApi.api.query.auctions?.auctionCounter(), - substrateApi.api.query.staking.minNominatorBond(), - substrateApi.api.query?.nominationPools?.minJoinBond(), - substrateApi.api.query?.staking?.minimumActiveStake && substrateApi.api.query?.staking?.minimumActiveStake() - ]); - - const minActiveStake = _minimumActiveStake?.toString() || '0'; - const minNominatorBond = _minNominatorBond.toString(); - - const bnMinActiveStake = new BN(minActiveStake); - const bnMinNominatorBond = new BN(minNominatorBond); - - const minStake = bnMinActiveStake.gt(bnMinNominatorBond) ? bnMinActiveStake : bnMinNominatorBond; - const rawTotalEraStake = _totalEraStake.toString(); - const rawTotalIssuance = _totalIssuance.toString(); - - const numAuctions = _auctionCounter ? _auctionCounter.toHuman() as number : 0; - const bnTotalEraStake = new BN(rawTotalEraStake); - const bnTotalIssuance = new BN(rawTotalIssuance); - - const inflation = calculateInflation(bnTotalEraStake, bnTotalIssuance, numAuctions, chainInfo.slug); - const expectedReturn = calculateChainStakedReturn(inflation, bnTotalEraStake, bnTotalIssuance, chainInfo.slug); - const minPoolJoin = _minPoolJoin?.toString() || undefined; - const unlockingPeriod = parseInt(unlockingEras) * (_STAKING_ERA_LENGTH_MAP[chainInfo.slug] || _STAKING_ERA_LENGTH_MAP.default); // in hours - - callback(chainInfo.slug, { - chain: chainInfo.slug, - type: StakingType.NOMINATED, - expectedReturn: !_STAKING_CHAIN_GROUP.ternoa.includes(chainInfo.slug) ? expectedReturn : undefined, // in %, annually - inflation, - era: parseInt(currentEra), - minStake: minStake.toString(), - maxValidatorPerNominator: parseInt(maxNominations), - maxWithdrawalRequestPerValidator: parseInt(maxUnlockingChunks), - allowCancelUnstaking: true, - unstakingPeriod: unlockingPeriod, - minJoinNominationPool: minPoolJoin - }); - }); -} - -export async function getRelayChainStakingMetadata (chainInfo: _ChainInfo, substrateApi: _SubstrateApi): Promise { - const chain = chainInfo.slug; - const chainApi = await substrateApi.isReady; - const _era = await chainApi.api.query.staking.currentEra(); - const currentEra = _era.toString(); - const maxNominations = chainApi.api.consts.staking?.maxNominations?.toString() || '16'; // TODO - const maxUnlockingChunks = chainApi.api.consts.staking.maxUnlockingChunks.toString(); - const unlockingEras = chainApi.api.consts.staking.bondingDuration.toString(); - - const [_totalEraStake, _totalIssuance, _auctionCounter, _minimumActiveStake, _minNominatorBond, _minPoolJoin, _eraStakers] = await Promise.all([ - chainApi.api.query.staking.erasTotalStake(parseInt(currentEra)), - chainApi.api.query.balances.totalIssuance(), - chainApi.api.query.auctions?.auctionCounter(), - chainApi.api.query?.staking?.minimumActiveStake && chainApi.api.query?.staking?.minimumActiveStake(), - chainApi.api.query.staking.minNominatorBond(), - chainApi.api.query?.nominationPools?.minJoinBond(), - chainApi.api.query.staking.erasStakers.entries(parseInt(currentEra)) - ]); - - const eraStakers = _eraStakers as any[]; - let allCurrentNominators: Record[] = []; - const nominatorList: string[] = []; - - for (const item of eraStakers) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const rawValidatorStat = item[1].toHuman() as Record; - const eraNominators = rawValidatorStat.others as Record[]; - - allCurrentNominators = allCurrentNominators.concat(eraNominators); - } - - for (const nominator of allCurrentNominators) { - if (!nominatorList.includes(nominator.who as string)) { - nominatorList.push(nominator.who as string); - } - } - - const minActiveStake = _minimumActiveStake?.toString() || '0'; - const minNominatorBond = _minNominatorBond.toString(); - - const bnMinActiveStake = new BN(minActiveStake); - const bnMinNominatorBond = new BN(minNominatorBond); - - const minStake = bnMinActiveStake.gt(bnMinNominatorBond) ? bnMinActiveStake : bnMinNominatorBond; - - const minPoolJoin = _minPoolJoin?.toString() || undefined; - const rawTotalEraStake = _totalEraStake.toString(); - const rawTotalIssuance = _totalIssuance.toString(); - - const numAuctions = _auctionCounter ? _auctionCounter.toHuman() as number : 0; - - const bnTotalEraStake = new BN(rawTotalEraStake); - const bnTotalIssuance = new BN(rawTotalIssuance); - - const inflation = calculateInflation(bnTotalEraStake, bnTotalIssuance, numAuctions, chain); - const expectedReturn = calculateChainStakedReturn(inflation, bnTotalEraStake, bnTotalIssuance, chain); - const unlockingPeriod = parseInt(unlockingEras) * _STAKING_ERA_LENGTH_MAP[chain]; // in hours - - return { - chain, - type: StakingType.NOMINATED, - era: parseInt(currentEra), - expectedReturn: !_STAKING_CHAIN_GROUP.ternoa.includes(chain) ? expectedReturn : undefined, // in %, annually - inflation, - minStake: minStake.toString(), - maxValidatorPerNominator: parseInt(maxNominations), - maxWithdrawalRequestPerValidator: parseInt(maxUnlockingChunks), - allowCancelUnstaking: true, - unstakingPeriod: unlockingPeriod, - minJoinNominationPool: minPoolJoin, - nominatorCount: nominatorList.length - } as ChainStakingMetadata; -} - -export async function subscribeRelayChainNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi, ledger: PalletStakingStakingLedger) { - const chain = chainInfo.slug; - const chainApi = await substrateApi.isReady; - - const [_nominations, _currentEra, _bonded, _minimumActiveStake, _minNominatorBond, _deriveSessionProgress] = await Promise.all([ - chainApi.api.query?.staking?.nominators(address), - chainApi.api.query?.staking?.currentEra(), - chainApi.api.query?.staking?.bonded(address), - chainApi.api.query?.staking?.minimumActiveStake && chainApi.api.query?.staking?.minimumActiveStake(), - chainApi.api.query?.staking?.minNominatorBond(), - chainApi.api.derive?.session?.progress() - ]); - - const minActiveStake = _minimumActiveStake?.toString() || '0'; - const minNominatorBond = _minNominatorBond.toString(); - - const bnMinActiveStake = new BN(minActiveStake); - const bnMinNominatorBond = new BN(minNominatorBond); - - const minStake = bnMinActiveStake.gt(bnMinNominatorBond) ? bnMinActiveStake : bnMinNominatorBond; - - const unlimitedNominatorRewarded = chainApi.api.consts.staking.maxExposurePageSize !== undefined; - const _maxNominatorRewardedPerValidator = (chainApi.api.consts.staking.maxNominatorRewardedPerValidator || 0).toString(); - const maxNominatorRewardedPerValidator = parseInt(_maxNominatorRewardedPerValidator); - const nominations = _nominations.toPrimitive() as unknown as PalletStakingNominations; - const currentEra = _currentEra.toString(); - const bonded = _bonded.toHuman(); - - const activeStake = ledger.active.toString(); - const nominationList: NominationInfo[] = []; - const unstakingList: UnstakingInfo[] = []; - - if (nominations) { - const validatorList = nominations.targets; - - await Promise.all(validatorList.map(async (validatorAddress) => { - let nominationStatus = EarningStatus.NOT_EARNING; - const [[identity], _eraStaker] = await Promise.all([ - parseIdentity(chainApi, validatorAddress), - chainApi.api.query.staking.erasStakers(currentEra, validatorAddress) - ]); - const eraStaker = _eraStaker.toPrimitive() as unknown as PalletStakingExposure; - const sortedNominators = eraStaker.others - .sort((a, b) => { - return new BigN(b.value).minus(a.value).toNumber(); - }) - ; - - const topNominators = sortedNominators - .map((nominator) => { - return nominator.who; - }) - ; - - if (!topNominators.includes(reformatAddress(address, _getChainSubstrateAddressPrefix(chainInfo)))) { // if nominator has target but not in nominator list - nominationStatus = EarningStatus.WAITING; - } else if (topNominators.slice(0, unlimitedNominatorRewarded ? undefined : maxNominatorRewardedPerValidator).includes(reformatAddress(address, _getChainSubstrateAddressPrefix(chainInfo)))) { // if address in top nominators - nominationStatus = EarningStatus.EARNING_REWARD; - } - - nominationList.push({ - chain, - validatorAddress, - status: nominationStatus, - validatorIdentity: identity, - activeStake: '0' // relaychain allocates stake accordingly - } as NominationInfo); - })); - } - - let stakingStatus = EarningStatus.NOT_EARNING; - const bnActiveStake = new BN(activeStake); - let waitingNominationCount = 0; - - if (bnActiveStake.gte(minStake)) { - for (const nomination of nominationList) { - if (nomination.status === EarningStatus.EARNING_REWARD) { // only need 1 earning nomination to count - stakingStatus = EarningStatus.EARNING_REWARD; - } else if (nomination.status === EarningStatus.WAITING) { - waitingNominationCount += 1; - } - } - - if (waitingNominationCount === nominationList.length) { - stakingStatus = EarningStatus.WAITING; - } - } - - ledger.unlocking.forEach((unlockingChunk) => { - // Calculate the remaining era - const isClaimable = unlockingChunk.era - parseInt(currentEra) < 0; - const remainingEra = unlockingChunk.era - parseInt(currentEra); - - // Calculate the remaining time for current era ending - const expectedBlockTime = _EXPECTED_BLOCK_TIME[chain]; - const eraLength = _deriveSessionProgress.eraLength.toNumber(); - const eraProgress = _deriveSessionProgress.eraProgress.toNumber(); - const remainingSlots = eraLength - eraProgress; - const remainingHours = expectedBlockTime * remainingSlots / 60 / 60; - - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chain] + remainingHours; - - unstakingList.push({ - chain, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: unlockingChunk.value.toString(), - waitingTime: waitingTime - } as UnstakingInfo); - }); - - return { - chain, - type: StakingType.NOMINATED, - status: stakingStatus, - address: address, - activeStake, - - nominations: nominationList, - unstakings: unstakingList, - isBondedBefore: bonded !== null - } as NominatorMetadata; -} - -/** - * Deprecated - * */ -export async function getRelayChainNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi): Promise { - if (isEthereumAddress(address)) { - return; - } - - const chain = chainInfo.slug; - const chainApi = await substrateApi.isReady; - - const [_ledger, _nominations, _currentEra, _bonded, _minimumActiveStake, _minNominatorBond] = await Promise.all([ - chainApi.api.query?.staking?.ledger(address), - chainApi.api.query?.staking?.nominators(address), - chainApi.api.query?.staking?.currentEra(), - chainApi.api.query?.staking?.bonded(address), - chainApi.api.query?.staking?.minimumActiveStake && chainApi.api.query?.staking?.minimumActiveStake(), - chainApi.api.query?.staking?.minNominatorBond() - ]); - - const minActiveStake = _minimumActiveStake?.toString() || '0'; - const minNominatorBond = _minNominatorBond.toString(); - - const bnMinActiveStake = new BN(minActiveStake); - const bnMinNominatorBond = new BN(minNominatorBond); - - const minStake = bnMinActiveStake.gt(bnMinNominatorBond) ? bnMinActiveStake : bnMinNominatorBond; - - const unlimitedNominatorRewarded = chainApi.api.consts.staking.maxExposurePageSize !== undefined; - const _maxNominatorRewardedPerValidator = (chainApi.api.consts.staking.maxNominatorRewardedPerValidator || 0).toString(); - const maxNominatorRewardedPerValidator = parseInt(_maxNominatorRewardedPerValidator); - const ledger = _ledger.toPrimitive() as unknown as PalletStakingStakingLedger; - const nominations = _nominations.toPrimitive() as unknown as PalletStakingNominations; - const currentEra = _currentEra.toString(); - const bonded = _bonded.toHuman(); - - if (!ledger) { - return { - chain, - type: StakingType.NOMINATED, - status: EarningStatus.NOT_STAKING, - address: address, - activeStake: '0', - - nominations: [], - unstakings: [] - } as NominatorMetadata; - } - - const activeStake = ledger.active.toString(); - const nominationList: NominationInfo[] = []; - const unstakingList: UnstakingInfo[] = []; - - if (nominations) { - const validatorList = nominations.targets; - - await Promise.all(validatorList.map(async (validatorAddress) => { - let nominationStatus = EarningStatus.NOT_EARNING; - const [[identity], _eraStaker] = await Promise.all([ - parseIdentity(chainApi, validatorAddress), - chainApi.api.query.staking.erasStakers(currentEra, validatorAddress) - ]); - const eraStaker = _eraStaker.toPrimitive() as unknown as PalletStakingExposure; - const sortedNominators = eraStaker.others - .sort((a, b) => { - return new BigN(b.value).minus(a.value).toNumber(); - }) - ; - - const topNominators = sortedNominators - .map((nominator) => { - return nominator.who; - }) - ; - - if (!topNominators.includes(reformatAddress(address, _getChainSubstrateAddressPrefix(chainInfo)))) { // if nominator has target but not in nominator list - nominationStatus = EarningStatus.WAITING; - } else if (topNominators.slice(0, unlimitedNominatorRewarded ? undefined : maxNominatorRewardedPerValidator).includes(reformatAddress(address, _getChainSubstrateAddressPrefix(chainInfo)))) { // if address in top nominators - nominationStatus = EarningStatus.EARNING_REWARD; - } - - nominationList.push({ - chain, - validatorAddress, - status: nominationStatus, - validatorIdentity: identity, - activeStake: '0' // relaychain allocates stake accordingly - } as NominationInfo); - })); - } - - let stakingStatus = EarningStatus.NOT_EARNING; - const bnActiveStake = new BN(activeStake); - let waitingNominationCount = 0; - - if (bnActiveStake.gte(minStake)) { - for (const nomination of nominationList) { - if (nomination.status === EarningStatus.EARNING_REWARD) { // only need 1 earning nomination to count - stakingStatus = EarningStatus.EARNING_REWARD; - } else if (nomination.status === EarningStatus.WAITING) { - waitingNominationCount += 1; - } - } - - if (waitingNominationCount === nominationList.length) { - stakingStatus = EarningStatus.WAITING; - } - } - - ledger.unlocking.forEach((unlockingChunk) => { - const isClaimable = unlockingChunk.era - parseInt(currentEra) < 0; - const remainingEra = unlockingChunk.era - parseInt(currentEra); - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chain]; - - unstakingList.push({ - chain, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: unlockingChunk.value.toString(), - waitingTime: waitingTime - } as UnstakingInfo); - }); - - return { - chain, - type: StakingType.NOMINATED, - status: stakingStatus, - address: address, - activeStake, - - nominations: nominationList, - unstakings: unstakingList, - isBondedBefore: bonded !== null - } as NominatorMetadata; -} - -export async function subscribeRelayChainPoolMemberMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi, poolMemberInfo: PalletNominationPoolsPoolMember) { - const unlimitedNominatorRewarded = substrateApi.api.consts.staking.maxExposurePageSize !== undefined; - const _maxNominatorRewardedPerValidator = (substrateApi.api.consts.staking.maxNominatorRewardedPerValidator || 0).toString(); - const maxNominatorRewardedPerValidator = parseInt(_maxNominatorRewardedPerValidator); - const poolsPalletId = substrateApi.api.consts.nominationPools.palletId.toString(); - const poolStashAccount = parsePoolStashAddress(substrateApi.api, 0, poolMemberInfo.poolId, poolsPalletId); - - const [_nominations, _poolMetadata, _currentEra, _deriveSessionProgress] = await Promise.all([ - substrateApi.api.query.staking.nominators(poolStashAccount), - substrateApi.api.query.nominationPools.metadata(poolMemberInfo.poolId), - substrateApi.api.query.staking.currentEra(), - substrateApi.api.derive?.session?.progress() - ]); - - const poolMetadata = _poolMetadata.toPrimitive() as unknown as Bytes; - const currentEra = _currentEra.toString(); - const nominations = _nominations.toJSON() as unknown as PalletStakingNominations; - - let stakingStatus = EarningStatus.NOT_EARNING; - - const getPoolName = () => { - if (poolMetadata.isUtf8) { - return poolMetadata.toUtf8(); - } else { - const str = poolMetadata.toString(); - - return isHex(str) ? hexToString(str) : str; - } - }; - - const poolName = getPoolName(); - - if (nominations) { - const validatorList = nominations.targets; - - await Promise.all(validatorList.map(async (validatorAddress) => { - const _eraStaker = await substrateApi.api.query.staking.erasStakers(currentEra, validatorAddress); - const eraStaker = _eraStaker.toPrimitive() as unknown as PalletStakingExposure; - - const sortedNominators = eraStaker.others - .sort((a, b) => { - return new BigN(b.value).minus(a.value).toNumber(); - }) - ; - - const topNominators = sortedNominators - .map((nominator) => { - return nominator.who; - }) - .slice(0, unlimitedNominatorRewarded ? undefined : maxNominatorRewardedPerValidator) - ; - - if (topNominators.includes(reformatAddress(poolStashAccount, _getChainSubstrateAddressPrefix(chainInfo)))) { // if address in top nominators - stakingStatus = EarningStatus.EARNING_REWARD; - } - })); - } - - const joinedPoolInfo: NominationInfo = { - activeStake: poolMemberInfo.points.toString(), - chain: chainInfo.slug, - status: stakingStatus, - validatorIdentity: poolName, - validatorAddress: poolMemberInfo.poolId.toString(), // use poolId - hasUnstaking: poolMemberInfo.unbondingEras && Object.keys(poolMemberInfo.unbondingEras).length > 0 - }; - - const unstakings: UnstakingInfo[] = []; - - Object.entries(poolMemberInfo.unbondingEras).forEach(([unlockingEra, amount]) => { - const isClaimable = parseInt(unlockingEra) - parseInt(currentEra) < 0; - const remainingEra = parseInt(unlockingEra) - parseInt(currentEra); - // const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chainInfo.slug]; - - // Calculate the remaining time for current era ending - const expectedBlockTime = _EXPECTED_BLOCK_TIME[chainInfo.slug]; - const eraLength = _deriveSessionProgress.eraLength.toNumber(); - const eraProgress = _deriveSessionProgress.eraProgress.toNumber(); - const remainingSlots = eraLength - eraProgress; - const remainingHours = expectedBlockTime * remainingSlots / 60 / 60; - - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chainInfo.slug] + remainingHours; - - unstakings.push({ - chain: chainInfo.slug, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: amount.toString(), - waitingTime: waitingTime - } as UnstakingInfo); - }); - - const bnActiveStake = new BN(poolMemberInfo.points.toString()); - - if (!bnActiveStake.gt(BN_ZERO)) { - stakingStatus = EarningStatus.NOT_EARNING; - } - - return { - chain: chainInfo.slug, - type: StakingType.POOLED, - address, - status: stakingStatus, - activeStake: poolMemberInfo.points.toString(), - nominations: [joinedPoolInfo], // can only join 1 pool at a time - unstakings - } as NominatorMetadata; -} - -/** - * Deprecated - * */ -export async function getRelayChainPoolMemberMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi): Promise { - const chainApi = await substrateApi.isReady; - - const [_poolMemberInfo, _currentEra] = await Promise.all([ - chainApi.api.query.nominationPools.poolMembers(address), - chainApi.api.query.staking.currentEra() - ]); - - const unlimitedNominatorRewarded = chainApi.api.consts.staking.maxExposurePageSize !== undefined; - const _maxNominatorRewardedPerValidator = (chainApi.api.consts.staking.maxNominatorRewardedPerValidator || 0).toString(); - const maxNominatorRewardedPerValidator = parseInt(_maxNominatorRewardedPerValidator); - const poolsPalletId = chainApi.api.consts.nominationPools.palletId.toString(); - const poolMemberInfo = _poolMemberInfo.toPrimitive() as unknown as PalletNominationPoolsPoolMember; - const currentEra = _currentEra.toString(); - - if (!poolMemberInfo) { - return { - chain: chainInfo.slug, - type: StakingType.POOLED, - address, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], // can only join 1 pool at a time - unstakings: [] - } as NominatorMetadata; - } - - let stakingStatus = EarningStatus.NOT_EARNING; - - const _poolMetadata = (await chainApi.api.query.nominationPools.metadata(poolMemberInfo.poolId)); - const poolMetadata = _poolMetadata.toPrimitive() as unknown as Bytes; - - const getPoolName = () => { - if (poolMetadata.isUtf8) { - return poolMetadata.toUtf8(); - } else { - const str = poolMetadata.toString(); - - return isHex(str) ? hexToString(str) : str; - } - }; - - const poolName = getPoolName(); - const poolStashAccount = parsePoolStashAddress(chainApi.api, 0, poolMemberInfo.poolId, poolsPalletId); - - const _nominations = await chainApi.api.query.staking.nominators(poolStashAccount); - const nominations = _nominations.toJSON() as unknown as PalletStakingNominations; - - if (nominations) { - const validatorList = nominations.targets; - - await Promise.all(validatorList.map(async (validatorAddress) => { - const _eraStaker = await chainApi.api.query.staking.erasStakers(currentEra, validatorAddress); - const eraStaker = _eraStaker.toPrimitive() as unknown as PalletStakingExposure; - - const sortedNominators = eraStaker.others - .sort((a, b) => { - return new BigN(b.value).minus(a.value).toNumber(); - }) - ; - - const topNominators = sortedNominators - .map((nominator) => { - return nominator.who; - }) - .slice(0, unlimitedNominatorRewarded ? undefined : maxNominatorRewardedPerValidator) - ; - - if (topNominators.includes(reformatAddress(poolStashAccount, _getChainSubstrateAddressPrefix(chainInfo)))) { // if address in top nominators - stakingStatus = EarningStatus.EARNING_REWARD; - } - })); - } - - const joinedPoolInfo: NominationInfo = { - activeStake: poolMemberInfo.points.toString(), - chain: chainInfo.slug, - status: stakingStatus, - validatorIdentity: poolName, - validatorAddress: poolMemberInfo.poolId.toString(), // use poolId - hasUnstaking: poolMemberInfo.unbondingEras && Object.keys(poolMemberInfo.unbondingEras).length > 0 - }; - - const unstakings: UnstakingInfo[] = []; - - Object.entries(poolMemberInfo.unbondingEras).forEach(([unlockingEra, amount]) => { - const isClaimable = parseInt(unlockingEra) - parseInt(currentEra) < 0; - const remainingEra = parseInt(unlockingEra) - parseInt(currentEra); - const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chainInfo.slug]; - - unstakings.push({ - chain: chainInfo.slug, - status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, - claimable: amount.toString(), - waitingTime: waitingTime - } as UnstakingInfo); - }); - - const bnActiveStake = new BN(poolMemberInfo.points.toString()); - - if (!bnActiveStake.gt(BN_ZERO)) { - stakingStatus = EarningStatus.NOT_EARNING; - } - - return { - chain: chainInfo.slug, - type: StakingType.POOLED, - address, - status: stakingStatus, - activeStake: poolMemberInfo.points.toString(), - nominations: [joinedPoolInfo], // can only join 1 pool at a time - unstakings - } as NominatorMetadata; -} - -export async function getRelayValidatorsInfo (chain: string, substrateApi: _SubstrateApi, decimals: number, chainStakingMetadata: ChainStakingMetadata): Promise { - const chainApi = await substrateApi.isReady; - - const _era = await chainApi.api.query.staking.currentEra(); - const currentEra = _era.toString(); - - const allValidators: string[] = []; - const validatorInfoList: ValidatorInfo[] = []; - - const [_totalEraStake, _eraStakers, _minBond, _stakingRewards] = await Promise.all([ - chainApi.api.query.staking.erasTotalStake(parseInt(currentEra)), - chainApi.api.query.staking.erasStakers.entries(parseInt(currentEra)), - chainApi.api.query.staking.minNominatorBond(), - chainApi.api.query.stakingRewards?.data && chainApi.api.query.stakingRewards.data() - ]); - - const stakingRewards = _stakingRewards?.toPrimitive() as unknown as TernoaStakingRewardsStakingRewardsData; - - const unlimitedNominatorRewarded = chainApi.api.consts.staking.maxExposurePageSize !== undefined; - const maxNominatorRewarded = (chainApi.api.consts.staking.maxNominatorRewardedPerValidator || 0).toString(); - const bnTotalEraStake = new BN(_totalEraStake.toString()); - const eraStakers = _eraStakers as any[]; - - const rawMinBond = _minBond.toHuman(); - const minBond = rawMinBond.replaceAll(',', ''); - - const totalStakeMap: Record = {}; - const bnDecimals = new BN((10 ** decimals).toString()); - - for (const item of eraStakers) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const rawValidatorInfo = item[0].toHuman() as any[]; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const rawValidatorStat = item[1].toHuman() as Record; - - const validatorAddress = rawValidatorInfo[1] as string; - const rawTotalStake = rawValidatorStat.total as string; - const rawOwnStake = rawValidatorStat.own as string; - - const bnTotalStake = new BN(rawTotalStake.replaceAll(',', '')); - const bnOwnStake = new BN(rawOwnStake.replaceAll(',', '')); - const otherStake = bnTotalStake.sub(bnOwnStake); - - totalStakeMap[validatorAddress] = bnTotalStake; - - let nominatorCount = 0; - - if ('others' in rawValidatorStat) { - const others = rawValidatorStat.others as Record[]; - - nominatorCount = others.length; - } - - allValidators.push(validatorAddress); - - validatorInfoList.push({ - address: validatorAddress, - totalStake: bnTotalStake.toString(), - ownStake: bnOwnStake.toString(), - otherStake: otherStake.toString(), - nominatorCount, - // to be added later - commission: 0, - expectedReturn: 0, - blocked: false, - isVerified: false, - minBond, - isCrowded: unlimitedNominatorRewarded ? false : nominatorCount > parseInt(maxNominatorRewarded) - } as ValidatorInfo); - } - - const extraInfoMap: Record = {}; - - await Promise.all(allValidators.map(async (address) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const [_commissionInfo, [identity, isVerified]] = await Promise.all([ - chainApi.api.query.staking.validators(address), - parseIdentity(chainApi, address) - ]); - - const commissionInfo = _commissionInfo.toHuman() as Record; - - extraInfoMap[address] = { - commission: commissionInfo.commission as string, - blocked: commissionInfo.blocked as boolean, - identity, - isVerified: isVerified - } as ValidatorExtraInfo; - })); - - const bnAvgStake = bnTotalEraStake.divn(validatorInfoList.length).div(bnDecimals); - - for (const validator of validatorInfoList) { - const commission = extraInfoMap[validator.address].commission; - - const bnValidatorStake = totalStakeMap[validator.address].div(bnDecimals); - - if (_STAKING_CHAIN_GROUP.aleph.includes(chain)) { - validator.expectedReturn = calculateAlephZeroValidatorReturn(chainStakingMetadata.expectedReturn as number, getCommission(commission)); - } else if (_STAKING_CHAIN_GROUP.ternoa.includes(chain)) { - const rewardPerValidator = new BN(stakingRewards.sessionExtraRewardPayout).divn(allValidators.length).div(bnDecimals); - const validatorStake = totalStakeMap[validator.address].div(bnDecimals).toNumber(); - - validator.expectedReturn = calculateTernoaValidatorReturn(rewardPerValidator.toNumber(), validatorStake, getCommission(commission)); - } else { - validator.expectedReturn = calculateValidatorStakedReturn(chainStakingMetadata.expectedReturn as number, bnValidatorStake, bnAvgStake, getCommission(commission)); - } - - validator.commission = parseFloat(commission.split('%')[0]); - validator.blocked = extraInfoMap[validator.address].blocked; - validator.identity = extraInfoMap[validator.address].identity; - validator.isVerified = extraInfoMap[validator.address].isVerified; - } - - return validatorInfoList; -} - -export async function getRelayPoolsInfo (chain: string, substrateApi: _SubstrateApi): Promise { - const chainApi = await substrateApi.isReady; - - const nominationPools: NominationPoolInfo[] = []; - - const _allPoolsInfo = await chainApi.api.query.nominationPools.reversePoolIdLookup.entries(); - - await Promise.all(_allPoolsInfo.map(async (_poolInfo) => { - const poolAddressList = _poolInfo[0].toHuman() as string[]; - const poolAddress = poolAddressList[0]; - const poolId = _poolInfo[1].toPrimitive() as number; - const poolsPalletId = substrateApi.api.consts.nominationPools.palletId.toString(); - const poolStashAccount = parsePoolStashAddress(substrateApi.api, 0, poolId, poolsPalletId); - - const [_nominations, _bondedPool, _metadata, _minimumActiveStake] = await Promise.all([ - chainApi.api.query.staking.nominators(poolStashAccount), - chainApi.api.query.nominationPools.bondedPools(poolId), - chainApi.api.query.nominationPools.metadata(poolId), - chainApi.api.query.staking.minimumActiveStake() - ]); - - const minimumActiveStake = _minimumActiveStake.toPrimitive() as number; - const nominations = _nominations.toJSON() as unknown as PalletStakingNominations; - - const poolMetadata = _metadata.toPrimitive() as unknown as string; - const bondedPool = _bondedPool.toPrimitive() as unknown as PalletNominationPoolsBondedPoolInner; - - // const poolName = transformPoolName(poolMetadata.isUtf8 ? poolMetadata.toUtf8() : poolMetadata.toString()); - - const poolName = isHex(poolMetadata) ? hexToString(poolMetadata) : poolMetadata; - - const isPoolOpen = bondedPool.state === 'Open'; - const isPoolNominating = !!nominations && nominations.targets.length > 0; - const isPoolEarningReward = bondedPool.points > minimumActiveStake; - - nominationPools.push({ - id: poolId, - address: poolAddress, - name: poolName, - bondedAmount: bondedPool.points?.toString() || '0', - roles: bondedPool.roles, - memberCounter: bondedPool.memberCounter, - state: bondedPool.state, - isProfitable: isPoolOpen && isPoolNominating && isPoolEarningReward - }); - })); - - return nominationPools; -} - -export async function getRelayBondingExtrinsic (substrateApi: _SubstrateApi, amount: string, targetValidators: ValidatorInfo[], chainInfo: _ChainInfo, address: string, nominatorMetadata?: NominatorMetadata, bondDest = 'Staked') { - const chainApi = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - let bondTx; - let nominateTx; - - const _params = chainApi.api.tx.staking.bond.toJSON() as Record; - const paramsCount = (_params.args as any[]).length; - - const validatorParamList = targetValidators.map((validator) => { - return validator.address; - }); - - if (!nominatorMetadata) { - if (paramsCount === 2) { - bondTx = chainApi.api.tx.staking.bond(binaryAmount, bondDest); - } else { - // @ts-ignore - bondTx = chainApi.api.tx.staking.bond(address, binaryAmount, bondDest); - } - - nominateTx = chainApi.api.tx.staking.nominate(validatorParamList); - - return chainApi.api.tx.utility.batchAll([bondTx, nominateTx]); - } - - if (!nominatorMetadata.isBondedBefore) { // first time - if (paramsCount === 2) { - bondTx = chainApi.api.tx.staking.bond(binaryAmount, bondDest); - } else { - // @ts-ignore - bondTx = chainApi.api.tx.staking.bond(nominatorMetadata.address, binaryAmount, bondDest); - } - - nominateTx = chainApi.api.tx.staking.nominate(validatorParamList); - - return chainApi.api.tx.utility.batchAll([bondTx, nominateTx]); - } else { - if (binaryAmount.gt(BN_ZERO)) { - bondTx = chainApi.api.tx.staking.bondExtra(binaryAmount); - } - - if (nominatorMetadata.isBondedBefore && targetValidators.length > 0) { - nominateTx = chainApi.api.tx.staking.nominate(validatorParamList); - } - } - - if (bondTx && !nominateTx) { - return bondTx; - } else if (nominateTx && !bondTx) { - return nominateTx; - } - - // @ts-ignore - return chainApi.api.tx.utility.batchAll([bondTx, nominateTx]); -} - -export async function getRelayUnbondingExtrinsic (substrateApi: _SubstrateApi, amount: string, nominatorMetadata: NominatorMetadata) { - const chainApi = await substrateApi.isReady; - const binaryAmount = new BN(amount); - - const isUnstakeAll = amount === nominatorMetadata.activeStake; - - if (isUnstakeAll) { - const chillTx = chainApi.api.tx.staking.chill(); - const unbondTx = chainApi.api.tx.staking.unbond(binaryAmount); - - return chainApi.api.tx.utility.batchAll([chillTx, unbondTx]); - } - - return chainApi.api.tx.staking.unbond(binaryAmount); -} - -export async function getRelayWithdrawalExtrinsic (substrateApi: _SubstrateApi, address: string) { - const chainApi = await substrateApi.isReady; - - if (chainApi.api.tx.staking.withdrawUnbonded.meta.args.length === 1) { - const _slashingSpans = (await chainApi.api.query.staking.slashingSpans(address)).toHuman() as Record; - const slashingSpanCount = _slashingSpans !== null ? _slashingSpans.spanIndex as string : '0'; - - return chainApi.api.tx.staking.withdrawUnbonded(slashingSpanCount); - } else { - // @ts-ignore - return chainApi.api.tx.staking.withdrawUnbonded(); - } -} - -export async function getRelayCancelWithdrawalExtrinsic (substrateApi: _SubstrateApi, selectedUnstaking: UnstakingInfo) { - const chainApi = await substrateApi.isReady; - - return chainApi.api.tx.staking.rebond(selectedUnstaking.claimable); -} - -// Pooling txs - -export async function getPoolingClaimRewardExtrinsic (substrateApi: _SubstrateApi, bondReward = true) { - const chainApi = await substrateApi.isReady; - - if (bondReward) { - return chainApi.api.tx.nominationPools.bondExtra('Rewards'); - } - - return chainApi.api.tx.nominationPools.claimPayout(); -} - -export async function getPoolingBondingExtrinsic (substrateApi: _SubstrateApi, amount: string, selectedPoolId: number, nominatorMetadata: NominatorMetadata | undefined) { - const chainApi = await substrateApi.isReady; - const bnActiveStake = new BN(nominatorMetadata?.activeStake || '0'); - - if (bnActiveStake.gt(BN_ZERO)) { // already joined a pool - return chainApi.api.tx.nominationPools.bondExtra({ FreeBalance: amount }); - } - - return chainApi.api.tx.nominationPools.join(amount, selectedPoolId); -} - -export async function getPoolingUnbondingExtrinsic (substrateApi: _SubstrateApi, amount: string, nominatorMetadata: NominatorMetadata) { - const chainApi = await substrateApi.isReady; - - return chainApi.api.tx.nominationPools.unbond({ Id: nominatorMetadata.address }, amount); -} - -export async function getPoolingWithdrawalExtrinsic (substrateApi: _SubstrateApi, nominatorMetadata: NominatorMetadata) { - const chainApi = await substrateApi.isReady; - - if (chainApi.api.tx.nominationPools.withdrawUnbonded.meta.args.length === 2) { - const _slashingSpans = (await chainApi.api.query.staking.slashingSpans(nominatorMetadata.address)).toHuman() as Record; - const slashingSpanCount = _slashingSpans !== null ? _slashingSpans.spanIndex as string : '0'; - - return chainApi.api.tx.nominationPools.withdrawUnbonded({ Id: nominatorMetadata.address }, slashingSpanCount); - } else { - // @ts-ignore - return chainApi.api.tx.nominationPools.withdrawUnbonded({ Id: nominatorMetadata.address }); - } -} diff --git a/packages/extension-base/src/koni/api/staking/bonding/utils.ts b/packages/extension-base/src/koni/api/staking/bonding/utils.ts deleted file mode 100644 index 47b9b88b3a8..00000000000 --- a/packages/extension-base/src/koni/api/staking/bonding/utils.ts +++ /dev/null @@ -1,802 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { NominationInfo, NominatorMetadata, StakingType, UnstakingInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getAstarWithdrawable } from '@subwallet/extension-base/koni/api/staking/bonding/astar'; -import { _KNOWN_CHAIN_INFLATION_PARAMS, _SUBSTRATE_DEFAULT_INFLATION_PARAMS, _SubstrateInflationParams } from '@subwallet/extension-base/services/chain-service/constants'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _getChainNativeTokenBasicInfo } from '@subwallet/extension-base/services/chain-service/utils'; -import { _STAKING_CHAIN_GROUP, RELAY_HANDLER_DIRECT_STAKING_CHAINS } from '@subwallet/extension-base/services/earning-service/constants'; -import { EarningStatus, PalletStakingEraRewardPoints, PalletStakingValidatorPrefs, UnstakingStatus, YieldPoolInfo, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; -import { detectTranslate, parseRawNumber, reformatAddress } from '@subwallet/extension-base/utils'; -import { balanceFormatter, formatNumber } from '@subwallet/extension-base/utils/number'; -import BigNumber from 'bignumber.js'; -import { t } from 'i18next'; - -import { ApiPromise } from '@polkadot/api'; -import { Codec } from '@polkadot/types/types'; -import { BN, BN_BILLION, BN_HUNDRED, BN_MILLION, BN_THOUSAND, BN_ZERO, bnToU8a, stringToU8a, u8aConcat } from '@polkadot/util'; - -export interface PalletDappsStakingDappInfo { - address: string, - name: string, - gitHubUrl: string, - tags: string[], - url: string, - imagesUrl: string[] -} - -export interface PalletDappsStakingUnlockingChunk { - amount: number, - unlockEra: number -} - -export interface PalletDappsStakingAccountLedger { - locked: number, - unbondingInfo: { - unlockingChunks: PalletDappsStakingUnlockingChunk[] - } -} - -export interface BlockHeader { - parentHash: string, - number: number, - stateRoot: string, - extrinsicsRoot: string -} - -export interface ParachainStakingStakeOption { - owner: string, - amount: number -} - -export interface KrestDelegateState { - delegations: ParachainStakingStakeOption[], - total: string -} - -export interface ParachainStakingCandidateMetadata { - bond: string, - delegationCount: number, - totalCounted: string, - lowestTopDelegationAmount: string, - status: any | 'Active' -} - -export enum PalletParachainStakingRequestType { - REVOKE = 'revoke', - DECREASE = 'decrease', - BOND_LESS = 'bondLess' -} - -export interface PalletParachainStakingDelegationRequestsScheduledRequest { - delegator: string, - whenExecutable: number, - action: Record -} - -export interface PalletParachainStakingDelegationInfo { - owner: string, - amount: number -} - -export interface PalletParachainStakingDelegator { - id: string, - delegations: PalletParachainStakingDelegationInfo[], - total: number, - lessTotal: number, - status: number -} - -export interface PalletIdentityRegistration { - judgements: any[], - deposit: number, - info: { - display: { - Raw: string - }, - web: { - Raw: string - }, - twitter: { - Raw: string - }, - riot: { - Raw: string - } - } -} - -export type PalletIdentitySuper = [string, { Raw: string }] - -export interface Unlocking { - remainingEras: BN; - value: BN; -} - -export function parsePoolStashAddress (api: ApiPromise, index: number, poolId: number, poolsPalletId: string) { - const ModPrefix = stringToU8a('modl'); - const U32Opts = { bitLength: 32, isLe: true }; - const EmptyH256 = new Uint8Array(32); - - return api.registry - .createType( - 'AccountId32', - u8aConcat( - ModPrefix, - poolsPalletId, - new Uint8Array([index]), - bnToU8a(new BN(poolId.toString()), U32Opts), - EmptyH256 - ) - ) - .toString(); -} - -export function transformPoolName (input: string): string { - return input.replace(/[^\x20-\x7E]/g, ''); -} - -export function getInflationParams (networkKey: string): _SubstrateInflationParams { - return _KNOWN_CHAIN_INFLATION_PARAMS[networkKey] || _SUBSTRATE_DEFAULT_INFLATION_PARAMS; -} - -export function calcInflationUniformEraPayout (totalIssuance: BN, yearlyInflationInTokens: number): number { - const totalIssuanceInTokens = totalIssuance.div(BN_BILLION).div(BN_THOUSAND).toNumber(); - - return (totalIssuanceInTokens === 0 ? 0.0 : yearlyInflationInTokens / totalIssuanceInTokens); -} - -export function calcInflationRewardCurve (minInflation: number, stakedFraction: number, idealStake: number, idealInterest: number, falloff: number) { - return (minInflation + ( - stakedFraction <= idealStake - ? (stakedFraction * (idealInterest - (minInflation / idealStake))) - : (((idealInterest * idealStake) - minInflation) * Math.pow(2, (idealStake - stakedFraction) / falloff)) - )); -} - -export function calculateInflation (totalEraStake: BN, totalIssuance: BN, numAuctions: number, networkKey: string) { - const inflationParams = getInflationParams(networkKey); - const { auctionAdjust, auctionMax, falloff, maxInflation, minInflation, stakeTarget } = inflationParams; - const idealStake = stakeTarget - (Math.min(auctionMax, numAuctions) * auctionAdjust); - const idealInterest = maxInflation / idealStake; - const stakedFraction = totalEraStake.mul(BN_MILLION).div(totalIssuance).toNumber() / BN_MILLION.toNumber(); - - if (_STAKING_CHAIN_GROUP.aleph.includes(networkKey)) { - if (inflationParams.yearlyInflationInTokens) { - return 100 * calcInflationUniformEraPayout(totalIssuance, inflationParams.yearlyInflationInTokens); - } else { - return 100 * calcInflationRewardCurve(minInflation, stakedFraction, idealStake, idealInterest, falloff); - } - } else { - return 100 * (minInflation + ( - stakedFraction <= idealStake - ? (stakedFraction * (idealInterest - (minInflation / idealStake))) - : (((idealInterest * idealStake) - minInflation) * Math.pow(2, (idealStake - stakedFraction) / falloff)) - )); - } -} - -export function calculateChainStakedReturn (inflation: number, totalEraStake: BN, totalIssuance: BN, networkKey: string) { - const stakedFraction = totalEraStake.mul(BN_MILLION).div(totalIssuance).toNumber() / BN_MILLION.toNumber(); - - let stakedReturn = inflation / stakedFraction; - - if (_STAKING_CHAIN_GROUP.aleph.includes(networkKey)) { - stakedReturn *= 0.9; // 10% goes to treasury - } - - return stakedReturn; -} - -export async function calculateChainStakedReturnV2 (chainInfo: _ChainInfo, totalIssuance: string, erasPerDay: number, lastTotalStaked: string, validatorEraReward: BigNumber, inflation: BigNumber, isCompound?: boolean) { - if (chainInfo.slug === 'analog_timechain') { - return await calculateAnalogChainStakedReturn(); - } - - const DAYS_PER_YEAR = 365; - const { decimals } = _getChainNativeTokenBasicInfo(chainInfo); - - const lastTotalStakedUnit = (new BigNumber(lastTotalStaked)).dividedBy(new BigNumber(10 ** decimals)); - const totalIssuanceUnit = (new BigNumber(totalIssuance)).dividedBy(new BigNumber(10 ** decimals)); - const supplyStaked = lastTotalStakedUnit.dividedBy(totalIssuanceUnit); - - const dayRewardRate = validatorEraReward.multipliedBy(erasPerDay).dividedBy(totalIssuance).multipliedBy(100); - - let inflationToStakers: BigNumber; - - if (!isCompound) { - inflationToStakers = dayRewardRate.multipliedBy(DAYS_PER_YEAR); - } else { - const multiplier = dayRewardRate.dividedBy(100).plus(1).exponentiatedBy(365); - - inflationToStakers = new BigNumber(100).multipliedBy(multiplier).minus(100); - } - - const averageRewardRate = (['avail_mainnet', 'dentnet'].includes(chainInfo.slug) ? inflation : inflationToStakers).dividedBy(supplyStaked); - - return averageRewardRate.toNumber(); -} - -export function calculateAlephZeroValidatorReturn (chainStakedReturn: number, commission: number) { - return chainStakedReturn * (100 - commission) / 100; -} - -export function calculateEnergyWebCollatorReturn (annualReward: string, collatorCommission: number, numberCollators: number, totalStake: string): number { - const rewardForNominators = new BigNumber(annualReward).multipliedBy(1 - collatorCommission); - const rewardPerNominator = rewardForNominators.div(numberCollators); - - return rewardPerNominator.div(totalStake).shiftedBy(2).toNumber(); -} - -export function calculateTernoaValidatorReturn (rewardPerValidator: number, validatorStake: number, commission: number) { - const percentRewardForNominators = (100 - commission) / 100; - const rewardForNominators = rewardPerValidator * percentRewardForNominators; - - const stakeRatio = rewardForNominators / validatorStake; - - return stakeRatio * 365 * 100; -} - -export async function calculateAnalogChainStakedReturn (): Promise { - const url = 'https://explorer-api.analog.one/api/nominations?projection=apy,rewardsClaimed,eraEndsTime'; - - try { - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - const errorText = await response.text(); - - throw new Error(`API error: ${response.status} - ${errorText}`); - } - - const apyInfo = await response.json() as { - data: { - apy: number; - } - }; - - return apyInfo?.data?.apy as number | undefined; - } catch (e) { - console.error('Fetch error:', e); - - return undefined; - } -} - -export function calculateValidatorStakedReturn (chainStakedReturn: number, totalValidatorStake: BN, avgStake: BN, commission: number) { - const bnAdjusted = avgStake.mul(BN_HUNDRED).div(totalValidatorStake); - const adjusted = bnAdjusted.toNumber() * chainStakedReturn; - // todo: should calculated in bignumber instead number? - const stakedReturn = (adjusted > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : adjusted) / 100; - - return stakedReturn * (100 - commission) / 100; // Deduct commission -} - -export function getCommission (commissionString: string) { - return parseFloat(commissionString.split('%')[0]); // Example: 12% -} - -export interface InflationConfig { - expect: { - min: string, - ideal: string, - max: string - }, - annual: { - min: string, - ideal: string, - max: string - }, - round: { - min: string, - ideal: string, - max: string - } -} - -export function getParaCurrentInflation (totalStaked: number, inflationConfig: InflationConfig) { // read more at https://hackmd.io/@sbAqOuXkRvyiZPOB3Ryn6Q/Sypr3ZJh5 - const expectMin = parseRawNumber(inflationConfig.expect.min); - const expectMax = parseRawNumber(inflationConfig.expect.max); - - if (totalStaked < expectMin) { - const inflationString = inflationConfig.annual.min.split('%')[0]; - - return parseFloat(inflationString); - } else if (totalStaked > expectMax) { - const inflationString = inflationConfig.annual.max.split('%')[0]; - - return parseFloat(inflationString); - } - - const inflationString = inflationConfig.annual.ideal.split('%')[0]; - - return parseFloat(inflationString); -} - -export interface TuringOptimalCompoundFormat { - period: string; // in days - apy: string; -} - -// validations and check conditions -export function isShowNominationByValidator (chain: string): 'showByValue' | 'showByValidator' | 'mixed' { - if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - return 'showByValue'; - } else if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - return 'mixed'; - } else if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - return 'showByValidator'; - } - - return 'showByValue'; -} - -export function getBondedValidators (nominations: NominationInfo[]) { - const bondedValidators: string[] = []; - let nominationCount = 0; - - for (const nomination of nominations) { - nominationCount += 1; - bondedValidators.push(reformatAddress(nomination.validatorAddress, 0)); - } - - return { - nominationCount, - bondedValidators - }; -} - -export function isUnstakeAll (selectedValidator: string, nominations: NominationInfo[], unstakeAmount: string) { - let isUnstakeAll = false; - - for (const nomination of nominations) { - const parsedValidatorAddress = reformatAddress(nomination.validatorAddress, 0); - const parsedSelectedValidator = reformatAddress(selectedValidator, 0); - - if (parsedValidatorAddress === parsedSelectedValidator) { - if (unstakeAmount === nomination.activeStake) { - isUnstakeAll = true; - } - - break; - } - } - - return isUnstakeAll; -} - -export enum YieldAction { - STAKE = 'STAKE', - UNSTAKE = 'UNSTAKE', - WITHDRAW = 'WITHDRAW', - CLAIM_REWARD = 'CLAIM_REWARD', - CANCEL_UNSTAKE = 'CANCEL_UNSTAKE', - - START_EARNING = 'EARN', - WITHDRAW_EARNING = 'WITHDRAW_EARNING', - CUSTOM_ACTION = 'CUSTOM_ACTION' -} - -export function getYieldAvailableActionsByType (yieldPoolInfo: YieldPoolInfo): YieldAction[] { - if ([YieldPoolType.NATIVE_STAKING, YieldPoolType.NOMINATION_POOL].includes(yieldPoolInfo.type)) { - if (yieldPoolInfo.type === YieldPoolType.NOMINATION_POOL) { - return [YieldAction.STAKE, YieldAction.CLAIM_REWARD, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; - } - - const chain = yieldPoolInfo.chain; - - if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW, YieldAction.CANCEL_UNSTAKE]; - } else if (_STAKING_CHAIN_GROUP.energy.includes(chain)) { - return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW, YieldAction.CANCEL_UNSTAKE]; - } else if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - return [YieldAction.STAKE, YieldAction.CLAIM_REWARD, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; - } - } - - if (yieldPoolInfo.type === YieldPoolType.LENDING) { - return [YieldAction.START_EARNING, YieldAction.WITHDRAW_EARNING]; - } else if (yieldPoolInfo.type === YieldPoolType.LIQUID_STAKING) { - return [YieldAction.START_EARNING, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; - } - - return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW, YieldAction.CANCEL_UNSTAKE]; -} - -export function getYieldAvailableActionsByPosition (yieldPosition: YieldPositionInfo, yieldPoolInfo: YieldPoolInfo, unclaimedReward?: string): YieldAction[] { - const result: YieldAction[] = []; - - if ([YieldPoolType.NATIVE_STAKING, YieldPoolType.NOMINATION_POOL].includes(yieldPoolInfo.type)) { - result.push(YieldAction.STAKE); - - const bnActiveStake = new BigNumber(yieldPosition.activeStake); - - if (yieldPosition.activeStake && bnActiveStake.gt('0')) { - result.push(YieldAction.UNSTAKE); - - const isAstarNetwork = _STAKING_CHAIN_GROUP.astar.includes(yieldPosition.chain); - const isAmplitudeNetwork = _STAKING_CHAIN_GROUP.amplitude.includes(yieldPosition.chain); - const bnUnclaimedReward = new BigNumber(unclaimedReward || '0'); - - if ( - ((yieldPosition.type === YieldPoolType.NOMINATION_POOL || isAmplitudeNetwork) && bnUnclaimedReward.gt('0')) || - isAstarNetwork - ) { - result.push(YieldAction.CLAIM_REWARD); - } - } - - if (yieldPosition.unstakings.length > 0) { - result.push(YieldAction.CANCEL_UNSTAKE); - const hasClaimable = yieldPosition.unstakings.some((unstaking) => unstaking.status === UnstakingStatus.CLAIMABLE); - - if (hasClaimable) { - result.push(YieldAction.WITHDRAW); - } - } - } else if (yieldPoolInfo.type === YieldPoolType.LIQUID_STAKING) { - result.push(YieldAction.START_EARNING); - - const activeBalance = new BigNumber(yieldPosition.activeStake); - - if (activeBalance.gt('0')) { - result.push(YieldAction.UNSTAKE); - } - - const hasWithdrawal = yieldPosition.unstakings.some((unstakingInfo) => unstakingInfo.status === UnstakingStatus.CLAIMABLE); - - if (hasWithdrawal) { - result.push(YieldAction.WITHDRAW); - } - - // TODO: check has unstakings to withdraw - } else { - result.push(YieldAction.START_EARNING); - result.push(YieldAction.WITHDRAW_EARNING); // TODO - } - - return result; -} - -export enum StakingAction { - STAKE = 'STAKE', - UNSTAKE = 'UNSTAKE', - WITHDRAW = 'WITHDRAW', - CLAIM_REWARD = 'CLAIM_REWARD', - CANCEL_UNSTAKE = 'CANCEL_UNSTAKE' -} - -export function getStakingAvailableActionsByChain (chain: string, type: StakingType): StakingAction[] { - if (type === StakingType.POOLED) { - return [StakingAction.STAKE, StakingAction.UNSTAKE, StakingAction.WITHDRAW, StakingAction.CLAIM_REWARD]; - } - - if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - return [StakingAction.STAKE, StakingAction.UNSTAKE, StakingAction.WITHDRAW, StakingAction.CANCEL_UNSTAKE]; - } else if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - return [StakingAction.STAKE, StakingAction.UNSTAKE, StakingAction.WITHDRAW, StakingAction.CLAIM_REWARD]; - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - return [StakingAction.STAKE, StakingAction.UNSTAKE, StakingAction.WITHDRAW]; - } - - return [StakingAction.STAKE, StakingAction.UNSTAKE, StakingAction.WITHDRAW, StakingAction.CANCEL_UNSTAKE]; -} - -export function getStakingAvailableActionsByNominator (nominatorMetadata: NominatorMetadata, unclaimedReward?: string): StakingAction[] { - const result: StakingAction[] = [StakingAction.STAKE]; - - const bnActiveStake = new BN(nominatorMetadata.activeStake); - - if (nominatorMetadata.activeStake && bnActiveStake.gt(BN_ZERO)) { - result.push(StakingAction.UNSTAKE); - - const isAstarNetwork = _STAKING_CHAIN_GROUP.astar.includes(nominatorMetadata.chain); - const isAmplitudeNetwork = _STAKING_CHAIN_GROUP.amplitude.includes(nominatorMetadata.chain); - const bnUnclaimedReward = new BN(unclaimedReward || '0'); - - if ( - ((nominatorMetadata.type === StakingType.POOLED || isAmplitudeNetwork) && bnUnclaimedReward.gt(BN_ZERO)) || - isAstarNetwork - ) { - result.push(StakingAction.CLAIM_REWARD); - } - } - - if (nominatorMetadata.unstakings.length > 0) { - result.push(StakingAction.CANCEL_UNSTAKE); - const hasClaimable = nominatorMetadata.unstakings.some((unstaking) => unstaking.status === UnstakingStatus.CLAIMABLE); - - if (hasClaimable) { - result.push(StakingAction.WITHDRAW); - } - } - - return result; -} - -export function isActionFromValidator (stakingType: StakingType, chain: string) { - if (stakingType === StakingType.POOLED || stakingType === StakingType.LIQUID_STAKING) { - return false; - } - - if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - return true; - } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - return true; - } else if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - return true; - } - - return false; -} - -export function getWithdrawalInfo (nominatorMetadata: NominatorMetadata) { - const unstakings = nominatorMetadata.unstakings; - - let result: UnstakingInfo | undefined; - - if (_STAKING_CHAIN_GROUP.astar.includes(nominatorMetadata.chain)) { - return getAstarWithdrawable(nominatorMetadata); - } - - for (const unstaking of unstakings) { - if (unstaking.status === UnstakingStatus.CLAIMABLE) { - result = unstaking; // only get the first withdrawal - break; - } - } - - return result; -} - -export function getEarningStatusByNominations (bnTotalActiveStake: BN, nominationList: NominationInfo[]): EarningStatus { - let stakingStatus: EarningStatus = EarningStatus.EARNING_REWARD; - - if (bnTotalActiveStake.isZero()) { - stakingStatus = EarningStatus.NOT_EARNING; - } else { - let invalidDelegationCount = 0; - - for (const nomination of nominationList) { - if (nomination.status === EarningStatus.NOT_EARNING) { - invalidDelegationCount += 1; - } - } - - if (invalidDelegationCount > 0 && invalidDelegationCount < nominationList.length) { - stakingStatus = EarningStatus.PARTIALLY_EARNING; - } else if (invalidDelegationCount === nominationList.length) { - stakingStatus = EarningStatus.NOT_EARNING; - } - } - - return stakingStatus; -} - -export function getValidatorLabel (chain: string) { - if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - return 'dApp'; - } else if (RELAY_HANDLER_DIRECT_STAKING_CHAINS.includes(chain) || _STAKING_CHAIN_GROUP.bittensor.includes(chain)) { - return 'Validator'; - } - - return 'Collator'; -} - -export function getAvgValidatorEraReward (supportedDays: number, eraRewardHistory: Codec[]) { - let sumEraReward = new BigNumber(0); - let failEra = 0; - - for (const _item of eraRewardHistory) { - const item = _item.toString(); - - if (!item) { - failEra += 1; - } else { - const eraReward = new BigNumber(item); - - sumEraReward = sumEraReward.plus(eraReward); - } - } - - return sumEraReward.dividedBy(new BigNumber(supportedDays - failEra)); -} - -export function getSupportedDaysByHistoryDepth (erasPerDay: number, maxSupportedEras: number, liveDay?: number) { - const maxSupportDay = Math.floor(maxSupportedEras / erasPerDay); - - if (liveDay && liveDay <= 30) { - return Math.min(Math.floor(liveDay - 1), maxSupportDay); - } - - if (maxSupportDay > 30) { - return 30; - } else { - return maxSupportDay; - } -} - -export function getRelayValidatorPointsMap (eraRewardMap: Record) { - // mapping store validator and totalPoints - const validatorTotalPointsMap: Record = {}; - - Object.values(eraRewardMap).forEach((info) => { - const individual = info.individual; - - Object.entries(individual).forEach(([validator, points]) => { - if (!validatorTotalPointsMap[validator]) { - validatorTotalPointsMap[validator] = new BigNumber(points); - } else { - validatorTotalPointsMap[validator] = validatorTotalPointsMap[validator].plus(points); - } - }); - }); - - return validatorTotalPointsMap; -} - -export function getRelayTopValidatorByPoints (validatorPointsList: Record) { - const sortValidatorPointsList = Object.fromEntries( - Object.entries(validatorPointsList) - .sort( - ( - a: [string, BigNumber], - b: [string, BigNumber] - ) => a[1].minus(b[1]).toNumber() - ) - .reverse() - ); - - // keep 50% first validator - const entries = Object.entries(sortValidatorPointsList); - const endIndex = Math.ceil(entries.length / 2); - const top50PercentEntries = entries.slice(0, endIndex); - const top50PercentRecord = Object.fromEntries(top50PercentEntries); - - return Object.keys(top50PercentRecord); -} - -export function getRelayBlockedValidatorList (validators: any[]) { - const blockValidatorList: string[] = []; - - for (const validator of validators) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const validatorAddress = validator[0].toHuman()[0] as string; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const validatorPrefs = validator[1].toHuman() as unknown as PalletStakingValidatorPrefs; - - const isBlocked = validatorPrefs.blocked; - - if (isBlocked) { - blockValidatorList.push(validatorAddress); - } - } - - return blockValidatorList; -} - -export function getRelayWaitingValidatorList (validators: any[]) { - const waitingValidators: string[] = []; - - for (const validator of validators) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const validatorAddress = validator[0].toHuman()[0] as string; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const validatorPrefs = validator[1].toHuman() as unknown as PalletStakingValidatorPrefs; - - const isBlocked = validatorPrefs.blocked; - - if (!isBlocked) { - waitingValidators.push(validatorAddress); - } - } - - return waitingValidators; -} - -export function getRelayEraRewardMap (eraRewardPointArray: Codec[], startEraForPoints: number) { - const eraRewardMap: Record = {}; - - for (const item of eraRewardPointArray) { - eraRewardMap[startEraForPoints] = item.toPrimitive() as unknown as PalletStakingEraRewardPoints; - startEraForPoints++; - } - - return eraRewardMap; -} - -export async function getRelayMaxNominations (substrateApi: _SubstrateApi, chain: string) { - await substrateApi.isReady; - const maxNominations = substrateApi.api.consts.staking?.maxNominations?.toString(); - const _maxNominationsByNominationQuota = await substrateApi.api.call.stakingApi?.nominationsQuota(0); - const maxNominationsByNominationQuota = _maxNominationsByNominationQuota?.toString(); - - const fallbackMaxNominations = ['zkverify_testnet', 'zkverify'].includes(chain) ? '10' : '16'; - - return maxNominationsByNominationQuota || maxNominations || fallbackMaxNominations; -} - -export const getMinStakeErrorMessage = (chainInfo: _ChainInfo, bnMinStake: BN): string => { - const tokenInfo = _getChainNativeTokenBasicInfo(chainInfo); - const number = formatNumber(bnMinStake.toString(), tokenInfo.decimals || 0, balanceFormatter); - - return t('bg.EARNING.koni.api.staking.bonding.utils.insufficientStakeToEarn', { replace: { tokenSymbol: tokenInfo.symbol, number } }); -}; - -export const getMaxValidatorErrorMessage = (chainInfo: _ChainInfo, max: number): string => { - let message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxValidatorsSelection'); - const label = getValidatorLabel(chainInfo.slug); - - if (max > 1) { - switch (label) { - case 'dApp': - message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxDappsSelection'); - break; - case 'Collator': - message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxCollatorsSelection'); - break; - case 'Validator': - message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxValidatorsSelection'); - break; - } - } else { - switch (label) { - case 'dApp': - message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxOneDappSelection'); - break; - case 'Collator': - message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxOneCollatorSelection'); - break; - case 'Validator': - message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxOneValidatorSelection'); - break; - } - } - - return t(message, { replace: { number: max } }); -}; - -export const getExistUnstakeErrorMessage = (chain: string, type?: StakingType, isStakeMore?: boolean): string => { - const label = getValidatorLabel(chain); - - if (!isStakeMore) { - switch (label) { - case 'dApp': - return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromDappOnce'); - case 'Collator': - return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromCollatorOnce'); - - case 'Validator': { - if (type === StakingType.POOLED) { - return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromPoolOnce'); - } - - return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromValidatorOnce'); - } - } - } else { - switch (label) { - case 'dApp': - return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingDapp'); - case 'Collator': - return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingCollator'); - - case 'Validator': { - if (type === StakingType.POOLED) { - return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingPool'); - } - - return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingValidator'); - } - } - } -}; diff --git a/packages/extension-base/src/koni/api/staking/config.ts b/packages/extension-base/src/koni/api/staking/config.ts deleted file mode 100644 index 5927a3a2e7f..00000000000 --- a/packages/extension-base/src/koni/api/staking/config.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { COMMON_CHAIN_SLUGS } from '@subwallet/chain-list'; - -export const SUBSQUID_ENDPOINTS: Record = { - [COMMON_CHAIN_SLUGS.KUSAMA]: 'https://squid.subsquid.io/kusama-explorer/graphql', - [COMMON_CHAIN_SLUGS.POLKADOT]: 'https://squid.subsquid.io/polkadot-explorer/graphql', - [COMMON_CHAIN_SLUGS.ASTAR]: 'https://squid.subsquid.io/astar-explorer/graphql', - [COMMON_CHAIN_SLUGS.MOONRIVER]: 'https://squid.subsquid.io/moonriver-explorer/graphql', - [COMMON_CHAIN_SLUGS.MOONBEAM]: 'https://squid.subsquid.io/moonbeam-explorer/graphql' -}; - -export const INDEXER_SUPPORTED_STAKING_CHAINS = [ - COMMON_CHAIN_SLUGS.POLKADOT as string, - COMMON_CHAIN_SLUGS.KUSAMA as string, - COMMON_CHAIN_SLUGS.ASTAR as string, - COMMON_CHAIN_SLUGS.MOONRIVER as string, - COMMON_CHAIN_SLUGS.MOONBEAM as string -]; - -export const SUBQUERY_ENDPOINTS: Record = { - polkadot: 'https://api.subquery.network/sq/nova-wallet/nova-westend', - kusama: 'https://api.subquery.network/sq/nova-wallet/nova-kusama', - westend: 'https://api.subquery.network/sq/nova-wallet/nova-westend', - picasso: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-picasso', - calamari: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-calamari', - khala: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-khala', - parallel: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-parallel', - bifrost: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-bifrost', - clover: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-clover', - basilisk: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-basilisk', - acala: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-acala', - astar: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-astar', - karura: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-karura', - altair: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-altair', - kilt: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-kilt', - robonomics: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-robonomics', - statemint: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-statemint', - quartz: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-quartz', - zeigeist: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-zeitgeist', - shiden: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-shiden', - statemine: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-statemine', - moonbeam: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-moonbeam', - moonriver: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-moonriver', - pioneer: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-bit-country' -}; diff --git a/packages/extension-base/src/koni/api/staking/index.ts b/packages/extension-base/src/koni/api/staking/index.ts deleted file mode 100644 index b0cf0771f21..00000000000 --- a/packages/extension-base/src/koni/api/staking/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { ChainType, NominatorMetadata, StakingItem, StakingRewardItem } from '@subwallet/extension-base/background/KoniTypes'; -import { getAmplitudeStakingOnChain, getAstarStakingOnChain, getParaStakingOnChain } from '@subwallet/extension-base/koni/api/staking/paraChain'; -import { getNominationPoolReward, getRelayPoolingOnChain, getRelayStakingOnChain } from '@subwallet/extension-base/koni/api/staking/relayChain'; -import { getAllSubsquidStaking } from '@subwallet/extension-base/koni/api/staking/subsquidStaking'; -import { _PURE_EVM_CHAINS } from '@subwallet/extension-base/services/chain-service/constants'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _isChainEvmCompatible, _isChainSupportSubstrateStaking, _isSubstrateRelayChain } from '@subwallet/extension-base/services/chain-service/utils'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { getAddressesByChainType } from '@subwallet/extension-base/utils'; - -interface PromiseMapping { - api: _SubstrateApi, - chain: string -} - -export function stakingOnChainApi (addresses: string[], substrateApiMap: Record, chainInfoMap: Record, stakingCallback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (nominatorMetadata: NominatorMetadata) => void) { - const filteredApiMap: PromiseMapping[] = []; - const evmAddresses = getAddressesByChainType(addresses, [ChainType.EVM]); - const substrateAddresses = getAddressesByChainType(addresses, [ChainType.SUBSTRATE]); - - Object.entries(chainInfoMap).forEach(([networkKey, chainInfo]) => { - if (_PURE_EVM_CHAINS.indexOf(networkKey) < 0 && _isChainSupportSubstrateStaking(chainInfo)) { - filteredApiMap.push({ chain: networkKey, api: substrateApiMap[networkKey] }); - } - }); - - const unsubList: VoidFunction[] = []; - - // eslint-disable-next-line @typescript-eslint/no-misused-promises - filteredApiMap.forEach(async ({ api: apiPromise, chain }) => { - const parentApi = await apiPromise.isReady; - const useAddresses = _isChainEvmCompatible(chainInfoMap[chain]) ? evmAddresses : substrateAddresses; - - if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { - const unsub = await getAmplitudeStakingOnChain(parentApi, useAddresses, chainInfoMap, chain, stakingCallback, nominatorStateCallback); - - unsubList.push(unsub); - } else if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { - const unsub = await getAstarStakingOnChain(parentApi, useAddresses, chainInfoMap, chain, stakingCallback, nominatorStateCallback); - - unsubList.push(unsub); - } else if (_STAKING_CHAIN_GROUP.para.includes(chain)) { - const unsub = await getParaStakingOnChain(parentApi, useAddresses, chainInfoMap, chain, stakingCallback, nominatorStateCallback); - - unsubList.push(unsub); - } else if (_STAKING_CHAIN_GROUP.relay.includes(chain)) { - const unsub = await getRelayStakingOnChain(parentApi, useAddresses, chainInfoMap, chain, stakingCallback, nominatorStateCallback); - - unsubList.push(unsub); - } - - if (_STAKING_CHAIN_GROUP.nominationPool.includes(chain)) { - const unsub = await getRelayPoolingOnChain(parentApi, useAddresses, chainInfoMap, chain, stakingCallback, nominatorStateCallback); - - unsubList.push(unsub); - } - }); - - return () => { - unsubList.forEach((unsub) => { - unsub && unsub(); - }); - }; -} - -export async function getNominationStakingRewardData (addresses: string[], chainInfoMap: Record, callback: (rewardItem: StakingRewardItem) => void) { - // might retrieve from other sources - await getAllSubsquidStaking(addresses, chainInfoMap, callback); -} - -export async function getPoolingStakingRewardData (addresses: string[], networkMap: Record, dotSamaApiMap: Record, callback: (rs: StakingRewardItem) => void) { - const activeNetworks: string[] = []; - - Object.entries(networkMap).forEach(([key, chainInfo]) => { - if (_isChainSupportSubstrateStaking(chainInfo) && _isSubstrateRelayChain(chainInfo)) { - activeNetworks.push(key); - } - }); - - if (activeNetworks.length === 0) { - return; - } - - await getNominationPoolReward(addresses, networkMap, dotSamaApiMap, callback); -} diff --git a/packages/extension-base/src/koni/api/staking/paraChain.ts b/packages/extension-base/src/koni/api/staking/paraChain.ts deleted file mode 100644 index c69929edca5..00000000000 --- a/packages/extension-base/src/koni/api/staking/paraChain.ts +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { APIItemState, NominatorMetadata, StakingItem, StakingRewardItem, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { subscribeAmplitudeNominatorMetadata } from '@subwallet/extension-base/koni/api/staking/bonding/amplitude'; -import { subscribeAstarNominatorMetadata } from '@subwallet/extension-base/koni/api/staking/bonding/astar'; -import { subscribeParaChainNominatorMetadata } from '@subwallet/extension-base/koni/api/staking/bonding/paraChain'; -import { KrestDelegateState, PalletDappsStakingAccountLedger, PalletParachainStakingDelegator, ParachainStakingStakeOption } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _getChainNativeTokenBasicInfo } from '@subwallet/extension-base/services/chain-service/utils'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { EarningStatus } from '@subwallet/extension-base/types'; -import { reformatAddress } from '@subwallet/extension-base/utils'; - -import { Codec } from '@polkadot/types/types'; -import { BN, BN_ZERO } from '@polkadot/util'; -import { isEthereumAddress } from '@polkadot/util-crypto'; - -function getSingleStakingAmplitude (substrateApi: _SubstrateApi, address: string, chainInfoMap: Record, chain: string, stakingCallback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (nominatorMetadata: NominatorMetadata) => void) { - return substrateApi.api.queryMulti([ - [substrateApi.api.query.parachainStaking.delegatorState, address], - [substrateApi.api.query.parachainStaking.unstaking, address] - ], async ([_delegatorState, _unstaking]) => { - let delegatorState: ParachainStakingStakeOption[] = []; - - if (_STAKING_CHAIN_GROUP.krest_network.includes(chain)) { - const krestDelegatorState = _delegatorState.toPrimitive() as unknown as KrestDelegateState; - - const delegates = krestDelegatorState?.delegations as unknown as ParachainStakingStakeOption[]; - - if (delegates) { - delegatorState = delegatorState.concat(delegates); - } - } else { - const delegate = _delegatorState.toPrimitive() as unknown as ParachainStakingStakeOption; - - if (delegate) { - delegatorState.push(delegate); - } - } - - const unstakingInfo = _unstaking.toPrimitive() as Record; - const { symbol } = _getChainNativeTokenBasicInfo(chainInfoMap[chain]); - const owner = reformatAddress(address, 42); - - if (!delegatorState && !unstakingInfo) { - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: '0', - activeBalance: '0', - unlockingBalance: '0', - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - nominatorStateCallback({ - chain, - type: StakingType.NOMINATED, - address: owner, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata); - } else { - let activeBalance = BN_ZERO; - let unstakingBalance = BN_ZERO; - - for (const delegate of delegatorState) { - const amount = new BN(delegate.amount?.toString()); - - activeBalance = activeBalance.add(amount); - } - - if (unstakingInfo) { - Object.values(unstakingInfo).forEach((unstakingAmount) => { - const bnUnstakingAmount = new BN(unstakingAmount.toString()); - - unstakingBalance = unstakingBalance.add(bnUnstakingAmount); - }); - } - - const totalBalance = activeBalance.add(unstakingBalance); - - const stakingItem = { - name: chainInfoMap[chain].name, - chain: chain, - balance: totalBalance.toString(), - activeBalance: activeBalance.toString(), - unlockingBalance: unstakingBalance.toString(), - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem; - - stakingCallback(chain, stakingItem); - - const nominatorMetadata = await subscribeAmplitudeNominatorMetadata(chainInfoMap[chain], owner, substrateApi, delegatorState, unstakingInfo); - - nominatorStateCallback(nominatorMetadata); - } - }); -} - -function getMultiStakingAmplitude (substrateApi: _SubstrateApi, useAddresses: string[], chainInfoMap: Record, chain: string, stakingCallback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (nominatorMetadata: NominatorMetadata) => void) { - return substrateApi.api.query.parachainStaking.delegatorState.multi(useAddresses, async (ledgers: Codec[]) => { - if (ledgers) { - const { symbol } = _getChainNativeTokenBasicInfo(chainInfoMap[chain]); - const _unstakingStates = await substrateApi.api.query.parachainStaking.unstaking.multi(useAddresses); - - await Promise.all(ledgers.map(async (_delegatorState, i) => { - const owner = reformatAddress(useAddresses[i], 42); - let delegatorState: ParachainStakingStakeOption[] = []; - - if (_STAKING_CHAIN_GROUP.krest_network.includes(chain)) { - const krestDelegatorState = _delegatorState.toPrimitive() as unknown as KrestDelegateState; - - const delegates = krestDelegatorState?.delegations as unknown as ParachainStakingStakeOption[]; - - if (delegates) { - delegatorState = delegatorState.concat(delegates); - } - } else { - const delegate = _delegatorState.toPrimitive() as unknown as ParachainStakingStakeOption; - - if (delegate) { - delegatorState.push(delegate); - } - } - - const unstakingInfo = _unstakingStates[i].toPrimitive() as unknown as Record; - - if (!delegatorState && !unstakingInfo) { - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: '0', - activeBalance: '0', - unlockingBalance: '0', - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - nominatorStateCallback({ - chain, - type: StakingType.NOMINATED, - address: owner, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata); - } else { - let activeBalance = BN_ZERO; - let unstakingBalance = BN_ZERO; - - for (const delegate of delegatorState) { - const amount = new BN(delegate.amount?.toString()); - - activeBalance = activeBalance.add(amount); - } - - if (unstakingInfo) { - Object.values(unstakingInfo).forEach((unstakingAmount) => { - const bnUnstakingAmount = new BN(unstakingAmount.toString()); - - unstakingBalance = unstakingBalance.add(bnUnstakingAmount); - }); - } - - const totalBalance = activeBalance.add(unstakingBalance); - - const stakingItem = { - name: chainInfoMap[chain].name, - chain: chain, - balance: totalBalance.toString(), - activeBalance: activeBalance.toString(), - unlockingBalance: unstakingBalance.toString(), - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem; - - stakingCallback(chain, stakingItem); - - const nominatorMetadata = await subscribeAmplitudeNominatorMetadata(chainInfoMap[chain], owner, substrateApi, delegatorState, unstakingInfo); - - nominatorStateCallback(nominatorMetadata); - } - })); - } - }); -} - -export function getAmplitudeStakingOnChain (parentApi: _SubstrateApi, useAddresses: string[], networks: Record, chain: string, callback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (nominatorMetadata: NominatorMetadata) => void) { - if (useAddresses.length === 1) { - return getSingleStakingAmplitude(parentApi, useAddresses[0], networks, chain, callback, nominatorStateCallback); - } - - return getMultiStakingAmplitude(parentApi, useAddresses, networks, chain, callback, nominatorStateCallback); -} - -export async function getAmplitudeUnclaimedStakingReward (substrateApiMap: Record, addresses: string[], networks: Record, chains: string[], callBack: (rs: StakingRewardItem) => void): Promise { - if (chains.length === 0) { - return []; - } - - const useAddresses: string[] = []; - - addresses.forEach((address) => { - if (!isEthereumAddress(address)) { - useAddresses.push(address); - } - }); - - const unclaimedRewardList: StakingRewardItem[] = []; - - await Promise.all(chains.map(async (chain) => { - if (_STAKING_CHAIN_GROUP.amplitude.includes(chain) && !_STAKING_CHAIN_GROUP.krest_network.includes(chain)) { - const networkInfo = networks[chain]; - const apiProps = await substrateApiMap[chain].isReady; - - await Promise.all(useAddresses.map(async (address) => { - const _unclaimedReward = await apiProps.api.query.parachainStaking.rewards(address); - - callBack({ - chain, - name: networkInfo.name, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: reformatAddress(address, 42), - unclaimedReward: _unclaimedReward.toString() - } as StakingRewardItem); - })); - } - })); - - return unclaimedRewardList; -} - -export function getParaStakingOnChain (substrateApi: _SubstrateApi, useAddresses: string[], chainInfoMap: Record, chain: string, stakingCallback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (nominatorMetadata: NominatorMetadata) => void) { - const { symbol } = _getChainNativeTokenBasicInfo(chainInfoMap[chain]); - - return substrateApi.api.query.parachainStaking.delegatorState.multi(useAddresses, async (ledgers: Codec[]) => { - if (ledgers) { - await Promise.all(ledgers.map(async (_delegatorState, i) => { - const delegatorState = _delegatorState.toPrimitive() as unknown as PalletParachainStakingDelegator; - const owner = reformatAddress(useAddresses[i], 42); - - if (delegatorState) { - const _totalBalance = delegatorState.total; - // let _unlockingBalance = delegatorState.lessTotal ? delegatorState.lessTotal : delegatorState.requests.lessTotal; - const _unlockingBalance = delegatorState.lessTotal; - - const totalBalance = new BN(_totalBalance.toString()); - const unlockingBalance = new BN(_unlockingBalance.toString()); - const activeBalance = totalBalance.sub(unlockingBalance); - - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: totalBalance.toString(), - activeBalance: activeBalance.toString(), - unlockingBalance: unlockingBalance.toString(), - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - const nominatorMetadata = await subscribeParaChainNominatorMetadata(chainInfoMap[chain], owner, substrateApi, delegatorState); - - nominatorStateCallback(nominatorMetadata); - } else { - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: '0', - activeBalance: '0', - unlockingBalance: '0', - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - nominatorStateCallback({ - chain, - type: StakingType.NOMINATED, - address: owner, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata); - } - })); - } - }); -} - -export function getAstarStakingOnChain (substrateApi: _SubstrateApi, useAddresses: string[], chainInfoMap: Record, chain: string, stakingCallback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (nominatorMetadata: NominatorMetadata) => void) { - const { symbol } = _getChainNativeTokenBasicInfo(chainInfoMap[chain]); - - return substrateApi.api.query.dappsStaking.ledger.multi(useAddresses, async (ledgers: Codec[]) => { - if (ledgers) { - await Promise.all(ledgers.map(async (_ledger, i) => { - let bnUnlockingBalance = BN_ZERO; - const owner = reformatAddress(useAddresses[i], 42); - - const ledger = _ledger.toPrimitive() as unknown as PalletDappsStakingAccountLedger; - - if (ledger && ledger.locked > 0) { - const unlockingChunks = ledger.unbondingInfo.unlockingChunks; - const _totalStake = ledger.locked; - const bnTotalStake = new BN(_totalStake.toString()); - - for (const chunk of unlockingChunks) { - const bnChunk = new BN(chunk.amount.toString()); - - bnUnlockingBalance = bnUnlockingBalance.add(bnChunk); - } - - const bnActiveStake = bnTotalStake.sub(bnUnlockingBalance); - - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: bnTotalStake.toString(), - activeBalance: bnActiveStake.toString(), - unlockingBalance: bnUnlockingBalance.toString(), - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - const nominatorMetadata = await subscribeAstarNominatorMetadata(chainInfoMap[chain], owner, substrateApi, ledger); - - nominatorStateCallback(nominatorMetadata); - } else { - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain, - balance: '0', - activeBalance: '0', - unlockingBalance: '0', - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - nominatorStateCallback({ - chain, - type: StakingType.NOMINATED, - address: owner, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata); - } - })); - } - }); -} diff --git a/packages/extension-base/src/koni/api/staking/relayChain.ts b/packages/extension-base/src/koni/api/staking/relayChain.ts deleted file mode 100644 index 705616971b3..00000000000 --- a/packages/extension-base/src/koni/api/staking/relayChain.ts +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { APIItemState, NominatorMetadata, StakingItem, StakingRewardItem, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { PalletNominationPoolsPoolMember } from '@subwallet/extension-base/core/substrate/types'; -import { PalletStakingStakingLedger, subscribeRelayChainNominatorMetadata, subscribeRelayChainPoolMemberMetadata } from '@subwallet/extension-base/koni/api/staking/bonding/relayChain'; -import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _getChainNativeTokenBasicInfo } from '@subwallet/extension-base/services/chain-service/utils'; -import { EarningStatus } from '@subwallet/extension-base/types'; -import { reformatAddress } from '@subwallet/extension-base/utils'; - -import { Codec } from '@polkadot/types/types'; -import { BN } from '@polkadot/util'; -import { isEthereumAddress } from '@polkadot/util-crypto'; - -export function getRelayStakingOnChain (substrateApi: _SubstrateApi, useAddresses: string[], chainInfoMap: Record, chain: string, stakingCallback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (rs: NominatorMetadata) => void) { - const { symbol } = _getChainNativeTokenBasicInfo(chainInfoMap[chain]); - - return substrateApi.api.query.staking?.ledger.multi(useAddresses, async (ledgers: Codec[]) => { - if (ledgers) { - await Promise.all(ledgers.map(async (_ledger: Codec, i) => { - const owner = reformatAddress(useAddresses[i], 42); - const ledger = _ledger.toPrimitive() as unknown as PalletStakingStakingLedger; - - if (ledger) { - const _totalBalance = ledger.total.toString(); - const _activeBalance = ledger.active.toString(); - const bnUnlockingBalance = new BN(_totalBalance).sub(new BN(_activeBalance)); - - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: _totalBalance, - activeBalance: _activeBalance, - unlockingBalance: bnUnlockingBalance.toString(), - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - const nominatorMetadata = await subscribeRelayChainNominatorMetadata(chainInfoMap[chain], owner, substrateApi, ledger); - - nominatorStateCallback(nominatorMetadata); - } else { - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: '0', - activeBalance: '0', - unlockingBalance: '0', - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: owner - } as StakingItem); - - nominatorStateCallback({ - chain, - type: StakingType.NOMINATED, - status: EarningStatus.NOT_STAKING, - address: owner, - activeStake: '0', - nominations: [], - unstakings: [] - } as NominatorMetadata); - } - })); - } - }); -} - -export function getRelayPoolingOnChain (substrateApi: _SubstrateApi, useAddresses: string[], chainInfoMap: Record, chain: string, stakingCallback: (networkKey: string, rs: StakingItem) => void, nominatorStateCallback: (rs: NominatorMetadata) => void) { - const { symbol } = _getChainNativeTokenBasicInfo(chainInfoMap[chain]); - - return substrateApi.api.query?.nominationPools?.poolMembers.multi(useAddresses, async (ledgers: Codec[]) => { - if (ledgers) { - await Promise.all(ledgers.map(async (_poolMemberInfo, i) => { - const poolMemberInfo = _poolMemberInfo.toPrimitive() as unknown as PalletNominationPoolsPoolMember; - const owner = reformatAddress(useAddresses[i], 42); - - if (poolMemberInfo) { - const bondedBalance = poolMemberInfo.points; - const unbondedBalance = poolMemberInfo.unbondingEras; - - let unlockingBalance = new BN(0); - const bnBondedBalance = new BN(bondedBalance.toString()); - - Object.entries(unbondedBalance).forEach(([, value]) => { - const bnUnbondedBalance = new BN(value.toString()); - - unlockingBalance = unlockingBalance.add(bnUnbondedBalance); - }); - - const totalBalance = bnBondedBalance.add(unlockingBalance); - - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: totalBalance.toString(), - activeBalance: bnBondedBalance.toString(), - unlockingBalance: unlockingBalance.toString(), - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.POOLED, - address: owner - } as StakingItem); - - const nominatorMetadata = await subscribeRelayChainPoolMemberMetadata(chainInfoMap[chain], owner, substrateApi, poolMemberInfo); - - nominatorStateCallback(nominatorMetadata); - } else { - stakingCallback(chain, { - name: chainInfoMap[chain].name, - chain: chain, - balance: '0', - activeBalance: '0', - unlockingBalance: '0', - nativeToken: symbol, - unit: symbol, - state: APIItemState.READY, - type: StakingType.POOLED, - address: owner - } as StakingItem); - - nominatorStateCallback({ - chain, - type: StakingType.POOLED, - address: owner, - status: EarningStatus.NOT_STAKING, - activeStake: '0', - nominations: [], // can only join 1 pool at a time - unstakings: [] - } as NominatorMetadata); - } - })); - } - }); -} - -export async function getNominationPoolReward (addresses: string[], chainInfoMap: Record, substrateApiMap: Record, callBack: (rs: StakingRewardItem) => void) { - const targetNetworks: string[] = []; - const validAddresses: string[] = []; - - Object.keys(chainInfoMap).forEach((key) => { - targetNetworks.push(key); - }); - - addresses.forEach((address) => { - if (!isEthereumAddress(address)) { - validAddresses.push(address); - } - }); - - try { - await Promise.all(targetNetworks.map(async (networkKey) => { - const substrateApi = await substrateApiMap[networkKey].isReady; - - if (substrateApi.api.call.nominationPoolsApi) { - await Promise.all(validAddresses.map(async (address) => { - const _unclaimedReward = await substrateApi.api.call?.nominationPoolsApi?.pendingRewards(address); - - if (_unclaimedReward) { - callBack({ - address: address, - chain: networkKey, - unclaimedReward: _unclaimedReward.toString(), - name: chainInfoMap[networkKey].name, - state: APIItemState.READY, - type: StakingType.POOLED - }); - } - })); - } - })); - } catch (e) { - console.debug(e); - } -} diff --git a/packages/extension-base/src/koni/api/staking/subsquidStaking.ts b/packages/extension-base/src/koni/api/staking/subsquidStaking.ts deleted file mode 100644 index 8936d3ae159..00000000000 --- a/packages/extension-base/src/koni/api/staking/subsquidStaking.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { APIItemState, StakingRewardItem, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { INDEXER_SUPPORTED_STAKING_CHAINS, SUBSQUID_ENDPOINTS } from '@subwallet/extension-base/koni/api/staking/config'; -import { _getChainSubstrateAddressPrefix, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; -import { fetchJson, reformatAddress } from '@subwallet/extension-base/utils'; - -import { isEthereumAddress } from '@polkadot/util-crypto'; - -interface RewardResponseItem { - amount: string, - blockNumber: string -} - -interface StakingResponseItem { - totalReward: string, - totalSlash: string, - activeBond: string, - rewards: RewardResponseItem[] -} - -const getSubsquidQuery = (account: string, chain: string) => { - if (chain === 'moonbeam' || chain === 'moonriver' || chain === 'astar') { - return ` - query MyQuery { - stakerById(id: "${account}") { - totalReward - activeBond - rewards(limit: 1, orderBy: blockNumber_DESC) { - amount - } - } - }`; - } - - return ` - query MyQuery { - stakerById(id: "${account}") { - totalReward - totalSlash - activeBond - rewards(limit: 1, orderBy: blockNumber_DESC) { - amount - } - } - }`; -}; - -const getSubsquidStaking = async (accounts: string[], chain: string, chainInfoMap: Record, callback: (rewardItem: StakingRewardItem) => void) => { - try { - await Promise.all(accounts.map(async (account) => { - if ((_isChainEvmCompatible(chainInfoMap[chain]) && isEthereumAddress(account)) || (!_isChainEvmCompatible(chainInfoMap[chain]) && !isEthereumAddress(account))) { - const parsedAccount = reformatAddress(account, _getChainSubstrateAddressPrefix(chainInfoMap[chain])); - const stakingRewardItem: StakingRewardItem = { - chain: chain, - name: chainInfoMap[chain].name, - state: APIItemState.READY, - type: StakingType.NOMINATED, - address: reformatAddress(account, 42) - }; - - try { - const respData = await fetchJson>( - SUBSQUID_ENDPOINTS[chain], - { method: 'post', data: { query: getSubsquidQuery(parsedAccount, chain) } } - ); - - const rewardItem = respData.stakerById as StakingResponseItem; - - if (rewardItem) { - const latestReward = rewardItem.rewards[0]; - - if (rewardItem.totalReward) { - stakingRewardItem.totalReward = rewardItem.totalReward; - } - - if (rewardItem.totalSlash) { - stakingRewardItem.totalSlash = rewardItem.totalSlash; - } - - if (latestReward && latestReward.amount) { - stakingRewardItem.latestReward = latestReward.amount; - } - } - } catch (e) { - console.error(e); - } - - if (stakingRewardItem.totalReward && parseFloat(stakingRewardItem.totalReward) > 0) { - callback(stakingRewardItem); - } - } - })); - } catch (e) { - console.debug(e); - } -}; - -export const getAllSubsquidStaking = async (accounts: string[], chainInfoMap: Record, callback: (rewardItem: StakingRewardItem) => void) => { - const filteredNetworks: string[] = []; - - Object.values(chainInfoMap).forEach((network) => { - if (INDEXER_SUPPORTED_STAKING_CHAINS.includes(network.slug)) { - filteredNetworks.push(network.slug); - } - }); - - try { - await Promise.all(filteredNetworks.map(async (network) => { - await getSubsquidStaking(accounts, network, chainInfoMap, callback); - })); - } catch (e) { - console.debug(e); - } -}; diff --git a/packages/extension-base/src/koni/api/staking/utils.ts b/packages/extension-base/src/koni/api/staking/utils.ts deleted file mode 100644 index 7512d27fde0..00000000000 --- a/packages/extension-base/src/koni/api/staking/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { _ChainInfo } from '@subwallet/chain-list/types'; -import { _getChainNativeTokenBasicInfo } from '@subwallet/extension-base/services/chain-service/utils'; -import { toUnit } from '@subwallet/extension-base/utils'; - -export function parseStakingBalance (balance: number, chain: string, network: Record): number { - const { decimals } = _getChainNativeTokenBasicInfo(network[chain]); - - return toUnit(balance, decimals); -} diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index cbb690e379c..1bf146776b7 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -7,7 +7,7 @@ import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { withErrorLog } from '@subwallet/extension-base/background/handlers/helpers'; import { createSubscription } from '@subwallet/extension-base/background/handlers/subscriptions'; -import { AccountExternalError, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CronReloadRequest, CrowdloanJson, ExternalRequestPromiseStatus, ExtrinsicType, FeeData, HistoryTokenPriceJSON, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangePriceCurrency, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConfirmationCompleteCardano, RequestConfirmationCompleteTon, RequestConnectWalletConnect, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetHistoryTokenPriceData, RequestGetTransaction, RequestKeyringExportMnemonic, RequestMigratePassword, RequestMigrateSoloAccount, RequestMigrateUnifiedAndFetchEligibleSoloAccounts, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestPingSession, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveAppConfig, RequestSaveBrowserConfig, RequestSaveMigrationAcknowledgedStatus, RequestSaveOSConfig, RequestSaveRecentAccount, RequestSaveUnifiedAccountMigrationInProgress, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestSwitchCurrentNetworkAuthorization, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseChangeMasterPassword, ResponseFindRawMetadata, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseMigrateSoloAccount, ResponseMigrateUnifiedAndFetchEligibleSoloAccounts, ResponseNftImport, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSubscribeCurrentTokenPrice, ResponseSubscribeHistory, ResponseUnlockKeyring, ShowCampaignPopupRequest, StakingJson, StakingRewardJson, StakingType, ThemeNames, TokenPriorityDetails, TransactionHistoryItem, TransactionResponse, UiSettings, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountExternalError, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CronReloadRequest, CrowdloanJson, ExternalRequestPromiseStatus, ExtrinsicType, FeeData, HistoryTokenPriceJSON, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangePriceCurrency, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConfirmationCompleteCardano, RequestConfirmationCompleteTon, RequestConnectWalletConnect, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetHistoryTokenPriceData, RequestGetTransaction, RequestKeyringExportMnemonic, RequestMigratePassword, RequestMigrateSoloAccount, RequestMigrateUnifiedAndFetchEligibleSoloAccounts, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestPingSession, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveAppConfig, RequestSaveBrowserConfig, RequestSaveMigrationAcknowledgedStatus, RequestSaveOSConfig, RequestSaveRecentAccount, RequestSaveUnifiedAccountMigrationInProgress, RequestSettingsType, RequestSigningApprovePasswordV2, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestSwitchCurrentNetworkAuthorization, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseChangeMasterPassword, ResponseFindRawMetadata, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseMigrateSoloAccount, ResponseMigrateUnifiedAndFetchEligibleSoloAccounts, ResponseNftImport, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSubscribeCurrentTokenPrice, ResponseSubscribeHistory, ResponseUnlockKeyring, ShowCampaignPopupRequest, StakingJson, StakingRewardJson, ThemeNames, TokenPriorityDetails, TransactionHistoryItem, UiSettings, ValidateNetworkRequest, ValidateNetworkResponse } from '@subwallet/extension-base/background/KoniTypes'; import { AccountAuthType, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountExport, RequestAuthorizeCancel, RequestAuthorizeReject, RequestCurrentAccountAddress, RequestMetadataApprove, RequestMetadataReject, RequestSigningApproveSignature, RequestSigningCancel, RequestTypes, ResponseAccountExport, ResponseAuthorizeList, ResponseType, SigningRequest, WindowOpenParams } from '@subwallet/extension-base/background/types'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { _SUPPORT_TOKEN_PAY_FEE_GROUP, ALL_ACCOUNT_KEY, BTC_DUST_AMOUNT, LATEST_SESSION } from '@subwallet/extension-base/constants'; @@ -23,9 +23,6 @@ import { resolveAzeroAddressToDomain, resolveAzeroDomainToAddress } from '@subwa import { parseSubstrateTransaction } from '@subwallet/extension-base/koni/api/dotsama/parseTransaction'; import { UNSUPPORTED_TRANSFER_EVM_CHAIN_NAME } from '@subwallet/extension-base/koni/api/nft/config'; import { getNftTransferExtrinsic, isRecipientSelf } from '@subwallet/extension-base/koni/api/nft/transfer'; -import { getBondingExtrinsic, getCancelWithdrawalExtrinsic, getClaimRewardExtrinsic, getNominationPoolsInfo, getUnbondingExtrinsic, getValidatorsInfo, validateBondingCondition, validateUnbondingCondition } from '@subwallet/extension-base/koni/api/staking/bonding'; -import { getTuringCancelCompoundingExtrinsic, getTuringCompoundExtrinsic } from '@subwallet/extension-base/koni/api/staking/bonding/paraChain'; -import { getPoolingBondingExtrinsic, getPoolingUnbondingExtrinsic, validatePoolBondingCondition, validateRelayUnbondingCondition } from '@subwallet/extension-base/koni/api/staking/bonding/relayChain'; import { YIELD_EXTRINSIC_TYPES } from '@subwallet/extension-base/koni/api/yield/helper/utils'; import KoniState from '@subwallet/extension-base/koni/background/handlers/State'; import { RequestOptimalTransferProcess } from '@subwallet/extension-base/services/balance-service/helpers/process'; @@ -58,7 +55,7 @@ import { isProposalExpired, isSupportWalletConnectChain, isSupportWalletConnectN import { ResultApproveWalletConnectSession, WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types'; import { SWStorage } from '@subwallet/extension-base/storage'; import { AccountsStore } from '@subwallet/extension-base/stores'; -import { AccountChainType, AccountJson, AccountProxyMap, AccountSignMode, AccountsWithCurrentAddress, BalanceJson, BasicTxErrorType, BasicTxWarningCode, BitcoinFeeDetail, BitcoinFeeInfo, BitcoinFeeRate, BriefProcessStep, BuyServiceInfo, BuyTokenInfo, CommonOptimalTransferPath, CommonStepFeeInfo, CommonStepType, EarningProcessType, EarningRewardJson, EvmFeeInfo, FeeChainType, FeeCustom, FeeDetail, FeeInfo, FeeOption, HandleYieldStepData, NominationPoolInfo, OptimalYieldPathParams, ProcessStep, ProcessTransactionData, ProcessType, RequestAccountBatchExportV2, RequestAccountCreateSuriV2, RequestAccountNameValidate, RequestBatchJsonGetAccountInfo, RequestBatchRestoreV2, RequestBounceableValidate, RequestChangeAllowOneSign, RequestChangeTonWalletContractVersion, RequestCheckPublicAndSecretKey, RequestClaimBridge, RequestCrossChainTransfer, RequestDeriveCreateMultiple, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestEarlyValidateYield, RequestEarningImpact, RequestExportAccountProxyMnemonic, RequestGetAllTonWalletContractVersion, RequestGetAmountForPair, RequestGetDeriveAccounts, RequestGetDeriveSuggestion, RequestGetTokensCanPayFee, RequestGetYieldPoolTargets, RequestInputAccountSubscribe, RequestJsonGetAccountInfo, RequestJsonRestoreV2, RequestMetadataHash, RequestMnemonicCreateV2, RequestMnemonicValidateV2, RequestPrivateKeyValidateV2, RequestShortenMetadata, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitProcessTransaction, RequestSubscribeProcessById, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseAccountBatchExportV2, ResponseAccountCreateSuriV2, ResponseAccountNameValidate, ResponseBatchJsonGetAccountInfo, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseExportAccountProxyMnemonic, ResponseGetAllTonWalletContractVersion, ResponseGetDeriveAccounts, ResponseGetDeriveSuggestion, ResponseGetYieldPoolTargets, ResponseInputAccountSubscribe, ResponseJsonGetAccountInfo, ResponseMetadataHash, ResponseMnemonicCreateV2, ResponseMnemonicValidateV2, ResponsePrivateKeyValidateV2, ResponseShortenMetadata, ResponseSubscribeProcessAlive, ResponseSubscribeProcessById, StakingTxErrorType, StepStatus, StorageDataInterface, SubmitChangeValidatorStaking, SummaryEarningProcessData, SwapBaseTxData, SwapFeeType, SwapRequestV2, TokenSpendingApprovalParams, ValidateYieldProcessParams, YieldPoolType, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; +import { AccountChainType, AccountJson, AccountProxyMap, AccountSignMode, AccountsWithCurrentAddress, BalanceJson, BasicTxErrorType, BasicTxWarningCode, BitcoinFeeDetail, BitcoinFeeInfo, BitcoinFeeRate, BriefProcessStep, BuyServiceInfo, BuyTokenInfo, CommonOptimalTransferPath, CommonStepFeeInfo, CommonStepType, EarningProcessType, EarningRewardJson, EvmFeeInfo, FeeChainType, FeeCustom, FeeDetail, FeeInfo, FeeOption, HandleYieldStepData, OptimalYieldPathParams, ProcessStep, ProcessTransactionData, ProcessType, RequestAccountBatchExportV2, RequestAccountCreateSuriV2, RequestAccountNameValidate, RequestBatchJsonGetAccountInfo, RequestBatchRestoreV2, RequestBounceableValidate, RequestChangeAllowOneSign, RequestChangeTonWalletContractVersion, RequestCheckPublicAndSecretKey, RequestClaimBridge, RequestCrossChainTransfer, RequestDeriveCreateMultiple, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestEarlyValidateYield, RequestEarningImpact, RequestExportAccountProxyMnemonic, RequestGetAllTonWalletContractVersion, RequestGetAmountForPair, RequestGetDeriveAccounts, RequestGetDeriveSuggestion, RequestGetTokensCanPayFee, RequestGetYieldPoolTargets, RequestInputAccountSubscribe, RequestJsonGetAccountInfo, RequestJsonRestoreV2, RequestMetadataHash, RequestMnemonicCreateV2, RequestMnemonicValidateV2, RequestPrivateKeyValidateV2, RequestShortenMetadata, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitProcessTransaction, RequestSubscribeProcessById, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseAccountBatchExportV2, ResponseAccountCreateSuriV2, ResponseAccountNameValidate, ResponseBatchJsonGetAccountInfo, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseExportAccountProxyMnemonic, ResponseGetAllTonWalletContractVersion, ResponseGetDeriveAccounts, ResponseGetDeriveSuggestion, ResponseGetYieldPoolTargets, ResponseInputAccountSubscribe, ResponseJsonGetAccountInfo, ResponseMetadataHash, ResponseMnemonicCreateV2, ResponseMnemonicValidateV2, ResponsePrivateKeyValidateV2, ResponseShortenMetadata, ResponseSubscribeProcessAlive, ResponseSubscribeProcessById, StepStatus, StorageDataInterface, SubmitChangeValidatorStaking, SummaryEarningProcessData, SwapBaseTxData, SwapFeeType, SwapRequestV2, TokenSpendingApprovalParams, ValidateYieldProcessParams, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { RequestAccountProxyEdit, RequestAccountProxyForget } from '@subwallet/extension-base/types/account/action/edit'; import { RequestSubmitSignPsbtTransfer, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation } from '@subwallet/extension-base/types/balance/transfer'; import { GetNotificationParams, RequestIsClaimedPolygonBridge, RequestSwitchStatusParams } from '@subwallet/extension-base/types/notification'; @@ -2925,200 +2922,6 @@ export default class KoniExtension { return this.#koniState.getNominatorMetadata(); } - private async getBondingOptions ({ chain, type }: BondingOptionParams): Promise { - const apiProps = this.#koniState.getSubstrateApi(chain); - const chainInfo = this.#koniState.getChainInfo(chain); - const chainStakingMetadata = await this.#koniState.getStakingMetadataByChain(chain, type); - - if (!chainStakingMetadata) { - return; - } - - const { decimals } = _getChainNativeTokenBasicInfo(chainInfo); - - return await getValidatorsInfo(chain, apiProps, decimals, chainStakingMetadata); - } - - private async getNominationPoolOptions (chain: string): Promise { - const substrateApi = this.#koniState.getSubstrateApi(chain); - - return await getNominationPoolsInfo(chain, substrateApi); - } - - private async submitBonding (inputData: RequestBondingSubmit): Promise { - const { address, amount, chain, nominatorMetadata, selectedValidators } = inputData; - const chainInfo = this.#koniState.getChainInfo(chain); - const chainStakingMetadata = await this.#koniState.getStakingMetadataByChain(chain, StakingType.NOMINATED); - - if (!chainStakingMetadata) { - const errMessage = t('bg.koni.handler.Extension.unableToFetchStakingDataReEnable', { replace: { chainName: chainInfo.name } }); - - return this.#koniState.transactionService - .generateBeforeHandleResponseErrors([new TransactionError(StakingTxErrorType.CAN_NOT_GET_METADATA, errMessage)]); - } - - const bondingValidation = validateBondingCondition(chainInfo, amount, selectedValidators, address, chainStakingMetadata, nominatorMetadata); - - if (!amount || !selectedValidators || bondingValidation.length > 0) { - return this.#koniState.transactionService - .generateBeforeHandleResponseErrors(bondingValidation); - } - - const substrateApi = this.#koniState.getSubstrateApi(chain); - const extrinsic = await getBondingExtrinsic(chainInfo, amount, selectedValidators, substrateApi, address, nominatorMetadata); - - return await this.#koniState.transactionService.handleTransaction({ - address, - chain: chain, - chainType: ChainType.SUBSTRATE, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_BOND, - transaction: extrinsic, - url: EXTENSION_REQUEST_URL, - transferNativeAmount: amount - }); - } - - private async submitUnbonding (inputData: RequestUnbondingSubmit): Promise { - const { amount, chain, nominatorMetadata, validatorAddress } = inputData; - - const chainStakingMetadata = await this.#koniState.getStakingMetadataByChain(chain, StakingType.NOMINATED); - - if (!chainStakingMetadata || !nominatorMetadata) { - return this.#koniState.transactionService.generateBeforeHandleResponseErrors([new TransactionError(BasicTxErrorType.INTERNAL_ERROR)]); - } - - const unbondingValidation = validateUnbondingCondition(nominatorMetadata, amount, chain, chainStakingMetadata, validatorAddress); - - if (!amount || unbondingValidation.length > 0) { - return this.#koniState.transactionService.generateBeforeHandleResponseErrors(unbondingValidation); - } - - const substrateApi = this.#koniState.getSubstrateApi(chain); - const extrinsic = await getUnbondingExtrinsic(nominatorMetadata, amount, chain, substrateApi, validatorAddress); - - return await this.#koniState.transactionService.handleTransaction({ - address: nominatorMetadata.address, - chain: chain, - transaction: extrinsic, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_UNBOND, - chainType: ChainType.SUBSTRATE - }); - } - - private async submitStakeClaimReward (inputData: RequestStakeClaimReward): Promise { - const { address, bondReward, slug } = inputData; - const poolHandler = this.#koniState.earningService.getPoolHandler(slug); - - if (!address || !poolHandler) { - return this.#koniState.transactionService.generateBeforeHandleResponseErrors([new TransactionError(BasicTxErrorType.INVALID_PARAMS)]); - } - - const chain = poolHandler.chain; - const stakingType: StakingType = poolHandler.type === YieldPoolType.NOMINATION_POOL ? StakingType.POOLED : StakingType.NOMINATED; - const substrateApi = this.#koniState.getSubstrateApi(chain); - const extrinsic = await getClaimRewardExtrinsic(substrateApi, chain, address, stakingType, bondReward); - - return await this.#koniState.transactionService.handleTransaction({ - address, - chain: chain, - transaction: extrinsic, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_CLAIM_REWARD, - chainType: ChainType.SUBSTRATE - }); - } - - private async submitCancelStakeWithdrawal (inputData: RequestStakeCancelWithdrawal): Promise { - const { address, selectedUnstaking, slug } = inputData; - const chain = this.#koniState.earningService.getPoolHandler(slug)?.chain; - - if (!chain || !selectedUnstaking) { - return this.#koniState.transactionService.generateBeforeHandleResponseErrors([new TransactionError(BasicTxErrorType.INVALID_PARAMS)]); - } - - const substrateApi = this.#koniState.getSubstrateApi(chain); - // @ts-ignore - const extrinsic = await getCancelWithdrawalExtrinsic(substrateApi, chain, selectedUnstaking); - - return await this.#koniState.transactionService.handleTransaction({ - address, - chain, - transaction: extrinsic, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_CANCEL_UNSTAKE, - chainType: ChainType.SUBSTRATE - }); - } - - private async submitPoolBonding (inputData: RequestStakePoolingBonding): Promise { - const { address, amount, chain, nominatorMetadata, selectedPool } = inputData; - - const chainInfo = this.#koniState.getChainInfo(chain); - const chainStakingMetadata = await this.#koniState.getStakingMetadataByChain(chain, StakingType.NOMINATED); - - if (!chainStakingMetadata) { - const errMessage = t('bg.koni.handler.Extension.unableToFetchStakingDataReEnable', { replace: { chainName: chainInfo.name } }); - - return this.#koniState.transactionService - .generateBeforeHandleResponseErrors([new TransactionError(StakingTxErrorType.CAN_NOT_GET_METADATA, errMessage)]); - } - - const bondingValidation = validatePoolBondingCondition(chainInfo, amount, selectedPool, address, chainStakingMetadata, nominatorMetadata); - - if (!amount || bondingValidation.length > 0) { - return this.#koniState.transactionService - .generateBeforeHandleResponseErrors(bondingValidation); - } - - const substrateApi = this.#koniState.getSubstrateApi(chain); - const extrinsic = await getPoolingBondingExtrinsic(substrateApi, amount, selectedPool.id, nominatorMetadata); - - return await this.#koniState.transactionService.handleTransaction({ - address, - chain, - transaction: extrinsic, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_JOIN_POOL, - chainType: ChainType.SUBSTRATE, - transferNativeAmount: amount - }); - } - - private async submitPoolingUnbonding (inputData: RequestStakePoolingUnbonding): Promise { - const { amount, chain, nominatorMetadata } = inputData; - - const chainStakingMetadata = await this.#koniState.getStakingMetadataByChain(chain, StakingType.NOMINATED); - - if (!chainStakingMetadata || !nominatorMetadata) { - const chainInfo = this.#koniState.getChainInfo(chain); - const errMessage = t('bg.koni.handler.Extension.unableToFetchStakingDataReEnable', { replace: { chainName: chainInfo?.name } }); - - return this.#koniState.transactionService - .generateBeforeHandleResponseErrors([new TransactionError(StakingTxErrorType.CAN_NOT_GET_METADATA, errMessage)]); - } - - const unbondingValidation = validateRelayUnbondingCondition(amount, chainStakingMetadata, nominatorMetadata); - - if (!amount || unbondingValidation.length > 0) { - return this.#koniState.transactionService - .generateBeforeHandleResponseErrors(unbondingValidation); - } - - const substrateApi = this.#koniState.getSubstrateApi(chain); - const extrinsic = await getPoolingUnbondingExtrinsic(substrateApi, amount, nominatorMetadata); - - return await this.#koniState.transactionService.handleTransaction({ - address: nominatorMetadata.address, - chain, - transaction: extrinsic, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_LEAVE_POOL, - chainType: ChainType.SUBSTRATE - }); - } - // EVM Transaction private async parseContractInput ({ chainId, contract, @@ -3128,52 +2931,6 @@ export default class KoniExtension { return await parseContractInput(data, contract, network); } - private async submitTuringStakeCompounding (inputData: RequestTuringStakeCompound) { - const { accountMinimum, address, bondedAmount, collatorAddress, networkKey } = inputData; - - if (!address) { - return this.#koniState.transactionService.generateBeforeHandleResponseErrors([new TransactionError(BasicTxErrorType.INVALID_PARAMS)]); - } - - const dotSamaApi = this.#koniState.getSubstrateApi(networkKey); - const chainInfo = this.#koniState.getChainInfo(networkKey); - const { decimals } = _getChainNativeTokenBasicInfo(chainInfo); - const parsedAccountMinimum = parseFloat(accountMinimum) * 10 ** decimals; - const extrinsic = await getTuringCompoundExtrinsic(dotSamaApi, address, collatorAddress, parsedAccountMinimum.toString(), bondedAmount); - - return await this.#koniState.transactionService.handleTransaction({ - address, - chain: networkKey, - transaction: extrinsic, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_COMPOUNDING, - chainType: ChainType.SUBSTRATE - }); - } - - private async submitTuringCancelStakeCompound (inputData: RequestTuringCancelStakeCompound) { - const { address, networkKey, taskId } = inputData; - const txState: TransactionResponse = {}; - - if (!address) { - txState.txError = true; - - return txState; - } - - const dotSamaApi = this.#koniState.getSubstrateApi(networkKey); - const extrinsic = await getTuringCancelCompoundingExtrinsic(dotSamaApi, taskId); - - return await this.#koniState.transactionService.handleTransaction({ - address, - chain: networkKey, - transaction: extrinsic, - data: inputData, - extrinsicType: ExtrinsicType.STAKING_CANCEL_COMPOUNDING, - chainType: ChainType.SUBSTRATE - }); - } - /// Keyring state // Subscribe keyring state @@ -5627,30 +5384,10 @@ export default class KoniExtension { return await this.completeConfirmationBitcoin(request as RequestConfirmationCompleteBitcoin); /// Stake - case 'pri(bonding.getBondingOptions)': - return await this.getBondingOptions(request as BondingOptionParams); - case 'pri(bonding.getNominationPoolOptions)': - return await this.getNominationPoolOptions(request as string); case 'pri(bonding.subscribeChainStakingMetadata)': return await this.subscribeChainStakingMetadata(id, port); case 'pri(bonding.subscribeNominatorMetadata)': return await this.subscribeStakingNominatorMetadata(id, port); - case 'pri(bonding.submitBondingTransaction)': - return await this.submitBonding(request as RequestBondingSubmit); - case 'pri(unbonding.submitTransaction)': - return await this.submitUnbonding(request as RequestUnbondingSubmit); - case 'pri(staking.submitClaimReward)': - return await this.submitStakeClaimReward(request as RequestStakeClaimReward); - case 'pri(staking.submitCancelWithdrawal)': - return await this.submitCancelStakeWithdrawal(request as RequestStakeCancelWithdrawal); - case 'pri(staking.submitTuringCompound)': - return await this.submitTuringStakeCompounding(request as RequestTuringStakeCompound); - case 'pri(staking.submitTuringCancelCompound)': - return await this.submitTuringCancelStakeCompound(request as RequestTuringCancelStakeCompound); - case 'pri(bonding.nominationPool.submitBonding)': - return await this.submitPoolBonding(request as RequestStakePoolingBonding); - case 'pri(bonding.nominationPool.submitUnbonding)': - return await this.submitPoolingUnbonding(request as RequestStakePoolingUnbonding); // EVM Transaction case 'pri(evm.transaction.parse.input)': diff --git a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts index ba29da49c83..481f85bd946 100644 --- a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts +++ b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 import { ChainType, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; -import { PalletStakingStakingLedger } from '@subwallet/extension-base/koni/api/staking/bonding/relayChain'; import KoniState from '@subwallet/extension-base/koni/background/handlers/State'; import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _getTokenOnChainAssetId } from '@subwallet/extension-base/services/chain-service/utils'; @@ -13,6 +12,14 @@ import { BN, BN_TEN, BN_ZERO } from '@polkadot/util'; import BaseLiquidStakingPoolHandler from './base'; +interface PalletStakingStakingLedger { + stash: string, + total: number, + active: number, + unlocking: UnlockingChunk[], + claimedRewards: number[] +} + // TODO: disable earning and stake actions but keep showing existing earning positions (currently handle on UI) export default class ParallelLiquidStakingPoolHandler extends BaseLiquidStakingPoolHandler { public slug: string; diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts index 7c70bd155d7..7257afc55c4 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts @@ -4,12 +4,11 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { APIItemState, ExtrinsicType, NominationInfo, UnstakingInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getBondedValidators, getEarningStatusByNominations, isUnstakeAll, KrestDelegateState } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; -import { BaseYieldPositionInfo, BasicTxErrorType, EarningRewardItem, EarningStatus, NativeYieldPoolInfo, ParachainStakingStakeOption, StakeCancelWithdrawalParams, SubmitJoinNativeStaking, TransactionData, UnstakingStatus, ValidatorInfo, YieldPoolInfo, YieldPositionInfo, YieldStepBaseInfo, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; +import { getBondedValidators, getEarningStatusByNominations, isUnstakeAll, parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; +import { BaseYieldPositionInfo, BasicTxErrorType, EarningRewardItem, EarningStatus, NativeYieldPoolInfo, StakeCancelWithdrawalParams, SubmitJoinNativeStaking, TransactionData, UnstakingStatus, ValidatorInfo, YieldPoolInfo, YieldPositionInfo, YieldStepBaseInfo, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, parseRawNumber, reformatAddress } from '@subwallet/extension-base/utils'; import { SubmittableExtrinsic } from '@polkadot/api/types'; @@ -19,6 +18,16 @@ import { BN, BN_ZERO } from '@polkadot/util'; import BaseParaNativeStakingPoolHandler from './base-para'; +interface ParachainStakingStakeOption { + owner: string, + amount: number +} + +interface KrestDelegateState { + delegations: ParachainStakingStakeOption[], + total: string +} + interface InflationConfig { collator: { maxRate: string, diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts index 3173cd8aad7..4f1bf47ab6d 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts @@ -4,9 +4,9 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { ExtrinsicType, NominationInfo, UnstakingInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getEarningStatusByNominations } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; +import { getEarningStatusByNominations } from '@subwallet/extension-base/services/earning-service/utils'; import { BaseYieldPositionInfo, BasicTxErrorType, EarningStatus, NativeYieldPoolInfo, PalletDappsStakingAccountLedger, PalletDappsStakingDappInfo, StakeCancelWithdrawalParams, SubmitJoinNativeStaking, TransactionData, UnstakingStatus, ValidatorInfo, YieldPoolInfo, YieldPoolMethodInfo, YieldPositionInfo, YieldStepBaseInfo, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, isUrl, parseRawNumber, reformatAddress } from '@subwallet/extension-base/utils'; diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/base-para.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/base-para.ts index d0409ce2873..4b6443231b3 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/base-para.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/base-para.ts @@ -3,8 +3,8 @@ import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { NominationInfo, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { getBondedValidators, getExistUnstakeErrorMessage, getMaxValidatorErrorMessage, getMinStakeErrorMessage } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import BaseNativeStakingPoolHandler from '@subwallet/extension-base/services/earning-service/handlers/native-staking/base'; +import { getBondedValidators, getExistUnstakeErrorMessage, getMaxValidatorErrorMessage, getMinStakeErrorMessage } from '@subwallet/extension-base/services/earning-service/utils'; import { BasicTxErrorType, EarningStatus, OptimalYieldPath, StakingTxErrorType, SubmitJoinNativeStaking, SubmitYieldJoinData, YieldStepBaseInfo, YieldStepType } from '@subwallet/extension-base/types'; import { isSameAddress, reformatAddress } from '@subwallet/extension-base/utils'; import { t } from 'i18next'; diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/energy.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/energy.ts index 9c21e920376..42fb551ceae 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/energy.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/energy.ts @@ -4,10 +4,9 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { ExtrinsicType, NominationInfo, UnstakingInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { calculateEnergyWebCollatorReturn, getBondedValidators, getEarningStatusByNominations, isUnstakeAll } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _EXPECTED_BLOCK_TIME, _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; +import { calculateEnergyWebCollatorReturn, getBondedValidators, getEarningStatusByNominations, isUnstakeAll, parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; import { BaseYieldPositionInfo, BasicTxErrorType, CollatorExtraInfo, EarningStatus, NativeYieldPoolInfo, PalletParachainStakingDelegationInfo, PalletParachainStakingRequestType, StakeCancelWithdrawalParams, SubmitJoinNativeStaking, TransactionData, UnstakingStatus, ValidatorInfo, YieldPoolInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, parseRawNumber, reformatAddress } from '@subwallet/extension-base/utils'; diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/mythos.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/mythos.ts index d3ef39d2f99..b5f6324c874 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/mythos.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/mythos.ts @@ -4,10 +4,10 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { APIItemState, ExtrinsicType, NominationInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getCommission } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _EXPECTED_BLOCK_TIME, _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; import BaseParaStakingPoolHandler from '@subwallet/extension-base/services/earning-service/handlers/native-staking/base-para'; +import { getCommission } from '@subwallet/extension-base/services/earning-service/utils'; import { BasicTxErrorType, EarningRewardItem, EarningStatus, NativeYieldPoolInfo, SubmitJoinNativeStaking, TransactionData, UnstakingInfo, UnstakingStatus, ValidatorInfo, YieldPoolInfo, YieldPoolMethodInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, reformatAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts index 33828980c1e..a9dafa89380 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts @@ -4,11 +4,10 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { ExtrinsicType, NominationInfo, UnstakingInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getBondedValidators, getEarningStatusByNominations, getParaCurrentInflation, InflationConfig, isUnstakeAll } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _EXPECTED_BLOCK_TIME, _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; import { _STAKING_CHAIN_GROUP, MANTA_MIN_DELEGATION, MANTA_VALIDATOR_POINTS_PER_BLOCK } from '@subwallet/extension-base/services/earning-service/constants'; -import { parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; +import { getBondedValidators, getEarningStatusByNominations, isUnstakeAll, parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; import { BaseYieldPositionInfo, BasicTxErrorType, CollatorExtraInfo, EarningStatus, NativeYieldPoolInfo, PalletParachainStakingDelegationRequestsScheduledRequest, PalletParachainStakingDelegator, ParachainStakingCandidateMetadata, StakeCancelWithdrawalParams, SubmitJoinNativeStaking, TransactionData, UnstakingStatus, ValidatorInfo, YieldPoolInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, parseRawNumber, reformatAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; @@ -48,6 +47,25 @@ interface AutoCompoundingDelegation { value: string } +function getParaCurrentInflation (totalStaked: number, inflationConfig: PalletParachainStakingInflationInflationInfo) { // read more at https://hackmd.io/@sbAqOuXkRvyiZPOB3Ryn6Q/Sypr3ZJh5 + const expectMin = parseRawNumber(inflationConfig.expect.min); + const expectMax = parseRawNumber(inflationConfig.expect.max); + + if (totalStaked < expectMin) { + const inflationString = inflationConfig.annual.min.split('%')[0]; + + return parseFloat(inflationString); + } else if (totalStaked > expectMax) { + const inflationString = inflationConfig.annual.max.split('%')[0]; + + return parseFloat(inflationString); + } + + const inflationString = inflationConfig.annual.ideal.split('%')[0]; + + return parseFloat(inflationString); +} + function calculateMantaNominatorReturn (decimal: number, commission: number, totalActiveCollators: number, bnAnnualInflation: BigN, blocksPreviousRound: number, bnCollatorExpectedBlocksPerRound: BigN, bnCollatorTotalStaked: BigN, isCountCommission: boolean) { const MIN_DELEGATION = new BigN(MANTA_MIN_DELEGATION as number); @@ -148,7 +166,7 @@ export default class ParaNativeStakingPoolHandler extends BaseParaNativeStakingP totalIssuance.add(unvestedAllocation); // for Turing network, read more at https://hackmd.io/@sbAqOuXkRvyiZPOB3Ryn6Q/Sypr3ZJh5 } - const inflationConfig = _inflation.toHuman() as unknown as InflationConfig; + const inflationConfig = _inflation.toHuman() as unknown as PalletParachainStakingInflationInflationInfo; const inflation = getParaCurrentInflation(parseRawNumber(totalStake.toString()), inflationConfig); const eraTime = _STAKING_ERA_LENGTH_MAP[this.chain] || _STAKING_ERA_LENGTH_MAP.default; // in hours const unstakingPeriod = parseInt(unstakingDelay) * eraTime; diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts index c6c0051bf49..7821f352469 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts @@ -4,15 +4,14 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { ExtrinsicType, NominationInfo, UnstakingInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { calculateAlephZeroValidatorReturn, calculateChainStakedReturnV2, calculateInflation, calculateTernoaValidatorReturn, calculateValidatorStakedReturn, getAvgValidatorEraReward, getCommission, getMaxValidatorErrorMessage, getMinStakeErrorMessage, getRelayBlockedValidatorList, getRelayEraRewardMap, getRelayMaxNominations, getRelayTopValidatorByPoints, getRelayValidatorPointsMap, getRelayWaitingValidatorList, getSupportedDaysByHistoryDepth } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; import { _getChainSubstrateAddressPrefix } from '@subwallet/extension-base/services/chain-service/utils'; import { _STAKING_CHAIN_GROUP, MaxEraRewardPointsEras } from '@subwallet/extension-base/services/earning-service/constants'; -import { applyDecimal, parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; -import { AllValidatorInfo, BaseYieldPositionInfo, BasicTxErrorType, EarningStatus, NativeYieldPoolInfo, OptimalYieldPath, PalletStakingActiveEraInfo, PalletStakingExposure, PalletStakingExposureItem, PalletStakingNominations, PalletStakingStakingLedger, SpStakingExposurePage, SpStakingPagedExposureMetadata, StakeCancelWithdrawalParams, StakingTxErrorType, SubmitChangeValidatorStaking, SubmitJoinNativeStaking, SubmitYieldJoinData, TernoaStakingRewardsStakingRewardsData, TransactionData, UnstakingStatus, ValidatorExtraInfo, ValidatorInfo, YieldPoolInfo, YieldPoolMethodInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; +import { applyDecimal, calculateAlephZeroValidatorReturn, calculateChainStakedReturnV2, calculateInflation, calculateTernoaValidatorReturn, calculateValidatorStakedReturn, getAvgValidatorEraReward, getCommission, getMaxValidatorErrorMessage, getMinStakeErrorMessage, getSupportedDaysByHistoryDepth, parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; +import { AllValidatorInfo, BaseYieldPositionInfo, BasicTxErrorType, EarningStatus, NativeYieldPoolInfo, OptimalYieldPath, PalletStakingActiveEraInfo, PalletStakingEraRewardPoints, PalletStakingExposure, PalletStakingExposureItem, PalletStakingNominations, PalletStakingStakingLedger, PalletStakingValidatorPrefs, SpStakingExposurePage, SpStakingPagedExposureMetadata, StakeCancelWithdrawalParams, StakingTxErrorType, SubmitChangeValidatorStaking, SubmitJoinNativeStaking, SubmitYieldJoinData, TernoaStakingRewardsStakingRewardsData, TransactionData, UnstakingStatus, ValidatorExtraInfo, ValidatorInfo, YieldPoolInfo, YieldPoolMethodInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, reformatAddress } from '@subwallet/extension-base/utils'; -import BigN from 'bignumber.js'; +import BigN, { BigNumber } from 'bignumber.js'; import { t } from 'i18next'; import { SubmittableExtrinsic } from '@polkadot/api/types'; @@ -858,3 +857,103 @@ export default class RelayNativeStakingPoolHandler extends BaseNativeStakingPool } /* Other actions */ } + +function getRelayValidatorPointsMap (eraRewardMap: Record) { + // mapping store validator and totalPoints + const validatorTotalPointsMap: Record = {}; + + Object.values(eraRewardMap).forEach((info) => { + const individual = info.individual; + + Object.entries(individual).forEach(([validator, points]) => { + if (!validatorTotalPointsMap[validator]) { + validatorTotalPointsMap[validator] = new BigNumber(points); + } else { + validatorTotalPointsMap[validator] = validatorTotalPointsMap[validator].plus(points); + } + }); + }); + + return validatorTotalPointsMap; +} + +function getRelayTopValidatorByPoints (validatorPointsList: Record) { + const sortValidatorPointsList = Object.fromEntries( + Object.entries(validatorPointsList) + .sort( + ( + a: [string, BigNumber], + b: [string, BigNumber] + ) => a[1].minus(b[1]).toNumber() + ) + .reverse() + ); + + // keep 50% first validator + const entries = Object.entries(sortValidatorPointsList); + const endIndex = Math.ceil(entries.length / 2); + const top50PercentEntries = entries.slice(0, endIndex); + const top50PercentRecord = Object.fromEntries(top50PercentEntries); + + return Object.keys(top50PercentRecord); +} + +function getRelayBlockedValidatorList (validators: any[]) { + const blockValidatorList: string[] = []; + + for (const validator of validators) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const validatorAddress = validator[0].toHuman()[0] as string; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const validatorPrefs = validator[1].toHuman() as unknown as PalletStakingValidatorPrefs; + + const isBlocked = validatorPrefs.blocked; + + if (isBlocked) { + blockValidatorList.push(validatorAddress); + } + } + + return blockValidatorList; +} + +function getRelayWaitingValidatorList (validators: any[]) { + const waitingValidators: string[] = []; + + for (const validator of validators) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const validatorAddress = validator[0].toHuman()[0] as string; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const validatorPrefs = validator[1].toHuman() as unknown as PalletStakingValidatorPrefs; + + const isBlocked = validatorPrefs.blocked; + + if (!isBlocked) { + waitingValidators.push(validatorAddress); + } + } + + return waitingValidators; +} + +function getRelayEraRewardMap (eraRewardPointArray: Codec[], startEraForPoints: number) { + const eraRewardMap: Record = {}; + + for (const item of eraRewardPointArray) { + eraRewardMap[startEraForPoints] = item.toPrimitive() as unknown as PalletStakingEraRewardPoints; + startEraForPoints++; + } + + return eraRewardMap; +} + +async function getRelayMaxNominations (substrateApi: _SubstrateApi, chain: string) { + await substrateApi.isReady; + const maxNominations = substrateApi.api.consts.staking?.maxNominations?.toString(); + const _maxNominationsByNominationQuota = await substrateApi.api.call.stakingApi?.nominationsQuota(0); + const maxNominationsByNominationQuota = _maxNominationsByNominationQuota?.toString(); + + const fallbackMaxNominations = ['zkverify_testnet', 'zkverify'].includes(chain) ? '10' : '16'; + + return maxNominationsByNominationQuota || maxNominations || fallbackMaxNominations; +} diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/tao.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/tao.ts index c9dd4fc4df7..0e473841b57 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/tao.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/tao.ts @@ -5,11 +5,11 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { ExtrinsicType, NominationInfo } from '@subwallet/extension-base/background/KoniTypes'; import { BITTENSOR_REFRESH_STAKE_APY, BITTENSOR_REFRESH_STAKE_INFO } from '@subwallet/extension-base/constants'; -import { getEarningStatusByNominations } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import KoniState from '@subwallet/extension-base/koni/background/handlers/State'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; import { _getAssetDecimals, _getAssetSymbol } from '@subwallet/extension-base/services/chain-service/utils'; import BaseParaStakingPoolHandler from '@subwallet/extension-base/services/earning-service/handlers/native-staking/base-para'; +import { getEarningStatusByNominations } from '@subwallet/extension-base/services/earning-service/utils'; import { BaseYieldPositionInfo, BasicTxErrorType, EarningStatus, NativeYieldPoolInfo, OptimalYieldPath, StakeCancelWithdrawalParams, StakingTxErrorType, SubmitBittensorChangeValidatorStaking, SubmitJoinNativeStaking, TransactionData, UnstakingInfo, ValidatorInfo, YieldPoolInfo, YieldPoolMethodInfo, YieldPoolType, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { ProxyServiceRoute } from '@subwallet/extension-base/types/environment'; import { fetchFromProxyService, formatNumber, reformatAddress } from '@subwallet/extension-base/utils'; diff --git a/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts b/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts index fdbf6b1c021..806de06d125 100644 --- a/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts +++ b/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts @@ -4,23 +4,43 @@ import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { APIItemState, ChainType, ExtrinsicType, NominationInfo, StakingType, UnstakingInfo } from '@subwallet/extension-base/background/KoniTypes'; import { PalletNominationPoolsPoolMember } from '@subwallet/extension-base/core/substrate/types'; -import { calculateChainStakedReturnV2, calculateInflation, getAvgValidatorEraReward, getExistUnstakeErrorMessage, getMinStakeErrorMessage, getSupportedDaysByHistoryDepth, parsePoolStashAddress } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import KoniState from '@subwallet/extension-base/koni/background/handlers/State'; import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; import { _getChainSubstrateAddressPrefix } from '@subwallet/extension-base/services/chain-service/utils'; +import { calculateChainStakedReturnV2, calculateInflation, getAvgValidatorEraReward, getExistUnstakeErrorMessage, getMinStakeErrorMessage, getSupportedDaysByHistoryDepth } from '@subwallet/extension-base/services/earning-service/utils'; import { BaseYieldPositionInfo, BasicTxErrorType, EarningRewardHistoryItem, EarningRewardItem, EarningStatus, HandleYieldStepData, NominationPoolInfo, NominationYieldPoolInfo, OptimalYieldPath, OptimalYieldPathParams, PalletNominationPoolsBondedPoolInner, PalletStakingActiveEraInfo, PalletStakingExposure, PalletStakingExposureItem, PalletStakingNominations, RequestYieldStepSubmit, SpStakingExposurePage, StakeCancelWithdrawalParams, StakingTxErrorType, SubmitChangeValidatorStaking, SubmitJoinNominationPool, SubmitYieldJoinData, TransactionData, UnstakingStatus, YieldPoolInfo, YieldPoolMethodInfo, YieldPoolType, YieldPositionInfo, YieldStepBaseInfo, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, reformatAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; import { t } from 'i18next'; +import { ApiPromise } from '@polkadot/api'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { UnsubscribePromise } from '@polkadot/api-base/types/base'; import { Codec } from '@polkadot/types/types'; -import { BN, BN_ZERO, hexToString, isHex, noop } from '@polkadot/util'; +import { BN, BN_ZERO, bnToU8a, hexToString, isHex, noop, stringToU8a, u8aConcat } from '@polkadot/util'; import BasePoolHandler from '../base'; +function parsePoolStashAddress (api: ApiPromise, index: number, poolId: number, poolsPalletId: string) { + const ModPrefix = stringToU8a('modl'); + const U32Opts = { bitLength: 32, isLe: true }; + const EmptyH256 = new Uint8Array(32); + + return api.registry + .createType( + 'AccountId32', + u8aConcat( + ModPrefix, + poolsPalletId, + new Uint8Array([index]), + bnToU8a(new BN(poolId.toString()), U32Opts), + EmptyH256 + ) + ) + .toString(); +} + export default class NominationPoolHandler extends BasePoolHandler { public readonly type = YieldPoolType.NOMINATION_POOL; protected readonly name: string; diff --git a/packages/extension-base/src/services/earning-service/utils/index.ts b/packages/extension-base/src/services/earning-service/utils/index.ts index fb1e2f0a65c..10d305c2da9 100644 --- a/packages/extension-base/src/services/earning-service/utils/index.ts +++ b/packages/extension-base/src/services/earning-service/utils/index.ts @@ -1,12 +1,19 @@ // Copyright 2019-2022 @subwallet/extension-base // SPDX-License-Identifier: Apache-2.0 -import { PalletIdentityRegistration, PalletIdentitySuper } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { _ChainInfo } from '@subwallet/chain-list/types'; +import { NominationInfo, StakingType } from '@subwallet/extension-base/background/KoniTypes'; +import { _KNOWN_CHAIN_INFLATION_PARAMS, _SUBSTRATE_DEFAULT_INFLATION_PARAMS, _SubstrateInflationParams } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { LendingYieldPoolInfo, LiquidYieldPoolInfo, NativeYieldPoolInfo, NominationYieldPoolInfo, YieldAssetExpectedEarning, YieldCompoundingPeriod, YieldPoolInfo, YieldPoolType } from '@subwallet/extension-base/types'; +import { _getChainNativeTokenBasicInfo } from '@subwallet/extension-base/services/chain-service/utils'; +import { _STAKING_CHAIN_GROUP, RELAY_HANDLER_DIRECT_STAKING_CHAINS } from '@subwallet/extension-base/services/earning-service/constants'; +import { EarningStatus, LendingYieldPoolInfo, LiquidYieldPoolInfo, NativeYieldPoolInfo, NominationYieldPoolInfo, PalletIdentityRegistration, PalletIdentitySuper, UnstakingStatus, YieldAction, YieldAssetExpectedEarning, YieldCompoundingPeriod, YieldPoolInfo, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; +import { balanceFormatter, detectTranslate, formatNumber, reformatAddress } from '@subwallet/extension-base/utils'; +import BigNumber from 'bignumber.js'; +import { t } from 'i18next'; -import { BN, hexToString, isHex } from '@polkadot/util'; +import { Codec } from '@polkadot/types/types'; +import { BN, BN_BILLION, BN_HUNDRED, BN_MILLION, BN_THOUSAND, hexToString, isHex } from '@polkadot/util'; export function calculateReward (apr: number, amount = 0, compoundingPeriod = YieldCompoundingPeriod.YEARLY, isApy = false): YieldAssetExpectedEarning { if (!apr) { @@ -160,3 +167,402 @@ export function applyDecimal (bnNumber: BN, decimals: number) { return bnNumber.div(bnDecimals); } + +function getInflationParams (networkKey: string): _SubstrateInflationParams { + return _KNOWN_CHAIN_INFLATION_PARAMS[networkKey] || _SUBSTRATE_DEFAULT_INFLATION_PARAMS; +} + +function calcInflationUniformEraPayout (totalIssuance: BN, yearlyInflationInTokens: number): number { + const totalIssuanceInTokens = totalIssuance.div(BN_BILLION).div(BN_THOUSAND).toNumber(); + + return (totalIssuanceInTokens === 0 ? 0.0 : yearlyInflationInTokens / totalIssuanceInTokens); +} + +function calcInflationRewardCurve (minInflation: number, stakedFraction: number, idealStake: number, idealInterest: number, falloff: number) { + return (minInflation + ( + stakedFraction <= idealStake + ? (stakedFraction * (idealInterest - (minInflation / idealStake))) + : (((idealInterest * idealStake) - minInflation) * Math.pow(2, (idealStake - stakedFraction) / falloff)) + )); +} + +export function calculateInflation (totalEraStake: BN, totalIssuance: BN, numAuctions: number, networkKey: string) { + const inflationParams = getInflationParams(networkKey); + const { auctionAdjust, auctionMax, falloff, maxInflation, minInflation, stakeTarget } = inflationParams; + const idealStake = stakeTarget - (Math.min(auctionMax, numAuctions) * auctionAdjust); + const idealInterest = maxInflation / idealStake; + const stakedFraction = totalEraStake.mul(BN_MILLION).div(totalIssuance).toNumber() / BN_MILLION.toNumber(); + + if (_STAKING_CHAIN_GROUP.aleph.includes(networkKey)) { + if (inflationParams.yearlyInflationInTokens) { + return 100 * calcInflationUniformEraPayout(totalIssuance, inflationParams.yearlyInflationInTokens); + } else { + return 100 * calcInflationRewardCurve(minInflation, stakedFraction, idealStake, idealInterest, falloff); + } + } else { + return 100 * (minInflation + ( + stakedFraction <= idealStake + ? (stakedFraction * (idealInterest - (minInflation / idealStake))) + : (((idealInterest * idealStake) - minInflation) * Math.pow(2, (idealStake - stakedFraction) / falloff)) + )); + } +} + +export async function calculateChainStakedReturnV2 (chainInfo: _ChainInfo, totalIssuance: string, erasPerDay: number, lastTotalStaked: string, validatorEraReward: BigNumber, inflation: BigNumber, isCompound?: boolean) { + if (chainInfo.slug === 'analog_timechain') { + return await calculateAnalogChainStakedReturn(); + } + + const DAYS_PER_YEAR = 365; + const { decimals } = _getChainNativeTokenBasicInfo(chainInfo); + + const lastTotalStakedUnit = (new BigNumber(lastTotalStaked)).dividedBy(new BigNumber(10 ** decimals)); + const totalIssuanceUnit = (new BigNumber(totalIssuance)).dividedBy(new BigNumber(10 ** decimals)); + const supplyStaked = lastTotalStakedUnit.dividedBy(totalIssuanceUnit); + + const dayRewardRate = validatorEraReward.multipliedBy(erasPerDay).dividedBy(totalIssuance).multipliedBy(100); + + let inflationToStakers: BigNumber; + + if (!isCompound) { + inflationToStakers = dayRewardRate.multipliedBy(DAYS_PER_YEAR); + } else { + const multiplier = dayRewardRate.dividedBy(100).plus(1).exponentiatedBy(365); + + inflationToStakers = new BigNumber(100).multipliedBy(multiplier).minus(100); + } + + const averageRewardRate = (['avail_mainnet', 'dentnet'].includes(chainInfo.slug) ? inflation : inflationToStakers).dividedBy(supplyStaked); + + return averageRewardRate.toNumber(); +} + +export function calculateAlephZeroValidatorReturn (chainStakedReturn: number, commission: number) { + return chainStakedReturn * (100 - commission) / 100; +} + +export function calculateEnergyWebCollatorReturn (annualReward: string, collatorCommission: number, numberCollators: number, totalStake: string): number { + const rewardForNominators = new BigNumber(annualReward).multipliedBy(1 - collatorCommission); + const rewardPerNominator = rewardForNominators.div(numberCollators); + + return rewardPerNominator.div(totalStake).shiftedBy(2).toNumber(); +} + +export function calculateTernoaValidatorReturn (rewardPerValidator: number, validatorStake: number, commission: number) { + const percentRewardForNominators = (100 - commission) / 100; + const rewardForNominators = rewardPerValidator * percentRewardForNominators; + + const stakeRatio = rewardForNominators / validatorStake; + + return stakeRatio * 365 * 100; +} + +export async function calculateAnalogChainStakedReturn (): Promise { + const url = 'https://explorer-api.analog.one/api/nominations?projection=apy,rewardsClaimed,eraEndsTime'; + + try { + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + const errorText = await response.text(); + + throw new Error(`API error: ${response.status} - ${errorText}`); + } + + const apyInfo = await response.json() as { + data: { + apy: number; + } + }; + + return apyInfo?.data?.apy as number | undefined; + } catch (e) { + console.error('Fetch error:', e); + + return undefined; + } +} + +export function calculateValidatorStakedReturn (chainStakedReturn: number, totalValidatorStake: BN, avgStake: BN, commission: number) { + const bnAdjusted = avgStake.mul(BN_HUNDRED).div(totalValidatorStake); + const adjusted = bnAdjusted.toNumber() * chainStakedReturn; + // todo: should calculated in bignumber instead number? + const stakedReturn = (adjusted > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : adjusted) / 100; + + return stakedReturn * (100 - commission) / 100; // Deduct commission +} + +export function getCommission (commissionString: string) { + return parseFloat(commissionString.split('%')[0]); // Example: 12% +} + +export function getBondedValidators (nominations: NominationInfo[]) { + const bondedValidators: string[] = []; + let nominationCount = 0; + + for (const nomination of nominations) { + nominationCount += 1; + bondedValidators.push(reformatAddress(nomination.validatorAddress, 0)); + } + + return { + nominationCount, + bondedValidators + }; +} + +export function isUnstakeAll (selectedValidator: string, nominations: NominationInfo[], unstakeAmount: string) { + let isUnstakeAll = false; + + for (const nomination of nominations) { + const parsedValidatorAddress = reformatAddress(nomination.validatorAddress, 0); + const parsedSelectedValidator = reformatAddress(selectedValidator, 0); + + if (parsedValidatorAddress === parsedSelectedValidator) { + if (unstakeAmount === nomination.activeStake) { + isUnstakeAll = true; + } + + break; + } + } + + return isUnstakeAll; +} + +export function getEarningStatusByNominations (bnTotalActiveStake: BN, nominationList: NominationInfo[]): EarningStatus { + let stakingStatus: EarningStatus = EarningStatus.EARNING_REWARD; + + if (bnTotalActiveStake.isZero()) { + stakingStatus = EarningStatus.NOT_EARNING; + } else { + let invalidDelegationCount = 0; + + for (const nomination of nominationList) { + if (nomination.status === EarningStatus.NOT_EARNING) { + invalidDelegationCount += 1; + } + } + + if (invalidDelegationCount > 0 && invalidDelegationCount < nominationList.length) { + stakingStatus = EarningStatus.PARTIALLY_EARNING; + } else if (invalidDelegationCount === nominationList.length) { + stakingStatus = EarningStatus.NOT_EARNING; + } + } + + return stakingStatus; +} + +export function getAvgValidatorEraReward (supportedDays: number, eraRewardHistory: Codec[]) { + let sumEraReward = new BigNumber(0); + let failEra = 0; + + for (const _item of eraRewardHistory) { + const item = _item.toString(); + + if (!item) { + failEra += 1; + } else { + const eraReward = new BigNumber(item); + + sumEraReward = sumEraReward.plus(eraReward); + } + } + + return sumEraReward.dividedBy(new BigNumber(supportedDays - failEra)); +} + +export function getSupportedDaysByHistoryDepth (erasPerDay: number, maxSupportedEras: number, liveDay?: number) { + const maxSupportDay = Math.floor(maxSupportedEras / erasPerDay); + + if (liveDay && liveDay <= 30) { + return Math.min(Math.floor(liveDay - 1), maxSupportDay); + } + + if (maxSupportDay > 30) { + return 30; + } else { + return maxSupportDay; + } +} + +export const getMinStakeErrorMessage = (chainInfo: _ChainInfo, bnMinStake: BN): string => { + const tokenInfo = _getChainNativeTokenBasicInfo(chainInfo); + const number = formatNumber(bnMinStake.toString(), tokenInfo.decimals || 0, balanceFormatter); + + return t('bg.EARNING.koni.api.staking.bonding.utils.insufficientStakeToEarn', { + replace: { + tokenSymbol: tokenInfo.symbol, + number + } + }); +}; + +export function getYieldAvailableActionsByType (yieldPoolInfo: YieldPoolInfo): YieldAction[] { + if ([YieldPoolType.NATIVE_STAKING, YieldPoolType.NOMINATION_POOL].includes(yieldPoolInfo.type)) { + if (yieldPoolInfo.type === YieldPoolType.NOMINATION_POOL) { + return [YieldAction.STAKE, YieldAction.CLAIM_REWARD, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; + } + + const chain = yieldPoolInfo.chain; + + if (_STAKING_CHAIN_GROUP.para.includes(chain)) { + return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW, YieldAction.CANCEL_UNSTAKE]; + } else if (_STAKING_CHAIN_GROUP.energy.includes(chain)) { + return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW, YieldAction.CANCEL_UNSTAKE]; + } else if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { + return [YieldAction.STAKE, YieldAction.CLAIM_REWARD, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; + } else if (_STAKING_CHAIN_GROUP.amplitude.includes(chain)) { + return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; + } + } + + if (yieldPoolInfo.type === YieldPoolType.LENDING) { + return [YieldAction.START_EARNING, YieldAction.WITHDRAW_EARNING]; + } else if (yieldPoolInfo.type === YieldPoolType.LIQUID_STAKING) { + return [YieldAction.START_EARNING, YieldAction.UNSTAKE, YieldAction.WITHDRAW]; + } + + return [YieldAction.STAKE, YieldAction.UNSTAKE, YieldAction.WITHDRAW, YieldAction.CANCEL_UNSTAKE]; +} + +export function getYieldAvailableActionsByPosition (yieldPosition: YieldPositionInfo, yieldPoolInfo: YieldPoolInfo, unclaimedReward?: string): YieldAction[] { + const result: YieldAction[] = []; + + if ([YieldPoolType.NATIVE_STAKING, YieldPoolType.NOMINATION_POOL].includes(yieldPoolInfo.type)) { + result.push(YieldAction.STAKE); + + const bnActiveStake = new BigNumber(yieldPosition.activeStake); + + if (yieldPosition.activeStake && bnActiveStake.gt('0')) { + result.push(YieldAction.UNSTAKE); + + const isAstarNetwork = _STAKING_CHAIN_GROUP.astar.includes(yieldPosition.chain); + const isAmplitudeNetwork = _STAKING_CHAIN_GROUP.amplitude.includes(yieldPosition.chain); + const bnUnclaimedReward = new BigNumber(unclaimedReward || '0'); + + if ( + ((yieldPosition.type === YieldPoolType.NOMINATION_POOL || isAmplitudeNetwork) && bnUnclaimedReward.gt('0')) || + isAstarNetwork + ) { + result.push(YieldAction.CLAIM_REWARD); + } + } + + if (yieldPosition.unstakings.length > 0) { + result.push(YieldAction.CANCEL_UNSTAKE); + const hasClaimable = yieldPosition.unstakings.some((unstaking) => unstaking.status === UnstakingStatus.CLAIMABLE); + + if (hasClaimable) { + result.push(YieldAction.WITHDRAW); + } + } + } else if (yieldPoolInfo.type === YieldPoolType.LIQUID_STAKING) { + result.push(YieldAction.START_EARNING); + + const activeBalance = new BigNumber(yieldPosition.activeStake); + + if (activeBalance.gt('0')) { + result.push(YieldAction.UNSTAKE); + } + + const hasWithdrawal = yieldPosition.unstakings.some((unstakingInfo) => unstakingInfo.status === UnstakingStatus.CLAIMABLE); + + if (hasWithdrawal) { + result.push(YieldAction.WITHDRAW); + } + + // TODO: check has unstakings to withdraw + } else { + result.push(YieldAction.START_EARNING); + result.push(YieldAction.WITHDRAW_EARNING); // TODO + } + + return result; +} + +export function getValidatorLabel (chain: string) { + if (_STAKING_CHAIN_GROUP.astar.includes(chain)) { + return 'dApp'; + } else if (RELAY_HANDLER_DIRECT_STAKING_CHAINS.includes(chain) || _STAKING_CHAIN_GROUP.bittensor.includes(chain)) { + return 'Validator'; + } + + return 'Collator'; +} + +export const getMaxValidatorErrorMessage = (chainInfo: _ChainInfo, max: number): string => { + let message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxValidatorsSelection'); + const label = getValidatorLabel(chainInfo.slug); + + if (max > 1) { + switch (label) { + case 'dApp': + message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxDappsSelection'); + break; + case 'Collator': + message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxCollatorsSelection'); + break; + case 'Validator': + message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxValidatorsSelection'); + break; + } + } else { + switch (label) { + case 'dApp': + message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxOneDappSelection'); + break; + case 'Collator': + message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxOneCollatorSelection'); + break; + case 'Validator': + message = detectTranslate('bg.EARNING.koni.api.staking.bonding.utils.maxOneValidatorSelection'); + break; + } + } + + return t(message, { replace: { number: max } }); +}; + +export const getExistUnstakeErrorMessage = (chain: string, type?: StakingType, isStakeMore?: boolean): string => { + // todo: update all .staking.bonding translate key + const label = getValidatorLabel(chain); + + if (!isStakeMore) { + switch (label) { + case 'dApp': + return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromDappOnce'); + case 'Collator': + return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromCollatorOnce'); + + case 'Validator': { + if (type === StakingType.POOLED) { + return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromPoolOnce'); + } + + return t('bg.EARNING.koni.api.staking.bonding.utils.unstakeFromValidatorOnce'); + } + } + } else { + switch (label) { + case 'dApp': + return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingDapp'); + case 'Collator': + return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingCollator'); + + case 'Validator': { + if (type === StakingType.POOLED) { + return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingPool'); + } + + return t('bg.EARNING.koni.api.staking.bonding.utils.cannotStakeMoreOnUnstakingValidator'); + } + } + } +}; diff --git a/packages/extension-base/src/types/yield/actions/others.ts b/packages/extension-base/src/types/yield/actions/others.ts index e5345860d2e..283c0b0c208 100644 --- a/packages/extension-base/src/types/yield/actions/others.ts +++ b/packages/extension-base/src/types/yield/actions/others.ts @@ -112,3 +112,15 @@ export interface RequestEarningImpact { netuid: number; type: ExtrinsicType; } + +export enum YieldAction { + STAKE = 'STAKE', + UNSTAKE = 'UNSTAKE', + WITHDRAW = 'WITHDRAW', + CLAIM_REWARD = 'CLAIM_REWARD', + CANCEL_UNSTAKE = 'CANCEL_UNSTAKE', + + START_EARNING = 'EARN', + WITHDRAW_EARNING = 'WITHDRAW_EARNING', + CUSTOM_ACTION = 'CUSTOM_ACTION' +} diff --git a/packages/extension-base/src/types/yield/info/pallet.ts b/packages/extension-base/src/types/yield/info/pallet.ts index d7c2fb2029f..0e78c4a6837 100644 --- a/packages/extension-base/src/types/yield/info/pallet.ts +++ b/packages/extension-base/src/types/yield/info/pallet.ts @@ -1,8 +1,6 @@ // Copyright 2019-2022 @subwallet/extension-base // SPDX-License-Identifier: Apache-2.0 -import { BN } from '@polkadot/util'; - export interface PalletStakingExposureItem { who: string, value: number @@ -40,18 +38,6 @@ export interface PalletDappsStakingAccountLedger { } } -export interface BlockHeader { - parentHash: string, - number: number, - stateRoot: string, - extrinsicsRoot: string -} - -export interface ParachainStakingStakeOption { - owner: string, - amount: number -} - export interface ParachainStakingCandidateMetadata { bond: string, delegationCount: number, @@ -106,11 +92,6 @@ export interface PalletIdentityRegistration { export type PalletIdentitySuper = [string, { Raw: string }] -export interface Unlocking { - remainingEras: BN; - value: BN; -} - export interface TernoaStakingRewardsStakingRewardsData { sessionEraPayout: string, sessionExtraRewardPayout: string diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/Process/Earn/Bond.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/Process/Earn/Bond.tsx index a05f3c95b72..a39d2f27c82 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/Process/Earn/Bond.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/Process/Earn/Bond.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { RequestBondingSubmit, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { SummaryEarningProcessData } from '@subwallet/extension-base/types'; import CommonTransactionInfo from '@subwallet/extension-koni-ui/components/Confirmation/CommonTransactionInfo'; import MetaInfo from '@subwallet/extension-koni-ui/components/MetaInfo/MetaInfo'; diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx index 006126536ae..e9925487128 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { RequestBondingSubmit, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { AlertBox } from '@subwallet/extension-koni-ui/components'; import CommonTransactionInfo from '@subwallet/extension-koni-ui/components/Confirmation/CommonTransactionInfo'; import MetaInfo from '@subwallet/extension-koni-ui/components/MetaInfo/MetaInfo'; diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Process/Earn/Bond.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Process/Earn/Bond.tsx index f8e7e2877cc..75d4141c7ab 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Process/Earn/Bond.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/Transaction/variants/Process/Earn/Bond.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { RequestBondingSubmit, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { ProcessTransactionData, SummaryEarningProcessData } from '@subwallet/extension-base/types'; import CommonTransactionInfo from '@subwallet/extension-koni-ui/components/Confirmation/CommonTransactionInfo'; import MetaInfo from '@subwallet/extension-koni-ui/components/MetaInfo/MetaInfo'; diff --git a/packages/extension-koni-ui/src/Popup/Transaction/helper/staking/index.ts b/packages/extension-koni-ui/src/Popup/Transaction/helper/staking/index.ts index 2a9445d6d8c..4b88e65c0bc 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/helper/staking/index.ts +++ b/packages/extension-koni-ui/src/Popup/Transaction/helper/staking/index.ts @@ -2,4 +2,3 @@ // SPDX-License-Identifier: Apache-2.0 export * from './base'; -export * from './stakingHandler'; diff --git a/packages/extension-koni-ui/src/Popup/Transaction/helper/staking/stakingHandler.ts b/packages/extension-koni-ui/src/Popup/Transaction/helper/staking/stakingHandler.ts deleted file mode 100644 index 3c31e4c625e..00000000000 --- a/packages/extension-koni-ui/src/Popup/Transaction/helper/staking/stakingHandler.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { UnstakingStatus } from '@subwallet/extension-base/types'; -import { ALL_KEY } from '@subwallet/extension-koni-ui/constants/common'; -import { getBondingOptions, getNominationPoolOptions } from '@subwallet/extension-koni-ui/messaging'; -import { store } from '@subwallet/extension-koni-ui/stores'; -// @ts-ignore -import humanizeDuration from 'humanize-duration'; -import { TFunction } from 'react-i18next'; - -export function getUnstakingPeriod (t: TFunction, unstakingPeriod?: number) { - if (unstakingPeriod) { - const days = unstakingPeriod / 24; - - if (days < 1) { - return t('ui.TRANSACTION.screen.Transaction.helper.stakingHandler.timeHours', { replace: { time: unstakingPeriod } }); - } else { - return t('ui.TRANSACTION.screen.Transaction.helper.stakingHandler.timeDays', { replace: { time: days } }); - } - } - - return ''; -} - -export function getWaitingTime (waitingTime: number, status: UnstakingStatus, t: TFunction) { - if (status === UnstakingStatus.CLAIMABLE) { - return t('ui.TRANSACTION.screen.Transaction.helper.stakingHandler.availableForWithdrawal'); - } else { - const waitingTimeInMs = waitingTime * 60 * 60 * 1000; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment - const formattedWaitingTime = humanizeDuration(waitingTimeInMs, { - units: ['d', 'h'], - round: true, - delimiter: ' ', - language: 'shortEn', - languages: { - shortEn: { - y: () => 'y', - mo: () => 'mo', - w: () => 'w', - d: () => 'd', - h: () => 'hr', - m: () => 'm', - s: () => 's', - ms: () => 'ms' - } - } // TODO: should not be shorten - }) as string; - - return t('ui.TRANSACTION.screen.Transaction.helper.stakingHandler.withdrawableInTime', { replace: { time: formattedWaitingTime } }); - } -} - -const fetchChainValidator = (chain: string, unmount: boolean, setValidatorLoading: (value: boolean) => void, setForceFetchValidator: (value: boolean) => void) => { - if (!unmount) { - setValidatorLoading(true); - getBondingOptions(chain, StakingType.NOMINATED) - .then((result) => { - store.dispatch({ type: 'bonding/updateChainValidators', payload: { chain, validators: result } }); - }) - .catch(console.error) - .finally(() => { - if (!unmount) { - setValidatorLoading(false); - setForceFetchValidator(false); - } - }); - } -}; - -const fetchChainPool = (chain: string, unmount: boolean, setPoolLoading: (value: boolean) => void, setForceFetchValidator: (value: boolean) => void) => { - if (!unmount && _STAKING_CHAIN_GROUP.nominationPool.includes(chain)) { - setPoolLoading(true); - getNominationPoolOptions(chain) - .then((result) => { - store.dispatch({ type: 'bonding/updateNominationPools', payload: { chain, pools: result } }); - }) - .catch(console.error) - .finally(() => { - if (!unmount) { - setPoolLoading(false); - setForceFetchValidator(false); - } - }); - } -}; - -export function fetchChainValidators ( - chain: string, - stakingType: string, - unmount: boolean, - setPoolLoading: (value: boolean) => void, - setValidatorLoading: (value: boolean) => void, - setForceFetchValidator: (value: boolean) => void -) { - if (stakingType === ALL_KEY) { - fetchChainValidator(chain, unmount, setValidatorLoading, setForceFetchValidator); - fetchChainPool(chain, unmount, setPoolLoading, setForceFetchValidator); - } else if (stakingType === StakingType.NOMINATED) { - fetchChainValidator(chain, unmount, setValidatorLoading, setForceFetchValidator); - } else if (stakingType === StakingType.POOLED) { - fetchChainPool(chain, unmount, setPoolLoading, setForceFetchValidator); - } -} diff --git a/packages/extension-koni-ui/src/Popup/Transaction/variants/Unbond.tsx b/packages/extension-koni-ui/src/Popup/Transaction/variants/Unbond.tsx index 9a6a4dda106..d529d99cb2d 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/Unbond.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/Unbond.tsx @@ -3,9 +3,8 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { AmountData, ExtrinsicType, NominationInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { isActionFromValidator } from '@subwallet/extension-base/services/earning-service/utils'; +import { getValidatorLabel, isActionFromValidator } from '@subwallet/extension-base/services/earning-service/utils'; import { AccountJson, RequestYieldLeave, SlippageType, SpecialYieldPoolMetadata, SubnetYieldPositionInfo, YieldPoolInfo, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; import { AccountSelector, AlertBox, AmountInput, HiddenInput, InstructionItem, MetaInfo, NominationSelector } from '@subwallet/extension-koni-ui/components'; import { BN_ZERO, UNSTAKE_ALERT_DATA, UNSTAKE_BIFROST_ALERT_DATA, UNSTAKE_BITTENSOR_ALERT_DATA, UNSTAKE_TANSSI_ALERT_DATA } from '@subwallet/extension-koni-ui/constants'; diff --git a/packages/extension-koni-ui/src/components/Field/Earning/EarningPoolSelector.tsx b/packages/extension-koni-ui/src/components/Field/Earning/EarningPoolSelector.tsx index 9523fab0e4b..9751c17c706 100644 --- a/packages/extension-koni-ui/src/components/Field/Earning/EarningPoolSelector.tsx +++ b/packages/extension-koni-ui/src/components/Field/Earning/EarningPoolSelector.tsx @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { YieldPoolType } from '@subwallet/extension-base/types'; import { fetchStaticData } from '@subwallet/extension-base/utils'; import { StakingPoolItem } from '@subwallet/extension-koni-ui/components'; diff --git a/packages/extension-koni-ui/src/components/Field/Earning/EarningValidatorSelector.tsx b/packages/extension-koni-ui/src/components/Field/Earning/EarningValidatorSelector.tsx index 2abf012fef7..943d7ab6462 100644 --- a/packages/extension-koni-ui/src/components/Field/Earning/EarningValidatorSelector.tsx +++ b/packages/extension-koni-ui/src/components/Field/Earning/EarningValidatorSelector.tsx @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { ChainRecommendValidator } from '@subwallet/extension-base/constants'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_CHAIN_GROUP, RELAY_HANDLER_DIRECT_STAKING_CHAINS } from '@subwallet/extension-base/services/earning-service/constants'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { NominationInfo, YieldPoolType } from '@subwallet/extension-base/types'; import { detectTranslate, fetchStaticData } from '@subwallet/extension-base/utils'; import { SelectValidatorInput, StakingValidatorItem } from '@subwallet/extension-koni-ui/components'; diff --git a/packages/extension-koni-ui/src/components/Field/NominationSelector.tsx b/packages/extension-koni-ui/src/components/Field/NominationSelector.tsx index ac4a7cd2e56..ec4ad38f284 100644 --- a/packages/extension-koni-ui/src/components/Field/NominationSelector.tsx +++ b/packages/extension-koni-ui/src/components/Field/NominationSelector.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { NominationInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { YieldPoolInfo } from '@subwallet/extension-base/types'; import { StakingNominationItem } from '@subwallet/extension-koni-ui/components'; import { Avatar } from '@subwallet/extension-koni-ui/components/Avatar'; diff --git a/packages/extension-koni-ui/src/components/Modal/Earning/EarningInstructionModal.tsx b/packages/extension-koni-ui/src/components/Modal/Earning/EarningInstructionModal.tsx index b9b720df53a..ce1686667fc 100644 --- a/packages/extension-koni-ui/src/components/Modal/Earning/EarningInstructionModal.tsx +++ b/packages/extension-koni-ui/src/components/Modal/Earning/EarningInstructionModal.tsx @@ -3,10 +3,9 @@ import { NotificationType } from '@subwallet/extension-base/background/KoniTypes'; import { ALL_ACCOUNT_KEY } from '@subwallet/extension-base/constants'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _isChainInfoCompatibleWithAccountInfo } from '@subwallet/extension-base/services/chain-service/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { calculateReward } from '@subwallet/extension-base/services/earning-service/utils'; +import { calculateReward, getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { YieldPoolType } from '@subwallet/extension-base/types'; import { balanceFormatter, detectTranslate, formatNumber } from '@subwallet/extension-base/utils'; import { InstructionItem } from '@subwallet/extension-koni-ui/components'; diff --git a/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx b/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx index 885d790c6e1..a0e79b49b0f 100644 --- a/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx +++ b/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx @@ -1,8 +1,8 @@ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_CHAIN_GROUP, RELAY_HANDLER_DIRECT_STAKING_CHAINS } from '@subwallet/extension-base/services/earning-service/constants'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { MetaInfo } from '@subwallet/extension-koni-ui/components'; import { VALIDATOR_DETAIL_MODAL } from '@subwallet/extension-koni-ui/constants'; import { useGetChainPrefixBySlug } from '@subwallet/extension-koni-ui/hooks'; diff --git a/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorSelectedModal/index.tsx b/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorSelectedModal/index.tsx index 0754a75af0d..d7a0e36098a 100644 --- a/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorSelectedModal/index.tsx +++ b/packages/extension-koni-ui/src/components/Modal/Earning/EarningValidatorSelectedModal/index.tsx @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _getAssetDecimals, _getAssetSymbol } from '@subwallet/extension-base/services/chain-service/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { NominationInfo, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; import { StakingNominationItem, StakingValidatorItem } from '@subwallet/extension-koni-ui/components'; import EmptyValidator from '@subwallet/extension-koni-ui/components/Account/EmptyValidator'; diff --git a/packages/extension-koni-ui/src/components/Modal/TransactionProcessDetailModal/parts/TransactionInfoBlock/Earn/Bond.tsx b/packages/extension-koni-ui/src/components/Modal/TransactionProcessDetailModal/parts/TransactionInfoBlock/Earn/Bond.tsx index 08ecfb5db0c..97d834e79e5 100644 --- a/packages/extension-koni-ui/src/components/Modal/TransactionProcessDetailModal/parts/TransactionInfoBlock/Earn/Bond.tsx +++ b/packages/extension-koni-ui/src/components/Modal/TransactionProcessDetailModal/parts/TransactionInfoBlock/Earn/Bond.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { RequestBondingSubmit, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { CommonFeeComponent, SummaryEarningProcessData } from '@subwallet/extension-base/types'; import CommonTransactionInfo from '@subwallet/extension-koni-ui/components/Confirmation/CommonTransactionInfo'; import MetaInfo from '@subwallet/extension-koni-ui/components/MetaInfo/MetaInfo'; diff --git a/packages/extension-koni-ui/src/components/NetworkInformation/index.tsx b/packages/extension-koni-ui/src/components/NetworkInformation/index.tsx deleted file mode 100644 index 92d2754affe..00000000000 --- a/packages/extension-koni-ui/src/components/NetworkInformation/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { AmountData, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import InfoIcon from '@subwallet/extension-koni-ui/components/Icon/InfoIcon'; -import MetaInfo from '@subwallet/extension-koni-ui/components/MetaInfo/MetaInfo'; -import useTranslation from '@subwallet/extension-koni-ui/hooks/common/useTranslation'; -import { getUnstakingPeriod } from '@subwallet/extension-koni-ui/Popup/Transaction/helper/staking/stakingHandler'; -import { ThemeProps } from '@subwallet/extension-koni-ui/types'; -import { ModalContext, Number, SwModal, SwNumberProps } from '@subwallet/react-ui'; -import { BigNumber } from 'bignumber.js'; -import React, { useCallback, useContext } from 'react'; -import styled from 'styled-components'; - -type Props = ThemeProps & { - activeNominators?: SwNumberProps['value']; - estimatedEarning?: SwNumberProps['value']; - inflation?: SwNumberProps['value']; - minimumActive: AmountData; - unstakingPeriod?: number; - maxValidatorPerNominator: SwNumberProps['value']; - stakingType: StakingType; -}; - -export const StakingNetworkDetailModalId = 'stakingNetworkDetailModalId'; - -function Component ({ activeNominators, - className, - estimatedEarning, - inflation, - maxValidatorPerNominator, - minimumActive, - stakingType, - unstakingPeriod }: Props): React.ReactElement { - const { t } = useTranslation(); - const { inactiveModal } = useContext(ModalContext); - - const onCancel = useCallback(() => { - inactiveModal(StakingNetworkDetailModalId); - }, [inactiveModal]); - - return ( - - }} - title={t('ui.NETWORK.components.NetworkInformation.networkDetails')} - > - - { - stakingType === StakingType.NOMINATED && ( - <> - - - { - !!activeNominators && - ( - -
- -
-
- ) - } - - ) - } - - {!!estimatedEarning && !!inflation && - -
- - / - - {t('ui.NETWORK.components.NetworkInformation.afterInflation')} -
-
- } - - - - {!!unstakingPeriod && - {getUnstakingPeriod(t, unstakingPeriod)} - } -
-
- ); -} - -export const StakingNetworkDetailModal = styled(Component)(({ theme: { token } }: Props) => { - return ({ - '.__active-nominators-value': { - }, - - '.__slash': { - marginLeft: token.marginXXS, - marginRight: token.marginXXS - }, - - '.__inflation': { - marginLeft: token.marginXXS, - color: token.colorTextLight4 - }, - - '.__current-nominator-count, .__total-nominator-count': { - display: 'inline-flex' - }, - - '.__total-nominator-count': { - color: token.colorTextLight4 - }, - - '.-to-right': { - textAlign: 'right' - } - }); -}); diff --git a/packages/extension-koni-ui/src/components/SelectValidatorInput.tsx b/packages/extension-koni-ui/src/components/SelectValidatorInput.tsx index fe3f2b852c5..1537612393e 100644 --- a/packages/extension-koni-ui/src/components/SelectValidatorInput.tsx +++ b/packages/extension-koni-ui/src/components/SelectValidatorInput.tsx @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import AvatarGroup, { BaseAccountInfo } from '@subwallet/extension-koni-ui/components/Account/Info/AvatarGroup'; import { ThemeProps } from '@subwallet/extension-koni-ui/types'; import { toShort } from '@subwallet/extension-koni-ui/utils'; diff --git a/packages/extension-koni-ui/src/hooks/modal/useSelectValidators.ts b/packages/extension-koni-ui/src/hooks/modal/useSelectValidators.ts index 797188b17fd..a3d2dad5a0b 100644 --- a/packages/extension-koni-ui/src/hooks/modal/useSelectValidators.ts +++ b/packages/extension-koni-ui/src/hooks/modal/useSelectValidators.ts @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { detectTranslate } from '@subwallet/extension-base/utils'; import { BasicOnChangeFunction } from '@subwallet/extension-koni-ui/components/Field/Base'; import { useNotification, useTranslation } from '@subwallet/extension-koni-ui/hooks/common'; diff --git a/packages/extension-koni-ui/src/messaging/transaction/index.ts b/packages/extension-koni-ui/src/messaging/transaction/index.ts index 19c430bf0c0..865a55f3b1a 100644 --- a/packages/extension-koni-ui/src/messaging/transaction/index.ts +++ b/packages/extension-koni-ui/src/messaging/transaction/index.ts @@ -5,5 +5,4 @@ export * from './base'; export * from './earning'; export * from './multi'; export * from './nft'; -export * from './staking'; export * from './transfer'; diff --git a/packages/extension-koni-ui/src/messaging/transaction/staking.ts b/packages/extension-koni-ui/src/messaging/transaction/staking.ts deleted file mode 100644 index b6bddbc4d8b..00000000000 --- a/packages/extension-koni-ui/src/messaging/transaction/staking.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ChainStakingMetadata, NominatorMetadata, RequestBondingSubmit, RequestStakePoolingUnbonding, RequestSubscribeStaking, RequestSubscribeStakingReward, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, StakingJson, StakingRewardJson, StakingType, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; -import { NominationPoolInfo, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestYieldStepSubmit } from '@subwallet/extension-base/types'; - -import { sendMessage } from '../base'; - -export async function submitPoolBonding (request: RequestYieldStepSubmit): Promise { - return sendMessage('pri(bonding.nominationPool.submitBonding)', request); -} - -export async function submitPoolUnbonding (request: RequestStakePoolingUnbonding): Promise { - return sendMessage('pri(bonding.nominationPool.submitUnbonding)', request); -} - -export async function submitBonding (request: RequestBondingSubmit): Promise { - return sendMessage('pri(bonding.submitBondingTransaction)', request); -} - -export async function submitUnbonding (request: RequestUnbondingSubmit): Promise { - return sendMessage('pri(unbonding.submitTransaction)', request); -} - -export async function submitStakeClaimReward (request: RequestStakeClaimReward): Promise { - return sendMessage('pri(staking.submitClaimReward)', request); -} - -export async function submitStakeCancelWithdrawal (request: RequestStakeCancelWithdrawal): Promise { - return sendMessage('pri(staking.submitCancelWithdrawal)', request); -} - -export async function submitTuringStakeCompounding (request: RequestTuringStakeCompound): Promise { - return sendMessage('pri(staking.submitTuringCompound)', request); -} - -export async function submitTuringCancelStakeCompounding (request: RequestTuringCancelStakeCompound): Promise { - return sendMessage('pri(staking.submitTuringCancelCompound)', request); -} - -export async function getBondingOptions (networkKey: string, type: StakingType): Promise { - return sendMessage('pri(bonding.getBondingOptions)', { chain: networkKey, type }); -} - -export async function getNominationPoolOptions (chain: string): Promise { - return sendMessage('pri(bonding.getNominationPoolOptions)', chain); -} - -export async function subscribeChainStakingMetadata (callback: (data: ChainStakingMetadata[]) => void): Promise { - return sendMessage('pri(bonding.subscribeChainStakingMetadata)', null, callback); -} - -export async function subscribeStakingNominatorMetadata (callback: (data: NominatorMetadata[]) => void): Promise { - return sendMessage('pri(bonding.subscribeNominatorMetadata)', null, callback); -} - -export async function getStaking (account: string): Promise { - // @ts-ignore - return sendMessage('pri(staking.getStaking)', account); -} - -export async function subscribeStaking (request: RequestSubscribeStaking, callback: (stakingData: StakingJson) => void): Promise { - return sendMessage('pri(staking.getSubscription)', request, callback); -} - -export async function getStakingReward (): Promise { - return sendMessage('pri(stakingReward.getStakingReward)'); -} - -export async function subscribeStakingReward (request: RequestSubscribeStakingReward, callback: (stakingRewardData: StakingRewardJson) => void): Promise { - return sendMessage('pri(stakingReward.getSubscription)', request, callback); -} diff --git a/packages/extension-web-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx b/packages/extension-web-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx index c26676768a9..3d1e13fb427 100644 --- a/packages/extension-web-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx +++ b/packages/extension-web-ui/src/Popup/Confirmations/variants/Transaction/variants/Bond.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { RequestBondingSubmit, StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { AlertBox } from '@subwallet/extension-web-ui/components'; import CommonTransactionInfo from '@subwallet/extension-web-ui/components/Confirmation/CommonTransactionInfo'; import MetaInfo from '@subwallet/extension-web-ui/components/MetaInfo/MetaInfo'; diff --git a/packages/extension-web-ui/src/Popup/Transaction/helper/staking/index.ts b/packages/extension-web-ui/src/Popup/Transaction/helper/staking/index.ts index 261cd53087d..7f740c5c050 100644 --- a/packages/extension-web-ui/src/Popup/Transaction/helper/staking/index.ts +++ b/packages/extension-web-ui/src/Popup/Transaction/helper/staking/index.ts @@ -2,4 +2,3 @@ // SPDX-License-Identifier: Apache-2.0 export * from './base'; -export * from './stakingHandler'; diff --git a/packages/extension-web-ui/src/Popup/Transaction/helper/staking/stakingHandler.ts b/packages/extension-web-ui/src/Popup/Transaction/helper/staking/stakingHandler.ts deleted file mode 100644 index 914a9fce84e..00000000000 --- a/packages/extension-web-ui/src/Popup/Transaction/helper/staking/stakingHandler.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-koni authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { StakingType } from '@subwallet/extension-base/background/KoniTypes'; -import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { UnstakingStatus } from '@subwallet/extension-base/types'; -import { ALL_KEY } from '@subwallet/extension-web-ui/constants/common'; -import { getBondingOptions, getNominationPoolOptions } from '@subwallet/extension-web-ui/messaging'; -import { store } from '@subwallet/extension-web-ui/stores'; -// @ts-ignore -import humanizeDuration from 'humanize-duration'; -import { TFunction } from 'react-i18next'; - -export function getUnstakingPeriod (t: TFunction, unstakingPeriod?: number) { - if (unstakingPeriod) { - const days = unstakingPeriod / 24; - - if (days < 1) { - return t('{{time}} hours', { replace: { time: unstakingPeriod } }); - } else { - return t('{{time}} days', { replace: { time: days } }); - } - } - - return ''; -} - -export function getWaitingTime (status: UnstakingStatus, t: TFunction, waitingTime?: number) { - if (status === UnstakingStatus.CLAIMABLE) { - return t('Available for withdrawal'); - } else { - if (waitingTime === undefined) { - return t('Waiting for withdrawal'); - } - - const waitingTimeInMs = waitingTime * 60 * 60 * 1000; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment - const formattedWaitingTime = humanizeDuration(waitingTimeInMs, { - units: ['d', 'h'], - round: true, - delimiter: ' ', - language: 'shortEn', - languages: { - shortEn: { - y: () => 'y', - mo: () => 'mo', - w: () => 'w', - d: () => 'd', - h: () => 'hr', - m: () => 'm', - s: () => 's', - ms: () => 'ms' - } - } // TODO: should not be shorten - }) as string; - - return t('Withdrawable in {{time}}', { replace: { time: formattedWaitingTime } }); - } -} - -export const fetchChainValidator = (chain: string, unmount: boolean, setValidatorLoading: (value: boolean) => void, setForceFetchValidator: (value: boolean) => void) => { - if (!unmount) { - setValidatorLoading(true); - getBondingOptions(chain, StakingType.NOMINATED) - .then((result) => { - store.dispatch({ type: 'bonding/updateChainValidators', payload: { chain, validators: result } }); - }) - .catch(console.error) - .finally(() => { - if (!unmount) { - setValidatorLoading(false); - setForceFetchValidator(false); - } - }); - } -}; - -export const fetchChainPool = (chain: string, unmount: boolean, setPoolLoading: (value: boolean) => void, setForceFetchValidator: (value: boolean) => void) => { - if (!unmount && _STAKING_CHAIN_GROUP.nominationPool.includes(chain)) { - setPoolLoading(true); - getNominationPoolOptions(chain) - .then((result) => { - store.dispatch({ type: 'bonding/updateNominationPools', payload: { chain, pools: result } }); - }) - .catch(console.error) - .finally(() => { - if (!unmount) { - setPoolLoading(false); - setForceFetchValidator(false); - } - }); - } -}; - -export function fetchChainValidators ( - chain: string, - stakingType: string, - unmount: boolean, - setPoolLoading: (value: boolean) => void, - setValidatorLoading: (value: boolean) => void, - setForceFetchValidator: (value: boolean) => void -) { - if (stakingType === ALL_KEY) { - fetchChainValidator(chain, unmount, setValidatorLoading, setForceFetchValidator); - fetchChainPool(chain, unmount, setPoolLoading, setForceFetchValidator); - } else if (stakingType === StakingType.NOMINATED) { - fetchChainValidator(chain, unmount, setValidatorLoading, setForceFetchValidator); - } else if (stakingType === StakingType.POOLED) { - fetchChainPool(chain, unmount, setPoolLoading, setForceFetchValidator); - } -} diff --git a/packages/extension-web-ui/src/Popup/Transaction/variants/Earn.tsx b/packages/extension-web-ui/src/Popup/Transaction/variants/Earn.tsx index 998acb3afdd..9ccd2cb6497 100644 --- a/packages/extension-web-ui/src/Popup/Transaction/variants/Earn.tsx +++ b/packages/extension-web-ui/src/Popup/Transaction/variants/Earn.tsx @@ -4,10 +4,9 @@ import { _ChainAsset } from '@subwallet/chain-list/types'; import { ExtrinsicType, NotificationType } from '@subwallet/extension-base/background/KoniTypes'; import { _handleDisplayInsufficientEarningError } from '@subwallet/extension-base/core/logic-validation/earning'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _getAssetDecimals, _getAssetSymbol, _getSubstrateGenesisHash, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { isLendingPool, isLiquidPool } from '@subwallet/extension-base/services/earning-service/utils'; +import { getValidatorLabel, isLendingPool, isLiquidPool } from '@subwallet/extension-base/services/earning-service/utils'; import { SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; import { EarningStatus, NominationPoolInfo, OptimalYieldPath, OptimalYieldPathParams, SubmitJoinNativeStaking, SubmitJoinNominationPool, SubmitYieldJoinData, ValidatorInfo, YieldPoolInfo, YieldPoolType, YieldStepType } from '@subwallet/extension-base/types'; import { addLazy } from '@subwallet/extension-base/utils'; diff --git a/packages/extension-web-ui/src/Popup/Transaction/variants/Unbond.tsx b/packages/extension-web-ui/src/Popup/Transaction/variants/Unbond.tsx index 67621abd656..c8412edee86 100644 --- a/packages/extension-web-ui/src/Popup/Transaction/variants/Unbond.tsx +++ b/packages/extension-web-ui/src/Popup/Transaction/variants/Unbond.tsx @@ -3,8 +3,7 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; import { AmountData, ExtrinsicType, NominationInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; -import { isActionFromValidator } from '@subwallet/extension-base/services/earning-service/utils'; +import { getValidatorLabel, isActionFromValidator } from '@subwallet/extension-base/services/earning-service/utils'; import { AccountJson, RequestYieldLeave, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; import { AccountSelector, AlertBox, AmountInput, HiddenInput, InstructionItem, NominationSelector } from '@subwallet/extension-web-ui/components'; import { BN_ZERO, UNSTAKE_ALERT_DATA } from '@subwallet/extension-web-ui/constants'; diff --git a/packages/extension-web-ui/src/components/Earning/desktop/EarningPositionDesktopItem.tsx b/packages/extension-web-ui/src/components/Earning/desktop/EarningPositionDesktopItem.tsx index 4aca023a6ce..7fd64c746b6 100644 --- a/packages/extension-web-ui/src/components/Earning/desktop/EarningPositionDesktopItem.tsx +++ b/packages/extension-web-ui/src/components/Earning/desktop/EarningPositionDesktopItem.tsx @@ -1,8 +1,8 @@ // Copyright 2019-2022 @subwallet/extension-web-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getYieldAvailableActionsByPosition, getYieldAvailableActionsByType, YieldAction } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; -import { YieldPoolInfo, YieldPoolType } from '@subwallet/extension-base/types'; +import { getYieldAvailableActionsByPosition, getYieldAvailableActionsByType } from '@subwallet/extension-base/services/earning-service/utils'; +import { YieldAction, YieldPoolInfo, YieldPoolType } from '@subwallet/extension-base/types'; import { BN_TEN } from '@subwallet/extension-base/utils'; import { MetaInfo } from '@subwallet/extension-web-ui/components'; import EarningTypeTag from '@subwallet/extension-web-ui/components/Earning/EarningTypeTag'; diff --git a/packages/extension-web-ui/src/components/Field/Earning/EarningPoolSelector.tsx b/packages/extension-web-ui/src/components/Field/Earning/EarningPoolSelector.tsx index 302c443f435..c5959c85921 100644 --- a/packages/extension-web-ui/src/components/Field/Earning/EarningPoolSelector.tsx +++ b/packages/extension-web-ui/src/components/Field/Earning/EarningPoolSelector.tsx @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-web-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { YieldPoolType } from '@subwallet/extension-base/types'; import { fetchStaticData } from '@subwallet/extension-base/utils'; import { BaseSelectModal, StakingPoolItem } from '@subwallet/extension-web-ui/components'; diff --git a/packages/extension-web-ui/src/components/Field/Earning/EarningValidatorSelector.tsx b/packages/extension-web-ui/src/components/Field/Earning/EarningValidatorSelector.tsx index 83e1fd1151a..f992721a022 100644 --- a/packages/extension-web-ui/src/components/Field/Earning/EarningValidatorSelector.tsx +++ b/packages/extension-web-ui/src/components/Field/Earning/EarningValidatorSelector.tsx @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { ChainRecommendValidator } from '@subwallet/extension-base/constants'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { YieldPoolType } from '@subwallet/extension-base/types'; import { detectTranslate, fetchStaticData } from '@subwallet/extension-base/utils'; import { BaseModal, SelectValidatorInput, StakingValidatorItem } from '@subwallet/extension-web-ui/components'; diff --git a/packages/extension-web-ui/src/components/Field/NominationSelector.tsx b/packages/extension-web-ui/src/components/Field/NominationSelector.tsx index 432a2e5140d..9cb423290c1 100644 --- a/packages/extension-web-ui/src/components/Field/NominationSelector.tsx +++ b/packages/extension-web-ui/src/components/Field/NominationSelector.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { NominationInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { StakingNominationItem } from '@subwallet/extension-web-ui/components'; import { Avatar } from '@subwallet/extension-web-ui/components/Avatar'; import { BasicInputWrapper } from '@subwallet/extension-web-ui/components/Field/Base'; diff --git a/packages/extension-web-ui/src/components/Modal/Earning/EarningInstructionModal.tsx b/packages/extension-web-ui/src/components/Modal/Earning/EarningInstructionModal.tsx index e5cb88e8d7e..a4e02934106 100644 --- a/packages/extension-web-ui/src/components/Modal/Earning/EarningInstructionModal.tsx +++ b/packages/extension-web-ui/src/components/Modal/Earning/EarningInstructionModal.tsx @@ -3,9 +3,8 @@ import { _ChainAsset } from '@subwallet/chain-list/types'; import { NotificationType } from '@subwallet/extension-base/background/KoniTypes'; -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { calculateReward } from '@subwallet/extension-base/services/earning-service/utils'; +import { calculateReward, getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { YieldPoolInfo, YieldPoolType } from '@subwallet/extension-base/types'; import { balanceFormatter, detectTranslate, formatNumber } from '@subwallet/extension-base/utils'; import { BaseModal, InstructionItem } from '@subwallet/extension-web-ui/components'; diff --git a/packages/extension-web-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx b/packages/extension-web-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx index 0b2df02a4ce..d6920d1047c 100644 --- a/packages/extension-web-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx +++ b/packages/extension-web-ui/src/components/Modal/Earning/EarningValidatorDetailModal.tsx @@ -1,8 +1,8 @@ // Copyright 2019-2022 @subwallet/extension-web-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { BaseModal, MetaInfo } from '@subwallet/extension-web-ui/components'; import { VALIDATOR_DETAIL_MODAL } from '@subwallet/extension-web-ui/constants'; import { useGetChainPrefixBySlug } from '@subwallet/extension-web-ui/hooks'; diff --git a/packages/extension-web-ui/src/components/SelectValidatorInput.tsx b/packages/extension-web-ui/src/components/SelectValidatorInput.tsx index b9bb2043dcb..3c2f8f221f8 100644 --- a/packages/extension-web-ui/src/components/SelectValidatorInput.tsx +++ b/packages/extension-web-ui/src/components/SelectValidatorInput.tsx @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-web-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import AvatarGroup, { BaseAccountInfo } from '@subwallet/extension-web-ui/components/Account/Info/AvatarGroup'; import { ThemeProps } from '@subwallet/extension-web-ui/types'; import { toShort } from '@subwallet/extension-web-ui/utils'; diff --git a/packages/extension-web-ui/src/hooks/modal/useSelectValidators.ts b/packages/extension-web-ui/src/hooks/modal/useSelectValidators.ts index 397628dcc53..4255e1febad 100644 --- a/packages/extension-web-ui/src/hooks/modal/useSelectValidators.ts +++ b/packages/extension-web-ui/src/hooks/modal/useSelectValidators.ts @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-web-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; +import { getValidatorLabel } from '@subwallet/extension-base/services/earning-service/utils'; import { detectTranslate } from '@subwallet/extension-base/utils'; import { BasicOnChangeFunction } from '@subwallet/extension-web-ui/components/Field/Base'; import { useNotification, useTranslation } from '@subwallet/extension-web-ui/hooks/common'; diff --git a/packages/extension-web-ui/src/messaging/transaction/index.ts b/packages/extension-web-ui/src/messaging/transaction/index.ts index b92340e8975..f7290fc40e2 100644 --- a/packages/extension-web-ui/src/messaging/transaction/index.ts +++ b/packages/extension-web-ui/src/messaging/transaction/index.ts @@ -3,6 +3,5 @@ export * from './base'; export * from './nft'; -export * from './staking'; export * from './transfer'; export * from './earning'; diff --git a/packages/extension-web-ui/src/messaging/transaction/staking.ts b/packages/extension-web-ui/src/messaging/transaction/staking.ts deleted file mode 100644 index 25118082acd..00000000000 --- a/packages/extension-web-ui/src/messaging/transaction/staking.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-web-ui authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ChainStakingMetadata, NominatorMetadata, RequestBondingSubmit, RequestStakePoolingUnbonding, RequestSubscribeStaking, RequestSubscribeStakingReward, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, StakingJson, StakingRewardJson, StakingType, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; -import { SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; -import { NominationPoolInfo, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestYieldStepSubmit } from '@subwallet/extension-base/types'; - -import { sendMessage } from '../base'; - -export async function submitPoolBonding (request: RequestYieldStepSubmit): Promise { - return sendMessage('pri(bonding.nominationPool.submitBonding)', request); -} - -export async function submitPoolUnbonding (request: RequestStakePoolingUnbonding): Promise { - return sendMessage('pri(bonding.nominationPool.submitUnbonding)', request); -} - -export async function submitBonding (request: RequestBondingSubmit): Promise { - return sendMessage('pri(bonding.submitBondingTransaction)', request); -} - -export async function submitUnbonding (request: RequestUnbondingSubmit): Promise { - return sendMessage('pri(unbonding.submitTransaction)', request); -} - -export async function submitStakeClaimReward (request: RequestStakeClaimReward): Promise { - return sendMessage('pri(staking.submitClaimReward)', request); -} - -export async function submitStakeCancelWithdrawal (request: RequestStakeCancelWithdrawal): Promise { - return sendMessage('pri(staking.submitCancelWithdrawal)', request); -} - -export async function submitTuringStakeCompounding (request: RequestTuringStakeCompound): Promise { - return sendMessage('pri(staking.submitTuringCompound)', request); -} - -export async function submitTuringCancelStakeCompounding (request: RequestTuringCancelStakeCompound): Promise { - return sendMessage('pri(staking.submitTuringCancelCompound)', request); -} - -export async function getBondingOptions (networkKey: string, type: StakingType): Promise { - return sendMessage('pri(bonding.getBondingOptions)', { chain: networkKey, type }); -} - -export async function getNominationPoolOptions (chain: string): Promise { - return sendMessage('pri(bonding.getNominationPoolOptions)', chain); -} - -export async function subscribeChainStakingMetadata (callback: (data: ChainStakingMetadata[]) => void): Promise { - return sendMessage('pri(bonding.subscribeChainStakingMetadata)', null, callback); -} - -export async function subscribeStakingNominatorMetadata (callback: (data: NominatorMetadata[]) => void): Promise { - return sendMessage('pri(bonding.subscribeNominatorMetadata)', null, callback); -} - -export async function getStaking (account: string): Promise { - // @ts-ignore - return sendMessage('pri(staking.getStaking)', account); -} - -export async function subscribeStaking (request: RequestSubscribeStaking, callback: (stakingData: StakingJson) => void): Promise { - return sendMessage('pri(staking.getSubscription)', request, callback); -} - -export async function getStakingReward (): Promise { - return sendMessage('pri(stakingReward.getStakingReward)'); -} - -export async function subscribeStakingReward (request: RequestSubscribeStakingReward, callback: (stakingRewardData: StakingRewardJson) => void): Promise { - return sendMessage('pri(stakingReward.getSubscription)', request, callback); -}