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: governance voting tab [LW-11519] #3240

Draft
wants to merge 11 commits into
base: chore/LW-11505-new-cardano-wallet
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## vNext

### Features

- Added Cardano governance voting tab ([PR 3240](https://github.com/input-output-hk/daedalus/pull/3240))

### Chores

- Update `cardano-node` to 10.1.1 via `cardano-wallet` [`ba7d3340968`](https://github.com/cardano-foundation/cardano-wallet/commit/ba7d33409680f4e75ef260add8744dcf71f40a77) ([PR 3229](https://github.com/input-output-hk/daedalus/pull/3229))
Expand Down
34 changes: 34 additions & 0 deletions source/common/utils/assertIsBech32WithPrefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { bech32, Decoded } from 'bech32';

const MAX_BECH32_LENGTH_LIMIT = 1023;

const isOneOf = <T>(target: T, options: T | T[]) =>
(Array.isArray(options) && options.includes(target)) || target === options;

export const assertIsBech32WithPrefix = (
target: string,
prefix: string | string[],
expectedDecodedLength?: number | number[]
): void => {
let decoded: Decoded;
try {
decoded = bech32.decode(target, MAX_BECH32_LENGTH_LIMIT);
} catch (error) {
throw new Error(
`expected bech32-encoded string with '${prefix}' prefix; ${error}`
);
}
if (!isOneOf(decoded.prefix, prefix)) {
throw new Error(
`expected bech32 prefix '${prefix}', got '${decoded.prefix}''`
);
}
if (
expectedDecodedLength &&
!isOneOf(decoded.words.length, expectedDecodedLength)
) {
throw new Error(
`expected decoded length of '${expectedDecodedLength}', got '${decoded.words.length}'`
);
}
};
21 changes: 16 additions & 5 deletions source/renderer/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import VotingRegistrationPage from './containers/voting/VotingRegistrationPage';
import { IS_STAKING_INFO_PAGE_AVAILABLE } from './config/stakingConfig';
import AnalyticsConsentPage from './containers/profile/AnalyticsConsentPage';
import TrackedRoute from './analytics/TrackedRoute';
import { Voting } from './containers/voting/Voting';
import VotingGovernancePage from './containers/voting/VotingGovernancePage';

export const Routes = withRouter(() => (
<Route path={ROUTES.ROOT}>
Expand Down Expand Up @@ -205,11 +207,20 @@ export const Routes = withRouter(() => (
component={RedeemItnRewardsContainer}
/>
</Route>
<TrackedRoute
pageTitle="Voting Registration"
path={ROUTES.VOTING.REGISTRATION}
component={VotingRegistrationPage}
/>
<Route path={ROUTES.VOTING.ROOT}>
<Voting>
<TrackedRoute
pageTitle="Voting Registration"
path={ROUTES.VOTING.REGISTRATION}
component={VotingRegistrationPage}
/>
<TrackedRoute
pageTitle="Voting Governance"
path={ROUTES.VOTING.GOVERNANCE}
component={VotingGovernancePage}
/>
</Voting>
</Route>
</Switch>
</Root>
</Route>
Expand Down
2 changes: 2 additions & 0 deletions source/renderer/app/actions/voting-actions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Action from './lib/Action';
import { DelegateVotesParams } from '../api/voting/types';

export default class VotingActions {
selectWallet: Action<string> = new Action();
sendTransaction: Action<{
amount: number;
passphrase: string | null | undefined;
}> = new Action();
delegateVotes: Action<DelegateVotesParams> = new Action();
generateQrCode: Action<number> = new Action();
saveAsPDF: Action<any> = new Action();
saveAsPDFSuccess: Action<any> = new Action();
Expand Down
24 changes: 24 additions & 0 deletions source/renderer/app/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { getPublicKey } from './transactions/requests/getPublicKey';
import { getICOPublicKey } from './transactions/requests/getICOPublicKey';
// Voting requests
import { createWalletSignature } from './voting/requests/createWalletSignature';
import { delegateVotes } from './voting/requests/delegateVotes';
import { getCatalystFund } from './voting/requests/getCatalystFund';
// Wallets requests
import { updateSpendingPassword } from './wallets/requests/updateSpendingPassword';
Expand Down Expand Up @@ -207,6 +208,7 @@ import type {
CreateWalletSignatureRequest,
GetCatalystFundResponse,
CatalystFund,
DelegateVotesParams,
} from './voting/types';
import type { StakePoolProps } from '../domains/StakePool';
import type { FaultInjectionIpcRequest } from '../../../common/types/cardano-node.types';
Expand Down Expand Up @@ -2747,6 +2749,28 @@ export default class AdaApi {
throw new ApiError(error);
}
};

delegateVotes = async (params: DelegateVotesParams) => {
logger.debug('AdaApi::delegateVotes called', {
parameters: filterLogData(params),
});

try {
const response = await delegateVotes(this.config, params);
logger.debug('AdaApi::delegateVotes success', {
response,
});

return response;
} catch (error) {
logger.debug('AdaApi::delegateVotes error', {
error,
});

throw new ApiError(error);
}
};

createVotingRegistrationTransaction = async (
request: CreateVotingRegistrationRequest
): Promise<WalletTransaction> => {
Expand Down
18 changes: 18 additions & 0 deletions source/renderer/app/api/voting/requests/delegateVotes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { request } from '../../utils/request';
import { RequestConfig } from '../../common/types';
import { Transaction } from '../../transactions/types';
import { DelegateVotesParams } from '../types';

export const delegateVotes = (
config: RequestConfig,
{ dRepId, passphrase, walletId }: DelegateVotesParams
): Promise<Transaction> =>
request(
{
...config,
method: 'PUT',
path: `/v2/dreps/${dRepId}/wallets/${walletId}`,
},
{},
{ passphrase }
);
6 changes: 6 additions & 0 deletions source/renderer/app/api/voting/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,9 @@ export type CatalystFund = {
registrationSnapshotTime: Date;
};
};

export type DelegateVotesParams = {
dRepId: string;
passphrase: string;
walletId: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { defineMessages } from 'react-intl';

export const messages = defineMessages({
abstain: {
id: 'voting.governance.abstain',
defaultMessage: '!!!Abstain',
description: 'Translation for the "abstain" governance vote type',
},
delegateToDRep: {
id: 'voting.governance.delegateToDRep',
defaultMessage: '!!!Delegate to DRep',
description: 'Translation for the "delegate to DRep" governance vote type',
},
drepInputLabel: {
id: 'voting.governance.drepInputLabel',
defaultMessage: '!!!Please type or paste a valid DRep ID here. Look up',
description: 'Label for DRep input on the governance page',
},
drepInputLabelLink: {
id: 'voting.governance.drepInputLabelLink',
defaultMessage: '!!!DRep directory',
description: 'Label link for DRep input on the governance page',
},
drepInputError: {
id: 'voting.governance.drepInputError',
defaultMessage: '!!!Invalid DRep ID',
description: 'Error for DRep input on the governance page',
},
drepInputPlaceholder: {
id: 'voting.governance.drepInputPlaceholder',
defaultMessage: '!!!Paste DRep ID here …',
description: 'Placeholder for DRep input on the governance page',
},
heading: {
id: 'voting.governance.heading',
defaultMessage: '!!!CARDANO VOTING POWER DELEGATION',
description: 'Headline for Governance',
},
learnMoreLinkLabel: {
id: 'voting.governance.learnMoreLinkLabel',
defaultMessage: '!!!Governance link label',
description: 'Link labels for governance page',
},
noConfidence: {
id: 'voting.governance.noConfidence',
defaultMessage: '!!!No Confidence',
description: 'Translation for the "no confidence" governance vote type',
},
paragraph1: {
id: 'voting.governance.paragraph1',
defaultMessage: '!!!Governance first paragraph',
description: 'First paragraph for governance page',
},
paragraph1LinkUrl: {
id: 'voting.governance.paragraph1LinkUrl',
defaultMessage: '!!!Governance first paragraph link url',
description: 'First paragraph link for governance page',
},
selectWalletLabel: {
id: 'voting.governance.selectWalletLabel',
defaultMessage: '!!!Select a wallet to delegate from',
description: 'Label for the wallet select on the governance page',
},
selectWalletPlaceholder: {
id: 'voting.governance.selectWalletPlaceholder',
defaultMessage: '!!!Select a wallet …',
description: 'Placeholder for the wallet select on the governance page',
},
selectVotingTypeLabel: {
id: 'voting.governance.selectVotingTypeLabel',
defaultMessage: '!!!Select voting registration type',
description:
'Label for the registration type select on the governance page',
},
submitLabel: {
id: 'voting.governance.submitLabel',
defaultMessage: '!!!Submit',
description: 'Label for the submit button on the governance page',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@import '../votingConfig';

.component {
flex: 1 0 0;
padding: 20px;
}

.heading {
@extend %accentText;
font-family: var(--font-semibold);
font-size: 18px;
letter-spacing: 2px;
margin-bottom: 14px;
text-align: center;
text-transform: uppercase;
}

.info {
@extend %regularText;
p {
display: block;
margin-bottom: 1em;
}
}

.walletSelect {
margin-top: 20px;

&.error {
input {
border-color: var(--theme-color-error);
}

:global {
.SimpleSelect_selectInput {
&:after {
background-color: var(--theme-color-error);
}
}
}
}

:global {
.SimpleOptions_option {
align-items: center;
display: flex;
height: 50px;
padding-bottom: 0;
padding-top: 0;
}
}
}

.voteTypeSelect,
.drepInput,
.voteSubmit {
margin-top: 40px;
}

.voteSubmit {
width: 100%;
}
Loading