Skip to content

Commit 1fa1545

Browse files
committed
Add revocation confirmation screen
1 parent d32c9a3 commit 1fa1545

File tree

2 files changed

+175
-18
lines changed

2 files changed

+175
-18
lines changed

packages/browser-wallet/src/popup/popupX/pages/Web3Id/Web3IdDetails.tsx

Lines changed: 166 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,148 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useMemo, useState } from 'react';
22
import { useTranslation } from 'react-i18next';
3-
import { Navigate, useParams } from 'react-router-dom';
3+
import { Navigate, useNavigate, useParams } from 'react-router-dom';
44
import { useAtomValue } from 'jotai';
5-
import { ContractAddress, HexString } from '@concordium/web-sdk';
5+
import {
6+
AccountAddress,
7+
AccountTransactionType,
8+
ContractAddress,
9+
HexString,
10+
TransactionHash,
11+
} from '@concordium/web-sdk';
612

713
import Page from '@popup/popupX/shared/Page';
814
import Web3IdCard from '@popup/popupX/shared/Web3IdCard';
915
import { storedVerifiableCredentialsAtom } from '@popup/store/verifiable-credential';
10-
import { absoluteRoutes } from '@popup/popupX/constants/routes';
11-
import { networkConfigurationAtom } from '@popup/store/settings';
12-
import { createCredentialId } from '@shared/utils/verifiable-credential-helpers';
16+
import { absoluteRoutes, submittedTransactionRoute } from '@popup/popupX/constants/routes';
17+
import { grpcClientAtom, networkConfigurationAtom } from '@popup/store/settings';
18+
import {
19+
CredentialQueryResponse,
20+
buildRevokeTransaction,
21+
buildRevokeTransactionParameters,
22+
createCredentialId,
23+
getCredentialHolderId,
24+
getCredentialRegistryContractAddress,
25+
getRevokeTransactionExecutionEnergyEstimate,
26+
} from '@shared/utils/verifiable-credential-helpers';
1327
import Button from '@popup/popupX/shared/Button';
1428
import Stop from '@assets/svgX/stop.svg';
1529
import Info from '@assets/svgX/info.svg';
30+
import { useCredentialEntry } from '@popup/popupX/shared/utils/verifiable-credentials';
31+
import FullscreenNotice, { FullscreenNoticeProps } from '@popup/popupX/shared/FullscreenNotice';
32+
import { displayNameOrSplitAddress, useHdWallet, useSelectedCredential } from '@popup/shared/utils/account-helpers';
33+
import { fetchContractName } from '@shared/utils/token-helpers';
34+
import Text from '@popup/popupX/shared/Text';
35+
import { ConfirmedCredential, CreationStatus, VerifiableCredential } from '@shared/storage/types';
36+
import { displayAsCcd, noOp, useAsyncMemo } from 'wallet-common-helpers';
37+
import {
38+
TransactionSubmitError,
39+
TransactionSubmitErrorType,
40+
useGetTransactionFee,
41+
useTransactionSubmit,
42+
} from '@popup/shared/utils/transaction-helpers';
43+
import ErrorMessage from '@popup/popupX/shared/Form/ErrorMessage';
44+
45+
type ConfirmRevocationProps = FullscreenNoticeProps & {
46+
credential: VerifiableCredential;
47+
entry: CredentialQueryResponse | undefined;
48+
walletCred: ConfirmedCredential;
49+
};
50+
51+
function ConfirmRevocation({ credential, entry, walletCred, ...props }: ConfirmRevocationProps) {
52+
const { t } = useTranslation('x', { keyPrefix: 'web3Id.details.confirmRevoke' });
53+
const hdWallet = useHdWallet();
54+
const client = useAtomValue(grpcClientAtom);
55+
const getFee = useGetTransactionFee();
56+
const nav = useNavigate();
57+
const submitTransaction = useTransactionSubmit(
58+
AccountAddress.fromBase58(walletCred.address),
59+
AccountTransactionType.Update
60+
);
61+
const [error, setError] = useState<Error>();
62+
63+
const payload = useAsyncMemo(
64+
async () => {
65+
if (entry === undefined || hdWallet === undefined || credential === undefined) {
66+
return undefined;
67+
}
68+
69+
const contractAddress = getCredentialRegistryContractAddress(credential.id);
70+
const credentialId = getCredentialHolderId(credential.id);
71+
const contractName = await fetchContractName(client, contractAddress.index, contractAddress.subindex);
72+
if (contractName === undefined) {
73+
throw new Error(`Unable to find contract name for address: ${contractAddress}`);
74+
}
75+
76+
const signingKey = hdWallet
77+
.getVerifiableCredentialSigningKey(contractAddress, credential.index)
78+
.toString('hex');
79+
80+
const parameters = await buildRevokeTransactionParameters(
81+
contractAddress,
82+
credentialId,
83+
entry.revocationNonce,
84+
signingKey
85+
);
86+
const maxExecutionEnergy = await getRevokeTransactionExecutionEnergyEstimate(
87+
client,
88+
contractName,
89+
parameters
90+
);
91+
return buildRevokeTransaction(contractAddress, contractName, credentialId, maxExecutionEnergy, parameters);
92+
},
93+
noOp,
94+
[client, credential, hdWallet, entry]
95+
);
96+
const fee = useMemo(
97+
() => (payload === undefined ? undefined : getFee(AccountTransactionType.Update, payload)),
98+
[payload, getFee]
99+
);
100+
101+
const submit = async () => {
102+
if (fee === undefined || payload === undefined) {
103+
throw Error('Fee could not be calculated');
104+
}
105+
try {
106+
const tx = await submitTransaction(payload, fee);
107+
nav(submittedTransactionRoute(TransactionHash.fromHexString(tx)));
108+
} catch (e) {
109+
if (e instanceof Error) {
110+
setError(e);
111+
}
112+
}
113+
};
114+
115+
useEffect(() => {
116+
setError(undefined);
117+
}, [props.open]);
118+
119+
return (
120+
<FullscreenNotice {...props}>
121+
<Page>
122+
<Page.Top heading={t('title')} />
123+
<Text.Capture>{t('description')}</Text.Capture>
124+
<Text.Capture className="m-t-30">
125+
{t('fee')}
126+
<br />
127+
{fee === undefined ? '...' : displayAsCcd(fee, false, true)}
128+
</Text.Capture>
129+
<Text.Capture className="m-t-5">
130+
{t('account')}
131+
<br />
132+
{displayNameOrSplitAddress(walletCred)}
133+
</Text.Capture>
134+
{error instanceof TransactionSubmitError &&
135+
error.type === TransactionSubmitErrorType.InsufficientFunds && (
136+
<ErrorMessage className="m-t-10 text-center">{t('error.insufficientFunds')}</ErrorMessage>
137+
)}
138+
<Page.Footer>
139+
<Button.Main label={t('buttonContinue')} onClick={submit} disabled={payload === undefined} />
140+
<Button.Main label={t('buttonCancel')} onClick={props.onClose} />
141+
</Page.Footer>
142+
</Page>
143+
</FullscreenNotice>
144+
);
145+
}
16146

