Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { getPSP22ContractPromise } from '@subwallet/extension-base/koni/api/cont
import { getDefaultWeightV2 } from '@subwallet/extension-base/koni/api/contract-handler/wasm/utils';
import { _BALANCE_CHAIN_GROUP, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX, USE_MULTILOCATION_INDEX } from '@subwallet/extension-base/services/chain-service/constants';
import { _EvmApi, _SubstrateAdapterSubscriptionArgs, _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types';
import { _checkSmartContractSupportByChain, _getAssetExistentialDeposit, _getAssetNetuid, _getChainExistentialDeposit, _getChainNativeTokenSlug, _getContractAddressOfToken, _getTokenOnChainAssetId, _getTokenOnChainInfo, _getTokenTypesSupportedByChain, _getXcmAssetMultilocation, _isBridgedToken, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils';
import { _checkSmartContractSupportByChain, _getAssetDecimals, _getAssetExistentialDeposit, _getAssetNetuid, _getChainExistentialDeposit, _getChainNativeTokenSlug, _getContractAddressOfToken, _getTokenOnChainAssetId, _getTokenOnChainInfo, _getTokenTypesSupportedByChain, _getXcmAssetMultilocation, _isBridgedToken, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils';
import { TaoStakeInfo } from '@subwallet/extension-base/services/earning-service/handlers/native-staking/tao';
import { BalanceItem, SubscribeBasePalletBalance, SubscribeSubstratePalletBalance } from '@subwallet/extension-base/types';
import { filterAlphaAssetsByChain, filterAssetsByChainAndType } from '@subwallet/extension-base/utils';
Expand All @@ -28,7 +28,7 @@ import { ContractPromise } from '@polkadot/api-contract';
import { subscribeERC20Interval } from '../evm';
import { subscribeEquilibriumTokenBalance } from './equilibrium';
import { subscribeGRC20Balance, subscribeVftBalance } from './gear';
import { buildLockedDetails, getSpecialStakingBalances } from './utils';
import { buildLockedDetails, getSpecialStakingBalancesWithDetails } from './utils';

export const subscribeSubstrateBalance = async (addresses: string[], chainInfo: _ChainInfo, assetMap: Record<string, _ChainAsset>, substrateApi: _SubstrateApi, evmApi: _EvmApi, callback: (rs: BalanceItem[]) => void, extrinsicType?: ExtrinsicType) => {
let unsubNativeToken: () => void;
Expand Down Expand Up @@ -124,7 +124,7 @@ export const subscribeSubstrateBalance = async (addresses: string[], chainInfo:
};

// eslint-disable-next-line @typescript-eslint/require-await
const subscribeWithSystemAccountPallet = async ({ addresses, callback, chainInfo, extrinsicType, substrateApi }: SubscribeSubstratePalletBalance) => {
const subscribeWithSystemAccountPallet = async ({ addresses, assetMap, callback, chainInfo, extrinsicType, substrateApi }: SubscribeSubstratePalletBalance) => {
const systemAccountKey = 'query_system_account';
const poolMembersKey = 'query_nominationPools_poolMembers';

Expand Down Expand Up @@ -154,7 +154,15 @@ const subscribeWithSystemAccountPallet = async ({ addresses, callback, chainInfo
const balances = rs[systemAccountKey];
const poolMemberInfos = rs[poolMembersKey];

const bittensorStakingBalances = await getSpecialStakingBalances(chainInfo, addresses, substrateApi);
const nativeTokenSlug = _getChainNativeTokenSlug(chainInfo);
const nativeTokenInfo = assetMap[nativeTokenSlug];

const bittensorStakingDetails = await getSpecialStakingBalancesWithDetails(
chainInfo,
addresses,
substrateApi,
_getAssetDecimals(nativeTokenInfo)
);

// Precompute totalLockedFromTransfer for each account to decide if need fetch locks/holds
const preItems = balances.map((_balance, index) => {
Expand All @@ -170,7 +178,7 @@ const subscribeWithSystemAccountPallet = async ({ addresses, callback, chainInfo
totalLockedFromTransfer += nominationPoolBalance;
}

totalLockedFromTransfer += BigInt(bittensorStakingBalances[index].toString());
totalLockedFromTransfer += BigInt(bittensorStakingDetails[index].native.toString());

return { index, totalLockedFromTransfer, balanceInfo };
});
Expand Down Expand Up @@ -221,15 +229,18 @@ const subscribeWithSystemAccountPallet = async ({ addresses, callback, chainInfo

const allLockEntries = [...lockItems, ...holdItems, ...freezeItems];

const transferableBalance = _getSystemPalletTransferable(balanceInfo, _getChainExistentialDeposit(chainInfo), extrinsicType);

const stakingDetails = bittensorStakingDetails[index];

const lockedDetails = buildLockedDetails(
allLockEntries,
totalLockedFromTransfer,
_getSystemPalletReservedBalance(balanceInfo),
bittensorStakingBalances[index]
stakingDetails?.native,
stakingDetails?.total
);

const transferableBalance = _getSystemPalletTransferable(balanceInfo, _getChainExistentialDeposit(chainInfo), extrinsicType);

return {
address: addresses[index],
tokenSlug: _getChainNativeTokenSlug(chainInfo),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,10 @@ import { FrameBalancesFreezesInfo, FrameBalancesHoldsInfo, FrameBalancesLocksInf
import { _BALANCE_CHAIN_GROUP, _BALANCE_LOCKED_ID_GROUP } from '@subwallet/extension-base/services/chain-service/constants';
import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types';
import { TaoStakeInfo } from '@subwallet/extension-base/services/earning-service/handlers/native-staking/tao';
import { getAlphaToTaoRate } from '@subwallet/extension-base/services/earning-service/utils/alpha-price';
import { LockedBalanceDetails } from '@subwallet/extension-base/types';
import BigN from 'bignumber.js';

export async function getSpecialStakingBalances (chainInfo: _ChainInfo, addresses: string[], substrateApi: _SubstrateApi): Promise<BigN[]> {
// Default: 0 for all addresses
let balances = new Array<BigN>(addresses.length).fill(new BigN(0));

// --- Bittensor ----------------------------------------------------------------
if (_BALANCE_CHAIN_GROUP.bittensor.includes(chainInfo.slug)) {
const rawData = await substrateApi.api.call.stakeInfoRuntimeApi.getStakeInfoForColdkeys(addresses);
const values: Array<[string, TaoStakeInfo[]]> = rawData.toPrimitive() as Array<[string, TaoStakeInfo[]]>;

balances = values.map(([, stakes]) =>
stakes
.filter((i) => i.netuid === 0)
.reduce((prev, curr) => prev.plus(curr.stake), BigN(0))
);

return balances;
}

return balances;
}

// handler according to different logic
const extractId = (id: string | Record<string, unknown> | undefined): string => {
if (!id) {
Expand All @@ -45,7 +25,7 @@ const extractId = (id: string | Record<string, unknown> | undefined): string =>
return keys.length ? keys[0] : '';
};

export function buildLockedDetails (item: (FrameBalancesLocksInfo | FrameBalancesHoldsInfo | FrameBalancesFreezesInfo)[], totalLockedFromTransfer: bigint, reserved: bigint, externalStaking?: BigN): LockedBalanceDetails {
export function buildLockedDetails (item: (FrameBalancesLocksInfo | FrameBalancesHoldsInfo | FrameBalancesFreezesInfo)[], totalLockedFromTransfer: bigint, reserved: bigint, externalStaking?: BigN, totalStakingEquivalent?: BigN): LockedBalanceDetails {
let stakingBalance = externalStaking || new BigN(0);
let govBalance = new BigN(0);
let democracyBalance = new BigN(0);
Expand Down Expand Up @@ -73,6 +53,75 @@ export function buildLockedDetails (item: (FrameBalancesLocksInfo | FrameBalance
governance: govBalance.toFixed(),
democracy: democracyBalance.toFixed(),
reserved: reservedBN.toFixed(),
others: others.gt(0) ? others.toFixed() : '0'
others: others.gt(0) ? others.toFixed() : '0',
totalStakingEquivalent: totalStakingEquivalent?.toFixed(0) || undefined
};
}

export type SpecialStakingBalance = {
/** Total staking value in native token (TAO) */
total: BigN;

/** Native TAO stake (netuid = 0) */
native: BigN;

/** Alpha stake converted to native TAO */
alphaConverted: BigN;
};

export async function getSpecialStakingBalancesWithDetails (chainInfo: _ChainInfo, addresses: string[], substrateApi: _SubstrateApi, nativeDecimals: number): Promise<SpecialStakingBalance[]> {
const result: SpecialStakingBalance[] = addresses.map(() => ({
total: new BigN(0),
native: new BigN(0),
alphaConverted: new BigN(0)
}));

// Only apply for Bittensor
if (!_BALANCE_CHAIN_GROUP.bittensor.includes(chainInfo.slug)) {
return result;
}

const api = await substrateApi.isReady;

const rawData =
await api.api.call.stakeInfoRuntimeApi.getStakeInfoForColdkeys(addresses);

const values = rawData.toPrimitive() as Array<[string, TaoStakeInfo[]]>;

for (let i = 0; i < values.length; i++) {
const [, stakes] = values[i];
const alphaByNetuid = new Map<number, BigN>();

// Separate native & alpha
for (const stake of stakes) {
const amount = new BigN(stake.stake);

if (stake.netuid === 0) {
result[i].native = result[i].native.plus(amount);
} else {
const prev = alphaByNetuid.get(stake.netuid) || new BigN(0);

alphaByNetuid.set(stake.netuid, prev.plus(amount));
}
}

// Convert alpha → native
for (const [netuid, totalAlpha] of alphaByNetuid.entries()) {
try {
const rate = new BigN((await getAlphaToTaoRate(substrateApi, chainInfo.slug, netuid, nativeDecimals)).toString());
const taoEquivalent = totalAlpha.multipliedBy(rate);

result[i].alphaConverted =
result[i].alphaConverted.plus(taoEquivalent);
} catch (e) {
console.warn(`Failed to convert alpha for netuid ${netuid}`, e);
}
}

// Final total
result[i].total =
result[i].native.plus(result[i].alphaConverted);
}

return result;
}
9 changes: 9 additions & 0 deletions packages/extension-base/src/services/balance-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,15 @@ export class BalanceService implements StoppableServiceInterface {
}

break;

case BalanceType.TOTAL_STAKE_EQUIVALENT:
value = rs.lockedDetails?.totalStakingEquivalent || '0';
break;

case BalanceType.TOTAL_EQUIVALENT:
value = new BigN(rs.lockedDetails?.totalStakingEquivalent || 0).plus(new BigN(rs.free)).toFixed();
break;

default:
value = rs.free;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright 2019-2022 @subwallet/extension-base
// SPDX-License-Identifier: Apache-2.0

export { default as TaoDelegateStakingPoolHandler } from './tao';
Loading
Loading