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

feat: adds voting power management #5337

Merged
merged 7 commits into from Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 22 additions & 7 deletions packages/shared/components/VotingPower.svelte
@@ -1,25 +1,40 @@
<script lang="typescript">
import { Text, Button } from 'shared/components'
import { localize } from '@core/i18n'
import { ButtonSize, FontWeight, TextType } from './enums'
import { selectedAccount } from '@core/account'
import { localize } from '@core/i18n'
import { formatTokenAmountBestMatch, visibleSelectedAccountAssets } from '@core/wallet'
import { openPopup } from '@auxiliary/popup'

const asset = $visibleSelectedAccountAssets?.baseCoin

const votingPowerBalance: string = '4821 SMR'
const maximalVotingPower: string = '4821 SMR'
$: votingPower = parseInt($selectedAccount?.votingPower, 10)
$: maxVotingPower = parseInt($selectedAccount?.balances?.baseCoin?.available)
$: formattedVotingPower = formatTokenAmountBestMatch(votingPower, asset?.metadata)
$: formattedMaxVotingPower = formatTokenAmountBestMatch(maxVotingPower, asset?.metadata)

function handleManageVotingPower(): void {
return
openPopup({
type: 'manageVotingPower',
})
}
</script>

<voting-power>
<Text fontSize="14" fontWeight={FontWeight.semibold} classes="mb-4">
{localize('views.governance.votingPower.title')}
</Text>
<Text type={TextType.h1}>{votingPowerBalance}</Text>
<Text type={TextType.h1}>{formattedVotingPower}</Text>
<Text fontWeight={FontWeight.medium} overrideColor classes="mb-4 text-gray-600">
{localize('views.governance.votingPower.maximal', { values: { value: maximalVotingPower } })}
{localize('views.governance.votingPower.maximal', { values: { value: formattedMaxVotingPower } })}
</Text>
<Button size={ButtonSize.Medium} onClick={handleManageVotingPower} classes="w-full">
<Button
size={ButtonSize.Medium}
onClick={handleManageVotingPower}
classes="w-full"
disabled={$selectedAccount.isTransferring}
isBusy={$selectedAccount.isTransferring}
>
{localize('views.governance.votingPower.manage')}
</Button>
</voting-power>
2 changes: 2 additions & 0 deletions packages/shared/components/popups/Index.svelte
Expand Up @@ -26,6 +26,7 @@
import LedgerConnectionGuidePopup from './LedgerConnectionGuidePopup.svelte'
import LegalUpdate from './LegalUpdate.svelte'
import ManageAccountPopup from './ManageAccountPopup.svelte'
import ManageVotingPowerPopup from './ManageVotingPowerPopup.svelte'
import MintNativeTokenFormPopup from './MintNativeTokenFormPopup.svelte'
import MintNativeTokenConfirmationPopup from './MintNativeTokenConfirmationPopup.svelte'
import MintNftFormPopup from './MintNftFormPopup.svelte'
Expand Down Expand Up @@ -119,6 +120,7 @@
faucetRequest: FaucetRequestPopup,
enableLedgerBlindSigning: EnableLedgerBlindSigningPopup,
testDeepLinkForm: TestDeepLinkFormPopup,
manageVotingPower: ManageVotingPowerPopup,
}

