Skip to content

Commit

Permalink
feat: adds voting power management (#5337)
Browse files Browse the repository at this point in the history
* feat: draft manage voting power popup

* feat: add slider input and text hint

* feat: adds voting power management functionality

* fixes from review and test

* fix: adds new account methods to account mock

Co-authored-by: Tuditi <daviddetroch@pm.me>
  • Loading branch information
jeeanribeiro and Tuditi committed Dec 5, 2022
1 parent 83999bf commit feb79a4
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 7 deletions.
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 })
} 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

0 comments on commit feb79a4

Please sign in to comment.