1
- import React from 'react' ;
2
- import Page from '@popup/popupX/shared/Page' ;
1
+ import React , { useEffect , useMemo , useState } from 'react' ;
3
2
import { useTranslation } from 'react-i18next' ;
4
- import Web3IdCard from '@popup/popupX/shared/Web3IdCard ' ;
3
+ import { Navigate , useNavigate , useParams } from 'react-router-dom ' ;
5
4
import { useAtomValue } from 'jotai' ;
5
+ import {
6
+ AccountAddress ,
7
+ AccountTransactionType ,
8
+ ContractAddress ,
9
+ HexString ,
10
+ TransactionHash ,
11
+ } from '@concordium/web-sdk' ;
12
+
13
+ import Page from '@popup/popupX/shared/Page' ;
14
+ import Web3IdCard from '@popup/popupX/shared/Web3IdCard' ;
6
15
import { storedVerifiableCredentialsAtom } from '@popup/store/verifiable-credential' ;
7
- import { ContractAddress , HexString } from '@concordium/web-sdk' ;
8
- import { Navigate , useParams } from 'react-router-dom' ;
9
- import { absoluteRoutes } from '@popup/popupX/constants/routes' ;
10
- import { networkConfigurationAtom } from '@popup/store/settings' ;
11
- 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' ;
27
+ import Button from '@popup/popupX/shared/Button' ;
28
+ import Stop from '@assets/svgX/stop.svg' ;
29
+ import Info from '@assets/svgX/info.svg' ;
30
+ import { useCredentialEntry , useCredentialStatus } 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 {
36
+ ConfirmedCredential ,
37
+ CreationStatus ,
38
+ VerifiableCredential ,
39
+ VerifiableCredentialStatus ,
40
+ } from '@shared/storage/types' ;
41
+ import { displayAsCcd , noOp , useAsyncMemo } from 'wallet-common-helpers' ;
42
+ import {
43
+ TransactionSubmitError ,
44
+ TransactionSubmitErrorType ,
45
+ useGetTransactionFee ,
46
+ useTransactionSubmit ,
47
+ } from '@popup/shared/utils/transaction-helpers' ;
48
+ import ErrorMessage from '@popup/popupX/shared/Form/ErrorMessage' ;
49
+ import { CIS4RevokeSubmittedLocationState } from '../SubmittedTransaction/SubmittedTransaction' ;
50
+
51
+ type ConfirmRevocationProps = FullscreenNoticeProps & {
52
+ credential : VerifiableCredential ;
53
+ entry : CredentialQueryResponse | undefined ;
54
+ walletCred : ConfirmedCredential ;
55
+ } ;
56
+
57
+ function ConfirmRevocation ( { credential, entry, walletCred, ...props } : ConfirmRevocationProps ) {
58
+ const { t } = useTranslation ( 'x' , { keyPrefix : 'web3Id.details.confirmRevoke' } ) ;
59
+ const hdWallet = useHdWallet ( ) ;
60
+ const client = useAtomValue ( grpcClientAtom ) ;
61
+ const getFee = useGetTransactionFee ( ) ;
62
+ const nav = useNavigate ( ) ;
63
+ const submitTransaction = useTransactionSubmit (
64
+ AccountAddress . fromBase58 ( walletCred . address ) ,
65
+ AccountTransactionType . Update
66
+ ) ;
67
+ const [ error , setError ] = useState < Error > ( ) ;
68
+
69
+ const payload = useAsyncMemo (
70
+ async ( ) => {
71
+ if ( entry === undefined || hdWallet === undefined || credential === undefined ) {
72
+ return undefined ;
73
+ }
74
+
75
+ const contractAddress = getCredentialRegistryContractAddress ( credential . id ) ;
76
+ const credentialId = getCredentialHolderId ( credential . id ) ;
77
+ const contractName = await fetchContractName ( client , contractAddress . index , contractAddress . subindex ) ;
78
+ if ( contractName === undefined ) {
79
+ throw new Error ( `Unable to find contract name for address: ${ contractAddress } ` ) ;
80
+ }
81
+
82
+ const signingKey = hdWallet
83
+ . getVerifiableCredentialSigningKey ( contractAddress , credential . index )
84
+ . toString ( 'hex' ) ;
85
+
86
+ const parameters = await buildRevokeTransactionParameters (
87
+ contractAddress ,
88
+ credentialId ,
89
+ entry . revocationNonce ,
90
+ signingKey
91
+ ) ;
92
+ const maxExecutionEnergy = await getRevokeTransactionExecutionEnergyEstimate (
93
+ client ,
94
+ contractName ,
95
+ parameters
96
+ ) ;
97
+ return buildRevokeTransaction ( contractAddress , contractName , credentialId , maxExecutionEnergy , parameters ) ;
98
+ } ,
99
+ noOp ,
100
+ [ client , credential , hdWallet , entry ]
101
+ ) ;
102
+ const fee = useMemo (
103
+ ( ) => ( payload === undefined ? undefined : getFee ( AccountTransactionType . Update , payload ) ) ,
104
+ [ payload , getFee ]
105
+ ) ;
106
+
107
+ const submit = async ( ) => {
108
+ if ( fee === undefined || payload === undefined ) {
109
+ throw Error ( 'Fee could not be calculated' ) ;
110
+ }
111
+ try {
112
+ const tx = await submitTransaction ( payload , fee ) ;
113
+ const submittedState : CIS4RevokeSubmittedLocationState = { type : 'cis4.revoke' } ;
114
+ nav ( submittedTransactionRoute ( TransactionHash . fromHexString ( tx ) ) , { state : submittedState } ) ;
115
+ } catch ( e ) {
116
+ if ( e instanceof Error ) {
117
+ setError ( e ) ;
118
+ }
119
+ }
120
+ } ;
121
+
122
+ useEffect ( ( ) => {
123
+ setError ( undefined ) ;
124
+ } , [ props . open ] ) ;
125
+
126
+ return (
127
+ < FullscreenNotice { ...props } >
128
+ < Page >
129
+ < Page . Top heading = { t ( 'title' ) } />
130
+ < Text . Capture > { t ( 'description' ) } </ Text . Capture >
131
+ < Text . Capture className = "m-t-30" >
132
+ { t ( 'fee' ) }
133
+ < br />
134
+ { fee === undefined ? '...' : displayAsCcd ( fee , false , true ) }
135
+ </ Text . Capture >
136
+ < Text . Capture className = "m-t-5" >
137
+ { t ( 'account' ) }
138
+ < br />
139
+ { displayNameOrSplitAddress ( walletCred ) }
140
+ </ Text . Capture >
141
+ { error instanceof TransactionSubmitError &&
142
+ error . type === TransactionSubmitErrorType . InsufficientFunds && (
143
+ < ErrorMessage className = "m-t-10 text-center" > { t ( 'error.insufficientFunds' ) } </ ErrorMessage >
144
+ ) }
145
+ < Page . Footer >
146
+ < Button . Main label = { t ( 'buttonContinue' ) } onClick = { submit } disabled = { payload === undefined } />
147
+ < Button . Main label = { t ( 'buttonCancel' ) } onClick = { props . onClose } />
148
+ </ Page . Footer >
149
+ </ Page >
150
+ </ FullscreenNotice >
151
+ ) ;
152
+ }
12
153
13
154
type Props = {
14
155
contract : ContractAddress . Type ;
@@ -20,17 +161,46 @@ function Web3IdDetailsParsed({ id, contract }: Props) {
20
161
const verifiableCredentials = useAtomValue ( storedVerifiableCredentialsAtom ) ;
21
162
const net = useAtomValue ( networkConfigurationAtom ) ;
22
163
const credential = verifiableCredentials . value . find ( ( c ) => c . id === createCredentialId ( id , contract , net ) ) ;
164
+ const [ showInfo , setShowInfo ] = useState ( false ) ;
165
+ const [ showConfirm , setShowConfirm ] = useState ( false ) ;
166
+ const credentialEntry = useCredentialEntry ( credential ) ;
167
+ const walletCredential = useSelectedCredential ( ) ;
168
+ const status = useCredentialStatus ( credential ) ;
23
169
24
170
if ( verifiableCredentials . loading ) return null ;
25
171
if ( credential === undefined ) throw new Error ( 'Expected to find credential' ) ;
26
172
173
+ const canRevoke =
174
+ walletCredential ?. status === CreationStatus . Confirmed &&
175
+ credentialEntry ?. credentialInfo . holderRevocable &&
176
+ status !== undefined &&
177
+ [ VerifiableCredentialStatus . Active , VerifiableCredentialStatus . NotActivated ] . includes ( status ) ;
178
+
27
179
return (
28
- < Page >
29
- < Page . Top heading = { t ( 'title' ) } />
30
- < Page . Main >
31
- < Web3IdCard credential = { credential } />
32
- </ Page . Main >
33
- </ Page >
180
+ < >
181
+ { canRevoke && (
182
+ < ConfirmRevocation
183
+ open = { showConfirm }
184
+ onClose = { ( ) => setShowConfirm ( false ) }
185
+ credential = { credential }
186
+ entry = { credentialEntry }
187
+ walletCred = { walletCredential }
188
+ />
189
+ ) }
190
+ < Page >
191
+ < Page . Top heading = { t ( 'title' ) } >
192
+ { canRevoke && < Button . Icon icon = { < Stop /> } onClick = { ( ) => setShowConfirm ( true ) } /> }
193
+ < Button . Icon
194
+ className = "web3-id-details-x__info"
195
+ icon = { < Info /> }
196
+ onClick = { ( ) => setShowInfo ( ( v ) => ! v ) }
197
+ />
198
+ </ Page . Top >
199
+ < Page . Main >
200
+ < Web3IdCard showInfo = { showInfo } credential = { credential } />
201
+ </ Page . Main >
202
+ </ Page >
203
+ </ >
34
204
) ;
35
205
}
36
206
0 commit comments