From a8d4d9955a8910cf6dafc3962b92a7d2f87f7d38 Mon Sep 17 00:00:00 2001 From: S2kael Date: Tue, 20 May 2025 12:25:45 +0700 Subject: [PATCH 1/2] [Issue-4187] Feat: Add logic to handle staking on asset hub --- .../src/services/chain-service/utils/index.ts | 8 +++ .../handlers/native-staking/base.ts | 4 ++ .../handlers/nomination-pool/index.ts | 7 ++- .../src/services/earning-service/service.ts | 54 ++++++++++++++++--- .../src/utils/staticData/assetHubStaking.json | 1 + .../src/utils/staticData/index.ts | 6 ++- 6 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 packages/extension-base/src/utils/staticData/assetHubStaking.json diff --git a/packages/extension-base/src/services/chain-service/utils/index.ts b/packages/extension-base/src/services/chain-service/utils/index.ts index be76faf0c8b..a3749258270 100644 --- a/packages/extension-base/src/services/chain-service/utils/index.ts +++ b/packages/extension-base/src/services/chain-service/utils/index.ts @@ -348,6 +348,14 @@ export function _getChainNativeTokenSlug (chainInfo: _ChainInfo) { return `${chainInfo.slug}-${_AssetType.NATIVE}-${_getChainNativeTokenBasicInfo(chainInfo).symbol}`; } +export function _getChainSubstrateTokenSymbol (chainInfo: _ChainInfo) { + if (chainInfo.substrateInfo) { + return chainInfo.substrateInfo.symbol || ''; + } else { + return ''; + } +} + export function _isLocalToken (tokenInfo: _ChainAsset) { return tokenInfo.assetType === _AssetType.LOCAL; } diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/base.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/base.ts index 3b868b58417..c14907e97dd 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/base.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/base.ts @@ -24,6 +24,10 @@ export default abstract class BaseNativeStakingPoolHandler extends BasePoolHandl claimReward: false }; + static generateSlug (symbol: string, chain: string): string { + return `${symbol}___native_staking___${chain}`; + } + constructor (state: KoniState, chain: string) { super(state, chain); 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 d21260f0476..b10a7a43fe4 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 @@ -36,6 +36,10 @@ export default class NominationPoolHandler extends BasePoolHandler { claimReward: true }; + static generateSlug (symbol: string, chain: string): string { + return `${symbol}___nomination_pool___${chain}`; + } + constructor (state: KoniState, chain: string) { super(state, chain); @@ -108,7 +112,8 @@ export default class NominationPoolHandler extends BasePoolHandler { const unlockingEras = substrateApi.api.consts.staking.bondingDuration.toString(); const maxSupportedEras = substrateApi.api.consts.staking.historyDepth.toString(); - const erasPerDay = 24 / _STAKING_ERA_LENGTH_MAP[chainInfo.slug]; // Can be exactly calculate from epochDuration, blockTime, sessionsPerEra + const eraInHours = _STAKING_ERA_LENGTH_MAP[chainInfo.slug] || _STAKING_ERA_LENGTH_MAP.default; // in hours + const erasPerDay = 24 / eraInHours; // Can be exactly calculate from epochDuration, blockTime, sessionsPerEra const supportedDays = getSupportedDaysByHistoryDepth(erasPerDay, parseInt(maxSupportedEras), parseInt(currentEra) / erasPerDay); diff --git a/packages/extension-base/src/services/earning-service/service.ts b/packages/extension-base/src/services/earning-service/service.ts index e400f93bf25..9e70b307697 100644 --- a/packages/extension-base/src/services/earning-service/service.ts +++ b/packages/extension-base/src/services/earning-service/service.ts @@ -6,7 +6,7 @@ import { ChainType, ExtrinsicType } from '@subwallet/extension-base/background/K import { CRON_REFRESH_CHAIN_STAKING_METADATA, CRON_REFRESH_EARNING_REWARD_HISTORY_INTERVAL, CRON_REFRESH_STAKING_REWARD_FAST_INTERVAL } from '@subwallet/extension-base/constants'; import KoniState from '@subwallet/extension-base/koni/background/handlers/State'; import { PersistDataServiceInterface, ServiceStatus, StoppableServiceInterface } from '@subwallet/extension-base/services/base/types'; -import { _isChainEnabled, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; +import { _getChainSubstrateTokenSymbol, _isChainEnabled, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; import BaseLiquidStakingPoolHandler from '@subwallet/extension-base/services/earning-service/handlers/liquid-staking/base'; import MythosNativeStakingPoolHandler from '@subwallet/extension-base/services/earning-service/handlers/native-staking/mythos'; @@ -14,7 +14,7 @@ import { EventService } from '@subwallet/extension-base/services/event-service'; import DatabaseService from '@subwallet/extension-base/services/storage-service/DatabaseService'; import { SWTransactionBase } from '@subwallet/extension-base/services/transaction-service/types'; import { BasicTxErrorType, EarningRewardHistoryItem, EarningRewardItem, EarningRewardJson, HandleYieldStepData, HandleYieldStepParams, OptimalYieldPath, OptimalYieldPathParams, RequestEarlyValidateYield, RequestEarningSlippage, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestYieldLeave, RequestYieldWithdrawal, ResponseEarlyValidateYield, TransactionData, ValidateYieldProcessParams, YieldPoolInfo, YieldPoolTarget, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; -import { addLazy, createPromiseHandler, getAddressesByChainType, PromiseHandler, removeLazy } from '@subwallet/extension-base/utils'; +import { addLazy, createPromiseHandler, fetchStaticData, getAddressesByChainType, PromiseHandler, removeLazy } from '@subwallet/extension-base/utils'; import { fetchStaticCache } from '@subwallet/extension-base/utils/fetchStaticCache'; import { BehaviorSubject } from 'rxjs'; @@ -27,10 +27,15 @@ const fetchPoolsData = async () => { return fetchData.data; }; +const fetchAhMapChain = async () => { + return await fetchStaticData>('asset-hub-staking-map'); +}; + export default class EarningService implements StoppableServiceInterface, PersistDataServiceInterface { protected readonly state: KoniState; protected handlers: Record = {}; private handlerCache: Map = new Map(); + private inactivePoolSlug: Set = new Set(); private earningRewardSubject: BehaviorSubject = new BehaviorSubject({ ready: false, data: {} }); private earningRewardHistorySubject: BehaviorSubject> = new BehaviorSubject>({}); @@ -44,6 +49,7 @@ export default class EarningService implements StoppableServiceInterface, Persis private dbService: DatabaseService; private eventService: EventService; private useOnlineCacheOnly = true; + private inactivePoolReady: PromiseHandler = createPromiseHandler(); constructor (state: KoniState) { this.state = state; @@ -66,12 +72,25 @@ export default class EarningService implements StoppableServiceInterface, Persis } const minAmountPercent: Record = {}; + const ahMapChain = await fetchAhMapChain(); for (const chain of chains) { const handlers: BasePoolHandler[] = []; + const chainInfo = this.state.getChainInfo(chain); + const symbol = _getChainSubstrateTokenSymbol(chainInfo); if (_STAKING_CHAIN_GROUP.relay.includes(chain)) { - handlers.push(new RelayNativeStakingPoolHandler(this.state, chain)); + const ahChain = ahMapChain[chain]; + + if (ahChain) { + handlers.push(new RelayNativeStakingPoolHandler(this.state, ahChain)); + + const relaySlug = RelayNativeStakingPoolHandler.generateSlug(symbol, chain); + + this.inactivePoolSlug.add(relaySlug); + } else { + handlers.push(new RelayNativeStakingPoolHandler(this.state, chain)); + } } if (_STAKING_CHAIN_GROUP.para.includes(chain)) { @@ -98,7 +117,17 @@ export default class EarningService implements StoppableServiceInterface, Persis } if (_STAKING_CHAIN_GROUP.nominationPool.includes(chain)) { - handlers.push(new NominationPoolHandler(this.state, chain)); + const ahChain = ahMapChain[chain]; + + if (ahChain) { + handlers.push(new NominationPoolHandler(this.state, ahChain)); + + const relaySlug = NominationPoolHandler.generateSlug(symbol, chain); + + this.inactivePoolSlug.add(relaySlug); + } else { + handlers.push(new NominationPoolHandler(this.state, chain)); + } } if (_STAKING_CHAIN_GROUP.liquidStaking.includes(chain)) { @@ -140,6 +169,7 @@ export default class EarningService implements StoppableServiceInterface, Persis minAmountPercent.default = BaseLiquidStakingPoolHandler.defaultMinAmountPercent; this.minAmountPercentSubject.next(minAmountPercent); + this.inactivePoolReady.resolve(); // Emit earning ready this.eventService.emit('earning.ready', true); @@ -162,7 +192,7 @@ export default class EarningService implements StoppableServiceInterface, Persis next: (data) => { const activeMap = this.state.getActiveChainInfoMap(); const activePositions = Object.values(data).filter((item) => { - return !!activeMap[item.chain]; + return !!activeMap[item.chain] && !this.inactivePoolSlug.has(item.slug); }); this.yieldPositionListSubject.next(Object.values(activePositions)); @@ -401,7 +431,9 @@ export default class EarningService implements StoppableServiceInterface, Persis const existedYieldPoolInfo = await this.dbService.getYieldPools(); existedYieldPoolInfo.forEach((info) => { - yieldPoolInfo[info.slug] = info; + if (!this.inactivePoolSlug.has(info.slug)) { + yieldPoolInfo[info.slug] = info; + } }); this.yieldPoolInfoSubject.next(yieldPoolInfo); @@ -447,6 +479,12 @@ export default class EarningService implements StoppableServiceInterface, Persis private async fetchingPoolsInfoOnline () { const onlineData = await fetchPoolsData(); + await this.inactivePoolReady.promise; + + for (const inactiveSlug of this.inactivePoolSlug) { + delete onlineData[inactiveSlug]; + } + Object.values(onlineData).forEach((item) => { this.updateYieldPoolInfo(item); }); @@ -566,7 +604,9 @@ export default class EarningService implements StoppableServiceInterface, Persis const yieldPositionInfo = this.yieldPositionSubject.getValue(); existedYieldPosition.forEach((item) => { - yieldPositionInfo[this._getYieldPositionKey(item.slug, item.address)] = item; + if (!this.inactivePoolSlug.has(item.slug)) { + yieldPositionInfo[this._getYieldPositionKey(item.slug, item.address)] = item; + } }); this.yieldPositionSubject.next(yieldPositionInfo); diff --git a/packages/extension-base/src/utils/staticData/assetHubStaking.json b/packages/extension-base/src/utils/staticData/assetHubStaking.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/extension-base/src/utils/staticData/assetHubStaking.json @@ -0,0 +1 @@ +{} diff --git a/packages/extension-base/src/utils/staticData/index.ts b/packages/extension-base/src/utils/staticData/index.ts index da828a95207..64cb2a98399 100644 --- a/packages/extension-base/src/utils/staticData/index.ts +++ b/packages/extension-base/src/utils/staticData/index.ts @@ -28,6 +28,8 @@ export const blockedActions: Record = require('./blockedActio export const oldChainPrefix: Record = require('./oldChainPrefix.json'); // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment export const paraSpellChainMap: Record = require('./paraSpellChainMap.json'); +// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment +export const assetHubStakingMap: Record = require('./assetHubStaking.json'); export enum StaticKey { BUY_SERVICE_INFOS = 'buy-service-infos', @@ -42,6 +44,7 @@ export enum StaticKey { BLOCKED_ACTIONS = 'blocked-actions', OLD_CHAIN_PREFIX = 'old-chain-prefix', PARASPELL_CHAIN_MAP= 'paraspell-chain-map', + ASSET_HUB_STAKING_MAP= 'asset-hub-staking-map', } export const staticData = { @@ -56,5 +59,6 @@ export const staticData = { [StaticKey.REMIND_NOTIFICATION_TIME]: remindNotificationTime, [StaticKey.BLOCKED_ACTIONS]: blockedActions, [StaticKey.OLD_CHAIN_PREFIX]: oldChainPrefix, - [StaticKey.PARASPELL_CHAIN_MAP]: paraSpellChainMap + [StaticKey.PARASPELL_CHAIN_MAP]: paraSpellChainMap, + [StaticKey.ASSET_HUB_STAKING_MAP]: assetHubStakingMap }; From 2df773b5a69b7bae702a26f7e22fcbd2583f74ec Mon Sep 17 00:00:00 2001 From: S2kael Date: Fri, 23 May 2025 12:23:57 +0700 Subject: [PATCH 2/2] [Issue-4187] Feat: Update logic withdraw --- .../handlers/native-staking/relay-chain.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 8d4951e05b1..b4c62d1d9e2 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 @@ -796,8 +796,18 @@ export default class RelayNativeStakingPoolHandler extends BaseNativeStakingPool const chainApi = await this.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'; + let slashingSpanCount: string | number; + + if (chainApi.api.query.staking.nominatorSlashInEra) { + const currentEra = await chainApi.api.query.staking.currentEra(); + const slashingSpans = (await chainApi.api.query.staking.nominatorSlashInEra(currentEra.toPrimitive(), address)).toPrimitive() as number; + + slashingSpanCount = slashingSpans !== null ? slashingSpans.toString() : '0'; + } else { + const _slashingSpans = (await chainApi.api.query.staking.slashingSpans(address)).toHuman() as Record; + + slashingSpanCount = _slashingSpans !== null ? _slashingSpans.spanIndex as string : '0'; + } return chainApi.api.tx.staking.withdrawUnbonded(slashingSpanCount); } else {