function onKey(event: KeyboardEvent): void {
Expand Down
55 changes: 55 additions & 0 deletions packages/shared/components/popups/ManageVotingPowerPopup.svelte
@@ -0,0 +1,55 @@
<script lang="typescript">
import { TextType } from 'shared/components/enums'
import { Button, Text, TextHint, AssetAmountInput } from 'shared/components'
import { selectedAccount } from '@core/account'
import { handleError } from '@core/error/handlers/handleError'
import { setVotingPower } from '@core/governance/actions'
import { localize } from '@core/i18n'
import { checkActiveProfileAuth } from '@core/profile'
import { visibleSelectedAccountAssets } from '@core/wallet'
import { closePopup } from '@auxiliary/popup'

const asset = $visibleSelectedAccountAssets?.baseCoin

let assetAmountInput: AssetAmountInput
let rawAmount = $selectedAccount?.votingPower

$: isTransferring = $selectedAccount?.isTransferring

function handleBack(): void {
closePopup()
}

async function handleConfirm(): Promise<void> {
try {
await assetAmountInput?.validate()
await checkActiveProfileAuth(async () => {
await setVotingPower(rawAmount)
closePopup()
})
} catch (err) {
handleError(err)
closePopup()
}
}
</script>

<Text type={TextType.h4} classes="mb-3">{localize('popups.manageVotingPower.title')}</Text>
<Text type={TextType.p} secondary classes="mb-5">{localize('popups.manageVotingPower.body')}</Text>
<div class="space-y-4 mb-6">
<AssetAmountInput bind:this={assetAmountInput} bind:rawAmount {asset} containsSlider disableAssetSelection />
<TextHint info text={localize('popups.manageVotingPower.hint')} />
</div>
<div class="flex flex-row flex-nowrap w-full space-x-4">
<Button outline classes="w-full" disabled={isTransferring} onClick={handleBack}>
{localize('actions.back')}
</Button>
<Button
classes="w-full"
disabled={$selectedAccount.isTransferring}
onClick={handleConfirm}
isBusy={$selectedAccount.isTransferring}
>
{localize('actions.confirm')}
</Button>
</div>
3 changes: 3 additions & 0 deletions packages/shared/lib/core/account/actions/buildAccountState.ts
Expand Up @@ -18,9 +18,11 @@ export async function buildAccountState(account: IAccount, metadata: IAccountMet
aliases: [],
}
let depositAddress: string
let votingPower: string
try {
balances = await account.getBalance()
depositAddress = await getDepositAddress(account)
votingPower = await account.getVotingPower()
} catch (err) {
console.error(err)
}
Expand All @@ -31,5 +33,6 @@ export async function buildAccountState(account: IAccount, metadata: IAccountMet
depositAddress,
balances,
isTransferring: false,
votingPower,
}
}
1 change: 1 addition & 0 deletions packages/shared/lib/core/account/actions/index.ts
Expand Up @@ -6,5 +6,6 @@ export * from './loadAccount'
export * from './resetSelectedAccount'
export * from './setNextSelectedAccount'
export * from './syncBalance'
export * from './syncVotingPower'
export * from './tryCreateAdditionalAccount'
export * from './tryEditSelectedAccountMetadata'
14 changes: 14 additions & 0 deletions packages/shared/lib/core/account/actions/syncVotingPower.ts
@@ -0,0 +1,14 @@
import { get } from 'svelte/store'
import { selectedAccount, updateSelectedAccount } from '../stores'
import { getVotingPower } from '../api/getVotingPower'
import { updateActiveAccount } from '@core/profile'

export async function syncVotingPower(accountIndex: number): Promise<void> {
const votingPower = await getVotingPower(accountIndex)
if (get(selectedAccount)?.index === accountIndex) {
updateSelectedAccount({ votingPower })
} else {
updateActiveAccount(accountIndex, { votingPower })
}
return
}
5 changes: 5 additions & 0 deletions packages/shared/lib/core/account/api/getVotingPower.ts
@@ -0,0 +1,5 @@
import { getAccount } from '@core/profile-manager'