17147
type Props = {
18148
contract: ContractAddress.Type;
@@ -25,24 +155,42 @@ function Web3IdDetailsParsed({ id, contract }: Props) {
25155
const net = useAtomValue(networkConfigurationAtom);
26156
const credential = verifiableCredentials.value.find((c) => c.id === createCredentialId(id, contract, net));
27157
const [showInfo, setShowInfo] = useState(false);
158+
const [showConfirm, setShowConfirm] = useState(false);
159+
const credentialEntry = useCredentialEntry(credential);
160+
const walletCredential = useSelectedCredential();
28161

29162
if (verifiableCredentials.loading) return null;
30163
if (credential === undefined) throw new Error('Expected to find credential');
31164

32165
return (
33-
<Page>
34-
<Page.Top heading={t('title')}>
35-
<Button.Icon icon={<Stop />} />
36-
<Button.Icon
37-
className="web3-id-details-x__info"
38-
icon={<Info />}
39-
onClick={() => setShowInfo((v) => !v)}
166+
<>
167+
{walletCredential?.status === CreationStatus.Confirmed && (
168+
<ConfirmRevocation
169+
open={showConfirm}
170+
onClose={() => setShowConfirm(false)}
171+
credential={credential}
172+
entry={credentialEntry}
173+
walletCred={walletCredential}
40174
/>
41-
</Page.Top>
42-
<Page.Main>
43-
<Web3IdCard showInfo={showInfo} credential={credential} />
44-
</Page.Main>
45-
</Page>
175+
)}
176+
<Page>
177+
<Page.Top heading={t('title')}>
178+
{walletCredential?.status === CreationStatus.Confirmed &&
179+
credentialEntry?.credentialInfo.holderRevocable &&
180+
credentialEntry !== undefined && (
181+
<Button.Icon icon={<Stop />} onClick={() => setShowConfirm(true)} />
182+
)}
183+
<Button.Icon
184+
className="web3-id-details-x__info"
185+
icon={<Info />}
186+
onClick={() => setShowInfo((v) => !v)}
187+
/>
188+
</Page.Top>
189+
<Page.Main>
190+
<Web3IdCard showInfo={showInfo} credential={credential} />
191+
</Page.Main>
192+
</Page>
193+
</>
46194
);
47195
}
48196

packages/browser-wallet/src/popup/popupX/pages/Web3Id/i18n/en.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ const t = {
1010
},
1111
details: {
1212
title: 'Credential details',
13+
confirmRevoke: {
14+
title: 'Revoke credential',
15+
description: "You're about to revoke your Web3 ID. This is an irreversible action.",
16+
buttonContinue: 'Revoke credential',
17+
buttonCancel: 'Cancel',
18+
fee: 'Estimated transaction fee',
19+
account: 'Selected account',
20+
error: { insufficientFunds: 'Insufficient funds on selected account to cover transaction fee' },
21+
},
1322
},
1423
};
1524

0 commit comments

Comments
 (0)