Skip to content

Commit

Permalink
[Issue-3984] feat: support mythos staking
Browse files Browse the repository at this point in the history
  • Loading branch information
bluezdot committed Jan 10, 2025
1 parent 153b100 commit 4d73cb5
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 8 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"@polkadot/types-support": "^15.0.1",
"@polkadot/util": "^13.2.3",
"@polkadot/util-crypto": "^13.2.3",
"@subwallet/chain-list": "0.2.97",
"@subwallet/chain-list": "/Users/truongnguyen/Workspace/SubWallet-ChainList/packages/chain-list/build",
"@subwallet/keyring": "^0.1.8-beta.0",
"@subwallet/react-ui": "5.1.2-b79",
"@subwallet/ui-keyring": "0.1.8-beta.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ export const _STAKING_ERA_LENGTH_MAP: Record<string, number> = { // in hours
enjin_relaychain: 24,
availTuringTest: 24,
polkadex: 24,
avail_mainnet: 24
avail_mainnet: 24,
muse_testnet: 0.04
};

export const _EXPECTED_BLOCK_TIME: Record<string, number> = { // in seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export const _STAKING_CHAIN_GROUP = {
lending: ['interlay'],
krest_network: ['krest_network'],
manta: ['manta_network'],
bittensor: ['bittensor', 'bittensor_devnet']
bittensor: ['bittensor', 'bittensor_devnet'],
mythos: ['muse_testnet']
};

export const TON_CHAINS = ['ton', 'ton_testnet'];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// Copyright 2019-2022 @subwallet/extension-base
// SPDX-License-Identifier: Apache-2.0

