Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ledger support for unbond and withdraw #1158

Merged
merged 7 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 7 additions & 2 deletions src/hooks/useAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Multisig } from 'src/modules/multisig';
import { useStore } from 'src/store';
import { SubstrateAccount, UnifiedAccount } from 'src/store/general/state';
import { container } from 'src/v2/common';
import { IEventAggregator, UnifyAccountMessage } from 'src/v2/messaging';
import { ExtrinsicStatusMessage, IEventAggregator, UnifyAccountMessage } from 'src/v2/messaging';
import { IdentityRepository } from 'src/v2/repositories/implementations/IdentityRepository';
import { IAccountUnificationService } from 'src/v2/services';
import { Symbols } from 'src/v2/symbols';
Expand Down Expand Up @@ -139,7 +139,12 @@ export const useAccount = () => {

const showAccountUnificationModal = (): void => {
const eventAggregator = container.get<IEventAggregator>(Symbols.EventAggregator);
eventAggregator.publish(new UnifyAccountMessage());
eventAggregator.publish(
new ExtrinsicStatusMessage({
success: false,
message: 'Ledger is not supported on this network.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use i18 here

})
);
};

const currentAccount = ref<string>('');
Expand Down
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 };
};
14 changes: 7 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 @@ -886,6 +879,13 @@ export default {
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 unlock and withdraw your tokens. Since Ledger is not supported yet for dApp staking v3 move your funds to a soft wallet to be able to participate in dApp staking.',
migrateNow: 'Migrate Now',
},
ledgerNotSupported: 'Ledger native accounts is not supported yet for dApp staking v3 yet.',
bobo-k2 marked this conversation as resolved.
Show resolved Hide resolved
},
bridge: {
bridge: 'Bridge',
Expand Down
8 changes: 7 additions & 1 deletion src/staking-v3/components/DiscoverV3.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</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';
Expand All @@ -48,6 +48,7 @@ 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 +63,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
34 changes: 24 additions & 10 deletions src/staking-v3/components/my-staking/MigrationSupport.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<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') }}
</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 +23,26 @@
</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';

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, 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 @@ -14,16 +14,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 @@ -692,17 +695,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
Loading