Skip to content

Commit

Permalink
Ledger support for unbond and withdraw (#1158)
Browse files Browse the repository at this point in the history
* Ledger support for unbond and withdraw

* Warning for Ledger users

* Update src/i18n/en-US/index.ts

Co-authored-by: impelcrypto <[email protected]>

* Message text update

* Code review fixes.

---------

Co-authored-by: impelcrypto <[email protected]>
Co-authored-by: Gregory Luneau <[email protected]>
  • Loading branch information
3 people authored Feb 2, 2024
1 parent c7b52f9 commit 04c5624
Show file tree
Hide file tree
Showing 16 changed files with 215 additions and 51 deletions.
2 changes: 1 addition & 1 deletion src/hooks/wallet/useLedger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@ export const useLedger = () => {

watch([currentAccount], handleLedgerData, { immediate: true });

return { isLedgerNanoS };
return { isLedgerNanoS, isLedger };
};
18 changes: 11 additions & 7 deletions src/i18n/en-US/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,6 @@ export default {
uaw: 'Unique Active Wallets',
cantClaimWihtoutError:
'You cannot claim with automatic re-stake because it has been a while since you claimed your rewards. Please turn off the Auto Re-Stake feature to be able to claim. After you claim rewards you can turn on re-stake again. The UI team is working to fix this issue.',
migrationSupport: {
actionRequired: 'Action Required',
yourTokensAreLocked:
'Your tokens are locked in dAppStaking V2. Please migrate your funds to V3 today and start participating.',
balanceFromV2: 'Balance from V2 (locked)',
migrateNow: 'Migrate Now',
},
stakePage: {
backToDappList: 'Back to dApps list',
whereFundsFrom: 'Where would you like to bring your funds from?',
Expand Down Expand Up @@ -893,6 +886,17 @@ export default {
'You will loose eligibility for bonus reward at the end of current period if you unstake more than {amount} tokens.',
loyalStakerWarning:
'You will loose eligibility for bonus reward at the end of current period if you unstake tokens now.',
unbondFrom: 'Unbond from {name}',
startUnbonding: 'Start unbonding',
unbondingEra: 'Unbonding takes {unbondingPeriod} eras before you can withdraw',
migrationSupport: {
actionRequired: 'Action Required',
yourTokensAreLocked:
'Your tokens are locked in dAppStaking V2. Please unbond and withdraw your tokens. dApp Staking V3 is temporally unavailable for those Ledger Astar Native App users, please move your funds to a soft wallet or a Ledger EVM account to be able to participate in dApp staking.',
migrateNow: 'Migrate Now',
},
ledgerNotSupported: 'Ledger native accounts are not supported for dApp staking V3 yet.',
moreInfo: 'More info',
unlockFrom: 'Unlock from {name}',
startUnlocking: 'Start unlocking',
unlockingDay: 'Unlocking takes {unbondingPeriod} days before you can withdraw',
Expand Down
2 changes: 2 additions & 0 deletions src/links/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const docsUrl = {
'https://docs.astar.network/tutorial/how-to-send-astr-sdn-from-metamask-to-polkadot.js',
troubleShooting: 'https://docs.astar.network/docs/use/user-guides/troubleshooting',
createPromotion: 'https://docs.astar.network/docs/use/dapp-staking/for-devs/create-promotion/',
faqLedger:
'https://docs.astar.network/docs/learn/dapp-staking/dapp-staking-faq/#q-i-am-a-leger-astar-native-app-user-what-do-i-need-to-do',
};

export const socialUrl = {
Expand Down
9 changes: 8 additions & 1 deletion src/staking-v3/components/DiscoverV3.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import { defineComponent, ref, onMounted } from 'vue';
import Dapps from './Dapps.vue';
import FeatureDapp from './FeatureDapp.vue';
import Leaderboard from './leaderboard/Leaderboard.vue';
import LeaderboardVote from './leaderboard/LeaderboardVote.vue';
import DynamicAdsArea from './DynamicAdsArea.vue';
import ToggleButtons from './ToggleButtons.vue';
import DataList from './data/DataList.vue';
import RegisterBanner from './RegisterBanner.vue';
import { useDappStaking } from '../hooks';
export default defineComponent({
components: {
Expand All @@ -62,13 +64,18 @@ export default defineComponent({
},
setup() {
const displayIndex = ref<number>(0);
const { warnIfLedger } = useDappStaking();
const toggleDapps = (index: number): void => {
displayIndex.value = index;
};
const searchText = ref<string>('');
onMounted(() => {
warnIfLedger();
});
return { displayIndex, searchText, toggleDapps };
},
});
Expand Down
37 changes: 27 additions & 10 deletions src/staking-v3/components/my-staking/MigrationSupport.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
<template>
<div class="wrapper--migration-support">
<div v-if="isLedger && hasLockedTokens" class="wrapper--migration-support">
<div class="wrapper--migration-support__inter">
<div class="row--header">
{{ $t('dappStaking.migrationSupport.actionRequired') }}
{{ $t('stakingV3.migrationSupport.actionRequired') }}
</div>
<div class="row--body">
<div class="text">
{{ $t('dappStaking.migrationSupport.yourTokensAreLocked') }}
{{ $t('stakingV3.migrationSupport.yourTokensAreLocked') }}
(<a :href="docsUrl.faqLedger" target="_blank"> {{ $t('stakingV3.moreInfo') }}</a
>)
</div>
<div class="row--locked-tokens">
<div>{{ $t('dappStaking.migrationSupport.balanceFromV2') }}</div>
<div>-- ASTR</div>
<div>{{ $t('stakingV3.lockedAmount') }}</div>
<token-balance-native :balance="availableToUnlock.toString()" />
<div class="column--migrate">
<button type="button" class="button--migrate">
{{ $t('dappStaking.migrationSupport.migrateNow') }}
<button type="button" class="button--migrate" @click="unlock(availableToUnlock)">
{{ $t('stakingV3.unlock') }}
</button>
</div>
</div>
Expand All @@ -23,12 +25,27 @@
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { defineComponent, computed } from 'vue';
import { useLedger } from 'src/hooks';
import { useDappStaking } from 'src/staking-v3/hooks';
import TokenBalanceNative from 'src/components/common/TokenBalanceNative.vue';
import { docsUrl } from 'src/links';
export default defineComponent({
props: {},
components: {
TokenBalanceNative,
},
setup() {
return {};
const { isLedger } = useLedger();
const { ledger, totalStake, unlock } = useDappStaking();
const hasLockedTokens = computed<Boolean>(
() => (ledger.value?.locked ?? BigInt(0)) > BigInt(0)
);
const availableToUnlock = computed<bigint>(
() => (ledger.value?.locked ?? BigInt(0)) - totalStake.value
);
return { isLedger, hasLockedTokens, availableToUnlock, docsUrl, unlock };
},
});
</script>
Expand Down
3 changes: 1 addition & 2 deletions src/staking-v3/components/my-staking/Staking.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

<div class="separator" />

<!-- TODO: add logic and show the component -->
<migration-support v-if="false" />
<migration-support />

<tab-component
:tabs="tabs"
Expand Down
61 changes: 48 additions & 13 deletions src/staking-v3/hooks/useDappStaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
EraLengths,
IDappStakingRepository,
IDappStakingService,
IDappStakingServiceV2Ledger,
PeriodType,
ProtocolState,
Rewards,
Expand All @@ -21,7 +22,7 @@ import {
import { Symbols } from 'src/v2/symbols';
import { ExtrinsicStatusMessage, IEventAggregator } from 'src/v2/messaging';
import { useStore } from 'src/store';
import { useAccount, useChainMetadata, useNetworkInfo } from 'src/hooks';
import { useAccount, useChainMetadata, useNetworkInfo, useLedger } from 'src/hooks';
import { useI18n } from 'vue-i18n';
import { useDapps } from './useDapps';
import { ethers } from 'ethers';
Expand All @@ -44,6 +45,7 @@ export function useDappStaking() {
const { registeredDapps, fetchStakeAmountsToStore, getDapp } = useDapps();
const { decimal } = useChainMetadata();
const { nativeTokenSymbol } = useNetworkInfo();
const { isLedger } = useLedger();

const currentBlock = computed<number>(() => store.getters['general/getCurrentBlock']);

Expand Down Expand Up @@ -278,10 +280,20 @@ export function useDappStaking() {
};

const withdraw = async (): Promise<void> => {
const stakingService = container.get<() => IDappStakingService>(
Symbols.DappStakingServiceFactoryV3
)();
await stakingService.claimUnlockedTokens(currentAccount.value, t('stakingV3.withdrawSuccess'));
if (isLedger.value) {
const stakingService = container.get<IDappStakingServiceV2Ledger>(
Symbols.DappStakingServiceV2Ledger
);
await stakingService.withdraw(currentAccount.value, t('stakingV3.withdrawSuccess'));
} else {
const stakingService = container.get<() => IDappStakingService>(
Symbols.DappStakingServiceFactoryV3
)();
await stakingService.claimUnlockedTokens(
currentAccount.value,
t('stakingV3.withdrawSuccess')
);
}
getCurrentEraInfo();
};

Expand All @@ -293,14 +305,21 @@ export function useDappStaking() {
};

const unlock = async (amount: bigint): Promise<void> => {
const stakingService = container.get<() => IDappStakingService>(
Symbols.DappStakingServiceFactoryV3
)();
await stakingService.unlockTokens(
currentAccount.value,
Number(ethers.utils.formatEther(amount)),
t('stakingV3.unlockSuccess')
);
if (isLedger.value) {
const stakingService = container.get<IDappStakingServiceV2Ledger>(
Symbols.DappStakingServiceV2Ledger
);
await stakingService.unlock(currentAccount.value, amount, t('stakingV3.unlockSuccess'));
} else {
const stakingService = container.get<() => IDappStakingService>(
Symbols.DappStakingServiceFactoryV3
)();
await stakingService.unlockTokens(
currentAccount.value,
Number(ethers.utils.formatEther(amount)),
t('stakingV3.unlockSuccess')
);
}
};

const getAllRewards = async (): Promise<void> => {
Expand Down Expand Up @@ -576,6 +595,21 @@ export function useDappStaking() {
return period.toString().padStart(3, '0');
};

const warnIfLedger = (): void => {
// Show warning to ledger users.
const { isLedger } = useLedger();
const { isDappStakingV3 } = useDappStaking();
if (isLedger.value && isDappStakingV3.value) {
const eventAggregator = container.get<IEventAggregator>(Symbols.EventAggregator);
eventAggregator.publish(
new ExtrinsicStatusMessage({
success: false,
message: t('stakingV3.ledgerNotSupported'),
})
);
}
};

return {
protocolState,
ledger,
Expand Down Expand Up @@ -622,5 +656,6 @@ export function useDappStaking() {
rewardExpiresInNextPeriod,
getStakerInfo,
formatPeriod,
warnIfLedger,
};
}
17 changes: 17 additions & 0 deletions src/staking-v3/logic/repositories/DappStakingRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,23 @@ export class DappStakingRepository implements IDappStakingRepository {
return api.tx.dappStaking.cleanupExpiredEntries();
}

/** @inheritdoc */
public async getUnbondAndUnstakeCall(amount: bigint): Promise<ExtrinsicPayload> {
const api = await this.api.getApi();
// Memo: address is ignored by runtime, but we need to pass something
// because runtime needs to keep the method signature.
return api.tx.dappStaking.unbondAndUnstake(
getDappAddressEnum('ajYMsCKsEAhEvHpeA4XqsfiA9v1CdzZPrCfS6pEfeGHW9j8'),
amount
);
}

/** @inheritdoc */
public async getWithdrawUnbondedCall(): Promise<ExtrinsicPayload> {
const api = await this.api.getApi();
return api.tx.dappStaking.withdrawUnbonded();
}

// ------------------ MAPPERS ------------------
private mapToModel(state: PalletDappStakingV3ProtocolState): ProtocolState {
return {
Expand Down
10 changes: 10 additions & 0 deletions src/staking-v3/logic/repositories/IDappStakingRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,14 @@ export interface IDappStakingRepository {
* Gets dApps tier assignment map.
*/
getLeaderboard(): Promise<Map<number, number>>;

/**
* Gets a call to the legacy code to support v2 ledger stakers to unlock their funds.
*/
getUnbondAndUnstakeCall(amount: bigint): Promise<ExtrinsicPayload>;

/**
* Gets a call to the legacy code to support v2 ledger stakers to withdraw their funds.
*/
getWithdrawUnbondedCall(): Promise<ExtrinsicPayload>;
}
22 changes: 6 additions & 16 deletions src/staking-v3/logic/services/DappStakingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ import { Guard } from 'src/v2/common';
import { IWalletService } from 'src/v2/services';
import { ExtrinsicPayload } from '@astar-network/astar-sdk-core';
import { ethers } from 'ethers';
import { SignerService } from './SignerService';

@injectable()
export class DappStakingService implements IDappStakingService {
export class DappStakingService extends SignerService implements IDappStakingService {
constructor(
@inject(Symbols.DappStakingRepositoryV3)
protected dappStakingRepository: IDappStakingRepository,
@inject(Symbols.TokenApiProviderRepository)
protected tokenApiRepository: IDataProviderRepository,
@inject(Symbols.WalletFactory) private walletFactory: () => IWalletService
) {}
@inject(Symbols.WalletFactory) walletFactory: () => IWalletService
) {
super(walletFactory);
}

// @inheritdoc
public async getDapps(
Expand Down Expand Up @@ -714,17 +717,4 @@ export class DappStakingService implements IDappStakingService {
): boolean {
return stakedPeriod < currentPeriod - rewardRetentionInPeriods;
}

private async signCall(
call: ExtrinsicPayload,
senderAddress: string,
successMessage: string
): Promise<void> {
const wallet = this.walletFactory();
await wallet.signAndSend({
extrinsic: call,
senderAddress: senderAddress,
successMessage,
});
}
}
39 changes: 39 additions & 0 deletions src/staking-v3/logic/services/DappStakingServiceV2Ledger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { injectable, inject } from 'inversify';
import { IDappStakingServiceV2Ledger } from './IDappStakingServiceV2Ledger';
import { SignerService } from './SignerService';
import { Symbols } from 'src/v2/symbols';
import { IDappStakingRepository } from '../repositories';
import { IWalletService } from 'src/v2/services';
import { Guard } from 'src/v2/common';

@injectable()
export class DappStakingServiceV2Ledger
extends SignerService
implements IDappStakingServiceV2Ledger
{
constructor(
@inject(Symbols.DappStakingRepositoryV3)
protected dappStakingRepository: IDappStakingRepository,
@inject(Symbols.WalletFactory) walletFactory: () => IWalletService
) {
super(walletFactory);
}

public async unlock(
senderAddress: string,
amount: bigint,
successMessage: string
): Promise<void> {
Guard.ThrowIfUndefined(senderAddress, 'senderAddress');

const call = await this.dappStakingRepository.getUnbondAndUnstakeCall(amount);
await this.signCall(call, senderAddress, successMessage);
}

public async withdraw(senderAddress: string, successMessage: string): Promise<void> {
Guard.ThrowIfUndefined(senderAddress, 'senderAddress');

const call = await this.dappStakingRepository.getWithdrawUnbondedCall();
await this.signCall(call, senderAddress, successMessage);
}
}
8 changes: 8 additions & 0 deletions src/staking-v3/logic/services/IDappStakingServiceV2Ledger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Support for v2 ledger stakers to enable them to unlock and withdraw their tokens.
*/
export interface IDappStakingServiceV2Ledger {
unlock(senderAddress: string, amount: bigint, successMessage: string): Promise<void>;

withdraw(senderAddress: string, successMessage: string): Promise<void>;
}
Loading

0 comments on commit 04c5624

Please sign in to comment.