import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
import { _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants';
import BaseParaStakingPoolHandler from '@subwallet/extension-base/services/earning-service/handlers/native-staking/base-para';
import { BasicTxErrorType, EarningStatus, NativeYieldPoolInfo, SubmitJoinNativeStaking, TransactionData, UnstakingInfo, ValidatorInfo, YieldPoolInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types';
import { balanceFormatter, formatNumber, reformatAddress } from '@subwallet/extension-base/utils';

import { SubmittableExtrinsic } from '@polkadot/api/types';
import { UnsubscribePromise } from '@polkadot/api-base/types/base';
import { Codec } from '@polkadot/types/types';

interface FrameSupportTokensMiscIdAmountRuntimeFreezeReason {
id: {
CollatorStaking: string
},
amount: string
}

export default class MythosNativeStakingPoolHandler extends BaseParaStakingPoolHandler {
/* Subscribe pool info */

async subscribePoolInfo (callback: (data: YieldPoolInfo) => void): Promise<VoidFunction> {
let cancel = false;
const chainApi = this.substrateApi;
const nativeToken = this.nativeToken;

const defaultCallback = async () => {
const data: NativeYieldPoolInfo = {
...this.baseInfo,
type: this.type,
metadata: {
...this.metadataInfo,
description: this.getDescription()
}
};

const poolInfo = await this.getPoolInfo();

!poolInfo && callback(data);
};

if (!this.isActive) {
await defaultCallback();

return () => {
cancel = true;
};
}

await defaultCallback();

await chainApi.isReady;

const unsub = await (chainApi.api.query.collatorStaking.currentSession(async (_session: Codec) => {
if (cancel) {
unsub();

return;
}

const currentSession = _session.toString();
const maxStakers = chainApi.api.consts.collatorStaking.maxStakers.toString();
const unstakeDelay = chainApi.api.consts.collatorStaking.stakeUnlockDelay.toString();
const maxStakedCandidates = chainApi.api.consts.collatorStaking.maxStakedCandidates.toString();
const sessionTime = _STAKING_ERA_LENGTH_MAP[this.chain] || _STAKING_ERA_LENGTH_MAP.default; // in hours
const unstakingPeriod = parseInt(unstakeDelay) * sessionTime;

const _minStake = await Promise.all([
chainApi.api.query.collatorStaking.minStake()
]);

const minStake = _minStake.toString();
const minStakeToHuman = formatNumber(minStake, nativeToken.decimals || 0, balanceFormatter);

const data: NativeYieldPoolInfo = {
...this.baseInfo,
type: this.type,
metadata: {
...this.metadataInfo,
description: this.getDescription(minStakeToHuman)
},
statistic: {
assetEarning: [
{
slug: this.nativeToken.slug
}
],
maxCandidatePerFarmer: parseInt(maxStakedCandidates),
maxWithdrawalRequestPerFarmer: 1, // todo: recheck
earningThreshold: {
join: minStake,
defaultUnstake: '0',
fastUnstake: '0'
},
era: parseInt(currentSession),
eraTime: sessionTime,
unstakingPeriod: unstakingPeriod
// tvl: totalStake.toString(), // todo
// inflation
},
maxPoolMembers: parseInt(maxStakers)
};

callback(data);
}) as unknown as UnsubscribePromise);

return () => {
cancel = true;
unsub();
};
}

/* Subscribe pool info */

/* Subscribe pool position */

async subscribePoolPosition (useAddresses: string[], resultCallback: (rs: YieldPositionInfo) => void): Promise<VoidFunction> {
let cancel = false;
const substrateApi = this.substrateApi;
const defaultInfo = this.baseInfo;
const unsub = await substrateApi.api.query.collatorStaking.candidateStake.multi(useAddresses, async (ledgers: Codec[]) => {
if (cancel) {
unsub();

return;
}

if (ledgers) {
await Promise.all(ledgers.map(async (candidateStake, i) => {
const owner = reformatAddress(useAddresses[i], 42);

resultCallback({ // todo: handle this
...defaultInfo,
type: this.type,
address: owner,
balanceToken: this.nativeToken.slug,
totalStake: '0',
activeStake: '0',
unstakeBalance: '0',
status: EarningStatus.NOT_STAKING,
isBondedBefore: false,
nominations: [],
unstakings: []
});
}));
}
});

return () => {
cancel = true;
unsub();
};
}

async parseCollatorMetadata () {
// todo
// collatorStaking.candidateStake
// collatorStaking.userStake
}

/* Subscribe pool position */

/* Get pool targets */

async getPoolTargets (): Promise<ValidatorInfo[]> {
// todo
// collatorStaking.candidates

return [] as ValidatorInfo[];
}

/* Get pool targets */

/* Join pool action */

async createJoinExtrinsic (data: SubmitJoinNativeStaking, positionInfo?: YieldPositionInfo): Promise<[TransactionData, YieldTokenBaseInfo]> {
const apiPromise = await this.substrateApi.isReady;
const { amount, selectedValidators } = data;

let lockTx: SubmittableExtrinsic<'promise'> | undefined;
let stakeTx: SubmittableExtrinsic<'promise'> | undefined;

const selectedValidatorInfo = selectedValidators[0];

const compoundTransactions = (bondTx: SubmittableExtrinsic<'promise'>, nominateTx: SubmittableExtrinsic<'promise'>): [TransactionData, YieldTokenBaseInfo] => {
const extrinsic = apiPromise.api.tx.utility.batchAll([bondTx, nominateTx]);

return [extrinsic, { slug: this.nativeToken.slug, amount: '0' }];
};

const _accountFreezes = await apiPromise.api.query.balances.freezes();
const accountFreezes = _accountFreezes.toPrimitive() as unknown as FrameSupportTokensMiscIdAmountRuntimeFreezeReason[];
const accountLocking = accountFreezes.filter((accountFreeze) => accountFreeze.id.CollatorStaking === 'Staking');

if (!accountLocking || !accountLocking.length) {
lockTx = apiPromise.api.tx.collatorStaking.lock(amount);
stakeTx = apiPromise.api.tx.collatorStaking.stake([{
candidate: selectedValidatorInfo.address,
stake: amount
}]);
} else {
const bnTotalLocking = accountLocking.reduce((old, currentLockAmount) => {
const bnCurrentLockAmount = BigInt(currentLockAmount.amount);

return old + bnCurrentLockAmount;
}, BigInt(0));

const lockAmount = (BigInt(amount) - bnTotalLocking).toString();

lockTx = apiPromise.api.tx.collatorStaking.lock(lockAmount);
stakeTx = apiPromise.api.tx.collatorStaking.stake([{
candidate: selectedValidatorInfo.address,
stake: amount
}]);
}

return compoundTransactions(lockTx, stakeTx);
}

/* Join pool action */

/* Leave pool action */

async handleYieldUnstake (amount: string, address: string, selectedTarget?: string): Promise<[ExtrinsicType, TransactionData]> {
const apiPromise = await this.substrateApi.isReady;
const extrinsicList = [
apiPromise.api.tx.collatorStaking.unstakeFrom(selectedTarget),
apiPromise.api.tx.collatorStaking.unlock(amount) // todo: can disable amount to unlock all
];

return [ExtrinsicType.STAKING_UNBOND, apiPromise.api.tx.utility.batch(extrinsicList)];
}

/* Leave pool action */

/* Other action */

async handleYieldCancelUnstake () {
return Promise.reject(new TransactionError(BasicTxErrorType.UNSUPPORTED));
}

async handleYieldWithdraw (address: string, unstakingInfo: UnstakingInfo) {
// todo: check UI
const apiPromise = await this.substrateApi.isReady;

return apiPromise.api.tx.collatorStaking.release();
}

/* Other action */
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PersistDataServiceInterface, ServiceStatus, StoppableServiceInterface }
import { _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';
import { EventService } from '@subwallet/extension-base/services/event-service';
import DatabaseService from '@subwallet/extension-base/services/storage-service/DatabaseService';
import { SWTransaction } from '@subwallet/extension-base/services/transaction-service/types';
Expand Down Expand Up @@ -39,7 +40,7 @@ export default class EarningService implements StoppableServiceInterface, Persis

private dbService: DatabaseService;
private eventService: EventService;
private useOnlineCacheOnly = true;
private useOnlineCacheOnly = false;

constructor (state: KoniState) {
this.state = state;
Expand Down Expand Up @@ -86,6 +87,10 @@ export default class EarningService implements StoppableServiceInterface, Persis
handlers.push(new TaoNativeStakingPoolHandler(this.state, chain));
}

if (_STAKING_CHAIN_GROUP.mythos.includes(chain)) {
handlers.push(new MythosNativeStakingPoolHandler(this.state, chain));
}

if (_STAKING_CHAIN_GROUP.nominationPool.includes(chain)) {
handlers.push(new NominationPoolHandler(this.state, chain));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ const Component: React.FC<Props> = (props: Props) => {
const totalApr = poolInfo.statistic?.totalApr;
const minJoinPool = poolInfo.statistic?.earningThreshold.join || '0';

console.log('minJoinPool', minJoinPool);

const getOrigin = () => {
switch (type) {
case YieldPoolType.NOMINATION_POOL:
Expand Down Expand Up @@ -126,6 +128,7 @@ const Component: React.FC<Props> = (props: Props) => {
if (Number(minJoinPool) === 0) {
result = result.replace(' from {{minActiveStake}}', '');
} else {
console.log(minJoinPool);
const string = formatNumber(minJoinPool, asset.decimals || 0, balanceFormatter);

result = result.replace('{{minActiveStake}}', `${string} ${asset.symbol}`);
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6439,15 +6439,15 @@ __metadata:
languageName: node
linkType: hard

"@subwallet/chain-list@npm:0.2.97":
version: 0.2.97
resolution: "@subwallet/chain-list@npm:0.2.97"
"@subwallet/chain-list@file:/Users/truongnguyen/Workspace/SubWallet-ChainList/packages/chain-list/build::locator=root-workspace-0b6124%40workspace%3A.":
version: 0.2.98-beta.1
resolution: "@subwallet/chain-list@file:/Users/truongnguyen/Workspace/SubWallet-ChainList/packages/chain-list/build#/Users/truongnguyen/Workspace/SubWallet-ChainList/packages/chain-list/build::hash=4d73a3&locator=root-workspace-0b6124%40workspace%3A."
dependencies:
"@polkadot/dev": 0.67.167
"@polkadot/util": ^12.5.1
eventemitter3: ^5.0.1
ts-md5: ^1.3.1
checksum: 3271231e2c5b435dd2f4c502da5af8b547a5ac8aafe7089d09c9966fd74b233eb4077d04e0c960857d5b5e2fbd29f178f08aec473e6ec23d19bd07b6642af432
checksum: 8f9571c1fccbef0a7d7e1c2b344dc0c27f6ea65e75781a19cb4ab032e70df9e048400ffa677d702648ab89dc608025773209dd2b52636bfa8c743e8f8a622af0
languageName: node
linkType: hard

Expand Down

0 comments on commit 4d73cb5

Please sign in to comment.