export async function getVotingPower(index?: number): Promise<string> {
return (await getAccount(index))?.getVotingPower()
}
1 change: 1 addition & 0 deletions packages/shared/lib/core/account/api/index.ts
@@ -1,3 +1,4 @@
export * from './createAliasIfNecessary'
export * from './getBalance'
export * from './getVotingPower'
export * from './prepareOutput'
Expand Up @@ -6,4 +6,5 @@ export interface IAccountState extends IAccount, IAccountMetadata {
depositAddress: string
balances: AccountBalance
isTransferring: boolean
votingPower: string
}
Expand Up @@ -62,6 +62,7 @@ export interface IAccount {
meltAmount: HexEncodedAmount,
transactionOptions?: TransactionOptions
): Promise<Transaction>
decreaseVotingPower(amount: string): Promise<Transaction>
destroyAlias(aliasId: string, transactionOptions?: TransactionOptions): Promise<Transaction>
destroyFoundry(foundryId: string, transactionOptions?: TransactionOptions): Promise<Transaction>
generateAddress(options?: AddressGenerationOptions): Promise<Address>
Expand All @@ -72,13 +73,15 @@ export interface IAccount {
getOutput(outputId: string): Promise<OutputData>
getOutputsWithAdditionalUnlockConditions(outputs: OutputsToClaim): Promise<string[]>
getTransaction(transactionId: string): Promise<Transaction>
getVotingPower(): Promise<string>
incomingTransactions(): Promise<[string, [ITransactionPayload, IOutputResponse[]]][]>
increaseNativeTokenSupply(
tokenId: string,
mintAmount: HexEncodedAmount,
increaseNativeTokenSupplyOptions?: IncreaseNativeTokenSupplyOptions,
transactionOptions?: TransactionOptions
): Promise<MintTokenTransaction>
increaseVotingPower(amount: string): Promise<Transaction>
minimumRequiredStorageDeposit(outputs: OutputTypes[]): Promise<string>
mintNativeToken(
nativeTokenOptions: NativeTokenOptions,
Expand Down
1 change: 1 addition & 0 deletions packages/shared/lib/core/governance/actions/index.ts
@@ -0,0 +1 @@
export * from './setVotingPower'
36 changes: 36 additions & 0 deletions packages/shared/lib/core/governance/actions/setVotingPower.ts
@@ -0,0 +1,36 @@
import { get } from 'svelte/store'
import { selectedAccount, updateSelectedAccount } from '@core/account'
import {
addActivityToAccountActivitiesInAllAccountActivities,
generateActivity,
preprocessTransaction,
} from '@core/wallet'
import { Transaction } from '@iota/wallet'

export async function setVotingPower(rawAmount: string): Promise<void> {
try {
const account = get(selectedAccount)
updateSelectedAccount({ isTransferring: true })
const votingPower = parseInt(account.votingPower, 10)
const amount = parseInt(rawAmount, 10)
if (amount > votingPower) {
const amountToIncrease = amount - votingPower
const transaction = await account.increaseVotingPower(amountToIncrease.toString())
await processAndAddToActivities(transaction)
} else if (amount < votingPower) {
const amountToDecrease = votingPower - amount
const transaction = await account.decreaseVotingPower(amountToDecrease.toString())
await processAndAddToActivities(transaction)
}
updateSelectedAccount({ isTransferring: false })
Tuditi marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
updateSelectedAccount({ isTransferring: false })
}
}

async function processAndAddToActivities(transaction: Transaction): Promise<void> {
const account = get(selectedAccount)
const preprocessedTransaction = await preprocessTransaction(transaction, account)
const activity = generateActivity(preprocessedTransaction, account)
addActivityToAccountActivitiesInAllAccountActivities(account.index, activity)
}
1 change: 1 addition & 0 deletions packages/shared/lib/core/governance/index.ts
@@ -1 +1,2 @@
export * from './actions'
export * from './stores'
@@ -1,3 +1,4 @@
import { syncVotingPower } from '@core/account'
import { updateNftInAllAccountNfts } from '@core/nfts'
import { ActivityDirection, ActivityType } from '@core/wallet'
import { updateClaimingTransactionInclusion } from '@core/wallet/actions/activities/updateClaimingTransactionInclusion'
Expand Down Expand Up @@ -33,5 +34,9 @@ export function handleTransactionInclusionEventInternal(
updateNftInAllAccountNfts(accountIndex, activity.nftId, { isSpendable })
}

if (activity.tag === 'PARTICIPATE') {
syncVotingPower(accountIndex)
}

updateClaimingTransactionInclusion(payload.transactionId, payload.inclusionState, accountIndex)
}
14 changes: 14 additions & 0 deletions packages/shared/lib/tests/__mocks__/account.mock.ts
Expand Up @@ -97,9 +97,15 @@ export class AccountMock implements IAccount {
): Promise<Transaction> {
throw new Error('Method not implemented.')
}

decreaseVotingPower(amount: string): Promise<Transaction> {
throw new Error('Method not implemented.')
}

destroyAlias(aliasId: string, transactionOptions?: TransactionOptions): Promise<Transaction> {
throw new Error('Method not implemented.')
}

destroyFoundry(foundryId: string, transactionOptions?: TransactionOptions): Promise<Transaction> {
throw new Error('Method not implemented.')
}
Expand Down Expand Up @@ -155,6 +161,10 @@ export class AccountMock implements IAccount {
return Promise.resolve([''])
}

getVotingPower(): Promise<string> {
throw new Error('Method not implemented.')
}

incomingTransactions(): Promise<[string, [ITransactionPayload, IOutputResponse[]]][]> {
throw new Error('Method not implemented.')
}
Expand All @@ -168,6 +178,10 @@ export class AccountMock implements IAccount {
throw new Error('Method not implemented.')
}

increaseVotingPower(amount: string): Promise<Transaction> {
throw new Error('Method not implemented.')
}

minimumRequiredStorageDeposit(outputs: OutputTypes[]): Promise<string> {
throw new Error('Method not implemented.')
}
Expand Down
5 changes: 5 additions & 0 deletions packages/shared/locales/en.json
Expand Up @@ -1003,6 +1003,11 @@
"faucetRequest": {
"title": "Faucet request",
"body": "Are you sure you want to request {token} tokens from the {network} faucet?"
},
"manageVotingPower": {
"title": "Manage voting power",
"body": "Define your voting power to vote on proposals.",
"hint": "Increasing your voting power locks your funds. In order to make them spendable again, you need to decrease your voting power. This affects all proposals with an active vote."
}
},
"charts": {
Expand Down