diff --git a/src/assets/img/token/hdx.png b/src/assets/img/token/hdx.png new file mode 100644 index 000000000..1b6a07963 Binary files /dev/null and b/src/assets/img/token/hdx.png differ diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts index efb2c0d28..f368eb759 100644 --- a/src/i18n/en-US/index.ts +++ b/src/i18n/en-US/index.ts @@ -1050,7 +1050,7 @@ export default { percentageLocked: 'Percentage of Supply Locked', unclaimedRewards: 'Unclaimed rewards', rewardsClaimedOnStake: 'Rewards will be claimed when stake', - ifYouStakeNow: 'If you stake now', + ifYouStakeNow: 'Basic APR + Bonus (If you stake now)', defiDescription: 'Discover the world of decentralized finance solutions on Astar.', 'unstoppable-grantsDescription': 'Support projects in the Unstoppable Community Grants program, powered by dApp staking.', diff --git a/src/modules/xcm/index.ts b/src/modules/xcm/index.ts index ce75ac8ed..7fba14724 100644 --- a/src/modules/xcm/index.ts +++ b/src/modules/xcm/index.ts @@ -350,6 +350,20 @@ export let xcmChainObj: XcmChainObj = { subscan: 'https://unique.subscan.io', isAstarNativeToken: false, }, + [Chain.HYDRATION]: { + name: Chain.HYDRATION, + relayChain: Chain.POLKADOT, + img: require('/src/assets/img/token/hdx.png'), + parachainId: parachainIds.HYDRATION, + endpoints: [ + 'wss://hydradx-rpc.dwellir.com', + 'wss://rpc.hydradx.cloud', + 'wss://hydradx.paras.ibp.network', + 'wss://rpc.helikon.io/hydradx', + ], + subscan: 'https://hydration.subscan.io', + isAstarNativeToken: true, + }, }; export const xcmChains = objToArray(xcmChainObj); diff --git a/src/modules/xcm/tokens/index.ts b/src/modules/xcm/tokens/index.ts index 5475a0fcc..6ab948205 100644 --- a/src/modules/xcm/tokens/index.ts +++ b/src/modules/xcm/tokens/index.ts @@ -165,6 +165,16 @@ export const xcmToken = { originChain: Chain.ASSET_HUB, minBridgeAmount: '1', }, + { + symbol: 'HDX', + isNativeToken: true, + assetId: '18446744073709551630', + originAssetId: 'HDX', + logo: require('/src/assets/img/token/hdx.png'), + isXcmCompatible: true, + originChain: Chain.HYDRATION, + minBridgeAmount: '5', + }, ], [endpointKey.SHIDEN]: [ { diff --git a/src/staking-v3/components/PeriodInfoVote.vue b/src/staking-v3/components/PeriodInfoVote.vue index 1a2e494e6..463302c56 100644 --- a/src/staking-v3/components/PeriodInfoVote.vue +++ b/src/staking-v3/components/PeriodInfoVote.vue @@ -41,7 +41,7 @@
-
{{ $t('stakingV3.basicAprPlusBonus') }}
+
{{ $t('stakingV3.estimatedRewards') }}
{{ $t('stakingV3.ifYouStakeNow') }}
diff --git a/src/staking-v3/hooks/useLeaderboard.ts b/src/staking-v3/hooks/useLeaderboard.ts index a716d860c..1a3ef0119 100644 --- a/src/staking-v3/hooks/useLeaderboard.ts +++ b/src/staking-v3/hooks/useLeaderboard.ts @@ -1,5 +1,5 @@ import { watch, ref, computed } from 'vue'; -import { CombinedDappInfo, PeriodType, useDappStaking, useDapps } from '..'; +import { CombinedDappInfo, DAppTier, PeriodType, useDappStaking, useDapps } from '..'; import { useStore } from 'src/store'; import { sort } from 'src/v2/common'; @@ -9,7 +9,7 @@ export function useLeaderboard() { const { dAppTiers, protocolState, eraLengths } = useDappStaking(); // Map key is a dApp tier. const leaderBoards = ref>(new Map()); - const leaderboard = computed>( + const leaderboard = computed>( () => store.getters['stakingV3/getLeaderboard'] ); @@ -43,8 +43,8 @@ export function useLeaderboard() { ]); sortedDapps.value.forEach((dapp) => { - const tier = leaderboard.value.get(dapp.chain.id); - tier !== undefined && leaderBoards.value.get(tier + 1)?.push(dapp); + const dappTier = leaderboard.value.get(dapp.chain.id); + dappTier !== undefined && leaderBoards.value.get(dappTier.tierId + 1)?.push(dapp); }); }; diff --git a/src/staking-v3/logic/interfaces/DappStakingV3.ts b/src/staking-v3/logic/interfaces/DappStakingV3.ts index b75e2ffd5..a6e079c49 100644 --- a/src/staking-v3/logic/interfaces/DappStakingV3.ts +++ b/src/staking-v3/logic/interfaces/DappStakingV3.ts @@ -110,6 +110,7 @@ export interface PalletDappStakingV3DAppTierRewards extends Struct { readonly dapps: BTreeMap, Compact>; readonly rewards: Vec; readonly period: Compact; + readonly rankRewards: Vec; } export interface PalletDappStakingV3EraInfo extends Struct { diff --git a/src/staking-v3/logic/models/DappStaking.ts b/src/staking-v3/logic/models/DappStaking.ts index 4fd2c3464..f8462ebc2 100644 --- a/src/staking-v3/logic/models/DappStaking.ts +++ b/src/staking-v3/logic/models/DappStaking.ts @@ -139,11 +139,13 @@ export interface DAppTierRewards { readonly dapps: DAppTier[]; readonly rewards: bigint[]; readonly period: number; + readonly rankRewards: bigint[]; } export interface DAppTier { readonly dappId: number; - readonly tierId: number | undefined; + readonly tierId: number; + readonly rank: number; } export interface Rewards { diff --git a/src/staking-v3/logic/repositories/DappStakingRepository.ts b/src/staking-v3/logic/repositories/DappStakingRepository.ts index f90b41f21..261bfa195 100644 --- a/src/staking-v3/logic/repositories/DappStakingRepository.ts +++ b/src/staking-v3/logic/repositories/DappStakingRepository.ts @@ -380,20 +380,23 @@ export class DappStakingRepository implements IDappStakingRepository { const tiers = tiersWrapped.unwrap(); const dapps: DAppTier[] = []; tiers.dapps.forEach((value, key) => - dapps.push({ - dappId: key.toNumber(), - tierId: value.toNumber(), - }) + dapps.push(this.mapDappTier(key.toNumber(), value.toNumber())) ); return { period: tiers.period.toNumber(), dapps, rewards: tiers.rewards.map((reward) => reward.toBigInt()), + rankRewards: tiers.rankRewards?.map((reward) => reward.toBigInt()) ?? [ + BigInt(0), + BigInt(0), + BigInt(0), + BigInt(0), + ], // for backward compatibility in case when not deployed on all networks }; } //* @inheritdoc - public async getLeaderboard(): Promise> { + public async getLeaderboard(): Promise> { const api = await this.api.getApi(); const tierAssignmentsBytes = await api.rpc.state.call( 'DappStakingApi_get_dapp_tier_assignment', @@ -401,8 +404,10 @@ export class DappStakingRepository implements IDappStakingRepository { ); const tierAssignment = api.createType('BTreeMap', tierAssignmentsBytes); - const result = new Map(); - tierAssignment.forEach((value, key) => result.set(key.toNumber(), value.toNumber())); + const result = new Map(); + tierAssignment.forEach((value, key) => + result.set(key.toNumber(), this.mapDappTier(key.toNumber(), value.toNumber())) + ); return result; } @@ -627,4 +632,12 @@ export class DappStakingRepository implements IDappStakingRepository { console.log('Staker info size: ' + result.size); return result; } + + private mapDappTier(dappId: number, rankTier: number): DAppTier { + return { + dappId: dappId, + tierId: rankTier & 0xf, + rank: rankTier >> 4, + }; + } } diff --git a/src/staking-v3/logic/repositories/IDappStakingRepository.ts b/src/staking-v3/logic/repositories/IDappStakingRepository.ts index 1821d085a..c3cd92063 100644 --- a/src/staking-v3/logic/repositories/IDappStakingRepository.ts +++ b/src/staking-v3/logic/repositories/IDappStakingRepository.ts @@ -3,6 +3,7 @@ import { AccountLedger, Constants, ContractStakeAmount, + DAppTier, DAppTierRewards, Dapp, DappBase, @@ -212,7 +213,7 @@ export interface IDappStakingRepository { /** * Gets dApps tier assignment map. */ - getLeaderboard(): Promise>; + getLeaderboard(): Promise>; /** * Gets a call to the legacy code to support v2 ledger stakers to unlock their funds. diff --git a/src/staking-v3/logic/services/DappStakingService.ts b/src/staking-v3/logic/services/DappStakingService.ts index 6a3091ed8..0a734edba 100644 --- a/src/staking-v3/logic/services/DappStakingService.ts +++ b/src/staking-v3/logic/services/DappStakingService.ts @@ -579,7 +579,9 @@ export class DappStakingService extends SignerService implements IDappStakingSer if (tierReward) { const dApp = tierReward?.dapps.find((d) => d.dappId === dapp.id); if (dApp && dApp.tierId !== undefined) { - result.rewards += tierReward.rewards[dApp.tierId]; + result.rewards += + tierReward.rewards[dApp.tierId] + + BigInt(dApp.rank) * tierReward.rankRewards[dApp.tierId]; result.erasToClaim.push(firstEra + index); } } diff --git a/src/staking-v3/store/getters.ts b/src/staking-v3/store/getters.ts index 3cd37ab2e..d0e109326 100644 --- a/src/staking-v3/store/getters.ts +++ b/src/staking-v3/store/getters.ts @@ -14,6 +14,7 @@ import { TiersConfiguration, EraLengths, DappInfo, + DAppTier, } from '../logic'; export interface DappStakingGetters { @@ -30,7 +31,7 @@ export interface DappStakingGetters { getDappTiers(state: DappStakingState): DAppTierRewards; getTiersConfiguration(state: DappStakingState): TiersConfiguration; getEraLengths(state: DappStakingState): EraLengths; - getLeaderboard(state: DappStakingState): Map; + getLeaderboard(state: DappStakingState): Map; } const getters: GetterTree & DappStakingGetters = { diff --git a/src/staking-v3/store/mutations.ts b/src/staking-v3/store/mutations.ts index 1535a197c..7e36eb6c7 100644 --- a/src/staking-v3/store/mutations.ts +++ b/src/staking-v3/store/mutations.ts @@ -15,6 +15,7 @@ import { TiersConfiguration, EraLengths, ProviderDappData, + DAppTier, } from '../logic'; export interface DappStakingMutations { @@ -37,7 +38,7 @@ export interface DappStakingMutations { setDappTiers(state: DappStakingState, dAppTiers: DAppTierRewards): void; setTiersConfiguration(state: DappStakingState, tiersConfiguration: TiersConfiguration): void; setEraLengths(state: DappStakingState, eraLengths: EraLengths): void; - setLeaderboard(state: DappStakingState, leaderboard: Map): void; + setLeaderboard(state: DappStakingState, leaderboard: Map): void; } const updateDapp = ( diff --git a/src/staking-v3/store/state.ts b/src/staking-v3/store/state.ts index c28d26cfc..3d82e9274 100644 --- a/src/staking-v3/store/state.ts +++ b/src/staking-v3/store/state.ts @@ -11,6 +11,7 @@ import { TiersConfiguration, EraLengths, DappInfo, + DAppTier, } from '../logic'; export interface DappStakingState { @@ -27,7 +28,7 @@ export interface DappStakingState { dAppTiers: DAppTierRewards; tiersConfiguration: TiersConfiguration; eraLengths: EraLengths; - leaderboard: Map; + leaderboard: Map; } function state(): DappStakingState { @@ -45,7 +46,7 @@ function state(): DappStakingState { dAppTiers: initialDappTiersConfiguration, tiersConfiguration: initialTiersConfiguration, eraLengths: initialEraLengths, - leaderboard: new Map(), + leaderboard: new Map(), }; } @@ -68,6 +69,7 @@ export const initialDappTiersConfiguration: DAppTierRewards = { dapps: [], rewards: [], period: 0, + rankRewards: [], }; export const initialEraLengths: EraLengths = { diff --git a/src/v2/config/xcm/XcmRepositoryConfiguration.ts b/src/v2/config/xcm/XcmRepositoryConfiguration.ts index ce634d71c..362573077 100644 --- a/src/v2/config/xcm/XcmRepositoryConfiguration.ts +++ b/src/v2/config/xcm/XcmRepositoryConfiguration.ts @@ -10,6 +10,7 @@ import { BifrostXcmRepository, EquilibriumXcmRepository, UniqueXcmRepository, + HydrationXcmRepository, } from 'src/v2/repositories/implementations'; import { Chain } from 'src/v2/models/XcmModels'; import { TypeMapping } from 'src/v2/config/types'; @@ -34,6 +35,7 @@ export const XcmRepositoryConfiguration: TypeMapping = { [Chain.BIFROST_KUSAMA]: BifrostXcmRepository, [Chain.EQUILIBRIUM]: EquilibriumXcmRepository, [Chain.UNIQUE]: UniqueXcmRepository, + [Chain.HYDRATION]: HydrationXcmRepository, }; export type AstarToken = 'ASTR' | 'SDN'; diff --git a/src/v2/models/XcmModels.ts b/src/v2/models/XcmModels.ts index cdb8e0929..5dc33e7d0 100644 --- a/src/v2/models/XcmModels.ts +++ b/src/v2/models/XcmModels.ts @@ -21,6 +21,7 @@ export enum Chain { BIFROST_KUSAMA = 'Bifrost', EQUILIBRIUM = 'Equilibrium', UNIQUE = 'Unique', + HYDRATION = 'Hydration', } export enum parachainIds { @@ -41,6 +42,7 @@ export enum parachainIds { BIFROST_KUSAMA = 2001, EQUILIBRIUM = 2011, UNIQUE = 2037, + HYDRATION = 2034, } export interface XcmChain { diff --git a/src/v2/repositories/implementations/index.ts b/src/v2/repositories/implementations/index.ts index 4826419a3..c8699ef85 100644 --- a/src/v2/repositories/implementations/index.ts +++ b/src/v2/repositories/implementations/index.ts @@ -18,6 +18,7 @@ export * from './ZkBridgeRepository'; export * from './xcm/BifrostXcmRepository'; export * from './xcm/EquilibriumXcmRepository'; export * from './xcm/UniqueXcmRepository'; +export * from './xcm/HydrationXcmRepository'; export * from './NftRepository'; export * from './AccountUnificationRepository'; export * from './InflationRepository'; diff --git a/src/v2/repositories/implementations/xcm/HydrationXcmRepository.ts b/src/v2/repositories/implementations/xcm/HydrationXcmRepository.ts new file mode 100644 index 000000000..d703bc223 --- /dev/null +++ b/src/v2/repositories/implementations/xcm/HydrationXcmRepository.ts @@ -0,0 +1,109 @@ +import { BN } from '@polkadot/util'; +import { decodeAddress } from '@polkadot/util-crypto'; +import { XcmTokenInformation } from 'src/modules/xcm'; +import { container } from 'src/v2/common'; +import { ExtrinsicPayload, IApi, IApiFactory } from 'src/v2/integration'; +import { Asset } from 'src/v2/models'; +import { XcmChain } from 'src/v2/models/XcmModels'; +import { TokensAccounts } from 'src/v2/repositories/implementations/xcm/AcalaXcmRepository'; +import { Symbols } from 'src/v2/symbols'; +import { XcmRepository } from '../XcmRepository'; + +// Mapping object for token IDs +const TOKEN_IDS: Record = { + HDX: { id: 0 }, + ASTR: { id: 9 }, +}; + +export class HydrationXcmRepository extends XcmRepository { + constructor() { + const defaultApi = container.get(Symbols.DefaultApi); + const apiFactory = container.get(Symbols.ApiFactory); + const registeredTokens = container.get(Symbols.RegisteredTokens); + super(defaultApi, apiFactory, registeredTokens); + this.astarTokens = { + ASTR: 9, + }; + } + + public async getTransferCall( + from: XcmChain, + to: XcmChain, + recipientAddress: string, + token: Asset, + amount: BN, + endpoint: string + ): Promise { + if (!to.parachainId) { + throw `Parachain id for ${to.name} is not defined`; + } + + const tokenData = TOKEN_IDS[token.originAssetId]; + if (!tokenData) { + throw `Token name for ${token.originAssetId} is not defined`; + } + + const version = 'V3'; + + const AccountId32 = { + id: decodeAddress(recipientAddress), + }; + + const destination = { + [version]: { + parents: '1', + interior: { + X2: [ + { + Parachain: to.parachainId, + }, + { + AccountId32, + }, + ], + }, + }, + }; + + const destWeight = { + Unlimited: null, + }; + + return await this.buildTxCall( + from, + endpoint, + 'xTokens', + 'transfer', + tokenData, + amount, + destination, + destWeight + ); + } + + public async getTokenBalance( + address: string, + chain: XcmChain, + token: Asset, + isNativeToken: boolean, + endpoint: string + ): Promise { + const api = await this.apiFactory.get(endpoint); + try { + if (token.originAssetId == 'HDX') { + return (await this.getNativeBalance(address, chain, endpoint)).toString(); + } else { + const tokenData = TOKEN_IDS[token.originAssetId]; + if (!tokenData) { + return '0'; + } + + const bal = await api.query.tokens.accounts(address, tokenData); + return bal.free.toString(); + } + } catch (e) { + console.error(e); + return '0'; + } + } +}