diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index d17e1bf65e..375e550392 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -128,7 +128,6 @@ const CredentialCard11: React.FC = ({ const secondaryField = overlay?.presentationFields?.find( (field) => field.name === overlay?.brandingOverlay?.secondaryAttribute ) - return [...(displayItems ?? []), primaryField, secondaryField] }, [displayItems, overlay]) @@ -224,12 +223,6 @@ const CredentialCard11: React.FC = ({ borderRadius: 15, borderColor: ColorPallet.semantic.focus, }, - seperator: { - width: '100%', - height: 2, - marginVertical: 10, - backgroundColor: ColorPallet.grayscale.lightGrey, - }, credActionText: { fontSize: 20, fontWeight: TextTheme.bold.fontWeight, @@ -240,6 +233,9 @@ const CredentialCard11: React.FC = ({ const backgroundColorIfErrorState = (backgroundColor?: string) => error || predicateError || isProofRevoked ? ColorPallet.notification.errorBorder : backgroundColor + const backgroundColorIfRevoked = (backgroundColor?: string) => + isProofRevoked ? ColorPallet.notification.errorBorder : backgroundColor + const fontColorWithHighContrast = () => { if (proof) { return ColorPallet.grayscale.mediumGrey @@ -382,7 +378,6 @@ const CredentialCard11: React.FC = ({ const AttributeLabel: React.FC<{ label: string }> = ({ label }) => { const ylabel = overlay.bundle?.labelOverlay?.attributeLabels[label] ?? startCase(label) - return ( = ({ ) } + const AttributeErrorLabel: React.FC<{ errorMessage: string }> = ({ errorMessage }) => { + return ( + + {errorMessage} + + ) + } + const AttributeValue: React.FC<{ value: string | number | null; warn?: boolean }> = ({ value, warn }) => { return ( <> {isDataUrl(value) ? ( - + ) : ( = ({ item && ( {!(item?.value || item?.satisfied) ? ( - - - + + + + + {item.hasError && ( + + + + + )} ) : ( )} + {/* Rendering attribute values */} {!(item?.value || item?.pValue) ? null : ( {flaggedAttributes?.includes(label) && !item.pValue && !allPI && proof && ( @@ -453,12 +478,7 @@ const CredentialCard11: React.FC = ({ size={ListItems.recordAttributeText.fontSize} /> )} - {!error ? ( - - ) : null} + )} {!error && item?.satisfied != undefined && item?.satisfied === false ? ( @@ -512,12 +532,12 @@ const CredentialCard11: React.FC = ({ - {(error || isProofRevoked) && ( + {isProofRevoked && ( - {error ? t('ProofRequest.NotAvailableInYourWallet') : t('CredentialDetails.Revoked')} + {t('CredentialDetails.Revoked')} )} @@ -555,7 +575,7 @@ const CredentialCard11: React.FC = ({ style={[ styles.secondaryBodyContainer, { - backgroundColor: backgroundColorIfErrorState(styles.secondaryBodyContainer.backgroundColor), + backgroundColor: backgroundColorIfRevoked(styles.secondaryBodyContainer.backgroundColor), overflow: 'hidden', }, ]} diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index 8b2fdb6f42..d2064bc6a0 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -644,6 +644,7 @@ const translation = { "CredentialMetadataNotFound": "Could not find credential metadata", "NewProofRequest": "New Proof Request", "NotAvailableInYourWallet": "Not in your wallet", + "MissingAttribute": "{{ name }} is missing", "PredicateNotSatisfied": "Requirement not met", "IsRequesting": "is requesting", "IsRequestingSomethingYouDontHaveAvailable": "is requesting something you don't have available", diff --git a/packages/legacy/core/App/localization/fr/index.ts b/packages/legacy/core/App/localization/fr/index.ts index df976bb70e..4bba33565b 100644 --- a/packages/legacy/core/App/localization/fr/index.ts +++ b/packages/legacy/core/App/localization/fr/index.ts @@ -636,6 +636,7 @@ const translation = { "CredentialMetadataNotFound": "Impossible de trouver les métadonnées des attestations", "NewProofRequest": "Nouvelle demande de preuve", "NotAvailableInYourWallet": "Non disponible dans votre portefeuille", + "MissingAttribute": "{{ name }} is missing (FR)", "PredicateNotSatisfied": "Requête non satisfaite", "IsRequesting": "demande", "IsRequestingSomethingYouDontHaveAvailable": "demande quelque chose que vous n'avez pas à votre disposition", diff --git a/packages/legacy/core/App/localization/pt-br/index.ts b/packages/legacy/core/App/localization/pt-br/index.ts index d38e4724c3..fdcb0d51e9 100644 --- a/packages/legacy/core/App/localization/pt-br/index.ts +++ b/packages/legacy/core/App/localization/pt-br/index.ts @@ -620,6 +620,7 @@ const translation = { "CredentialMetadataNotFound": "Não foi possível encontrar metadados de credenciais", "NewProofRequest": "Nova Prova de Credencial", "NotAvailableInYourWallet": "Não disponível em sua carteira", + "MissingAttribute": "{{ name }} is missing (PT-BR)", "PredicateNotSatisfied": "Requisito não atendido", "IsRequesting": "está requisitando", "IsRequestingSomethingYouDontHaveAvailable": "está requisitando algo que você não tem disponível", diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 36528a2664..8fd1dc14b7 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -200,7 +200,7 @@ const ProofRequest: React.FC = ({ navigation, proofId }) => { fields: { [key: string]: Attribute[] & Predicate[] } - ) => { + ): boolean => { const revList = credExRecords.map((cred) => { return { id: cred.credentials.map((item) => item.credentialRecordId), @@ -238,6 +238,7 @@ const ProofRequest: React.FC = ({ navigation, proofId }) => { setLoading(false) setDescriptorMetadata(descriptorMetadata) + // Credentials that satisfy the proof request let credList: string[] = [] if (selectedCredentials.length > 0) { credList = selectedCredentials @@ -282,6 +283,7 @@ const ProofRequest: React.FC = ({ navigation, proofId }) => { >, } : undefined + setRetrievedCredentials(selectRetrievedCredentials) const activeCreds = groupedProof.filter((item: any) => credList.includes(item.credId)) @@ -294,7 +296,7 @@ const ProofRequest: React.FC = ({ navigation, proofId }) => { return { ...prev, [current.credId]: current.attributes ?? current.predicates ?? [] } }, {}) } - + // Check for revoked credentials const records = fullCredentials.filter((record: any) => record.credentials.some((cred: any) => credList.includes(cred.credentialRecordId)) ) @@ -692,32 +694,6 @@ const ProofRequest: React.FC = ({ navigation, proofId }) => { /> {!hasAvailableCredentials && ( - {!(loading || attestationLoading) && ( - <> - {hasMatchingCredDef && ( - - )} - - {t('ProofRequest.MissingCredentials')} - - - )} - - } footer={proofPageFooter()} items={activeCreds.filter((cred) => cred.credExchangeRecord === undefined) ?? []} /> diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index df72d478c1..d840757f65 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -22,6 +22,7 @@ import { ProofState, parseDid, OutOfBandRecord, + CredentialPreviewAttribute, } from '@credo-ts/core' import { BasicMessageRole } from '@credo-ts/core/build/modules/basic-messages/BasicMessageRole' import { useConnectionById } from '@credo-ts/react-hooks' @@ -432,7 +433,13 @@ export const evaluatePredicates = }) } -const addMissingDisplayAttributes = (attrReq: AnonCredsRequestedAttribute) => { +// scans through requested attributes and records +// Builds and returns label: value attributes +// Flagging any attribute not found in wallet credentials +const addMissingDisplayAttributes = ( + attrReq: AnonCredsRequestedAttribute, + records: CredentialExchangeRecord[] +): ProofCredentialAttributes => { const { name, names, restrictions } = attrReq const credName = credNameFromRestriction(restrictions) const credDefId = credDefIdFromRestrictions(restrictions) @@ -449,47 +456,81 @@ const addMissingDisplayAttributes = (attrReq: AnonCredsRequestedAttribute) => { attributes: [] as Attribute[], } + // Filter records for requested schema id of credential definition id + const filteredCredentialRecords = records.filter( + (record: CredentialExchangeRecord) => + getCredentialSchemaIdForRecord(record) === schemaId || getCredentialDefinitionIdForRecord(record) === credDefId + ) + + // for reach requested attribute + // scan through filtered credential attributes + // display any that are found + // flag any not found attributes with an error for (const attributeName of [...(names ?? (name && [name]) ?? [])]) { - processedAttributes.attributes?.push( - new Attribute({ - revoked: false, - credentialId: credName, - name: attributeName, - value: '', - }) - ) + // filter through credential record attributes for a given name + const [attribute] = filteredCredentialRecords.map((item: CredentialExchangeRecord) => { + return item.credentialAttributes?.filter( + (attribute: CredentialPreviewAttribute) => attribute.name === attributeName + ) + }) + if (attribute && attribute.length > 0) { + // attribute found, display as expected + processedAttributes.attributes?.push( + new Attribute({ + revoked: false, + credentialId: credName, + name: attributeName, + value: attribute[0].value, + hasError: false, + }) + ) + } else { + // no attribute found, flag this with an error + processedAttributes.attributes?.push( + new Attribute({ + revoked: false, + credentialId: credName, + name: attributeName, + value: '', + hasError: true, + }) + ) + } } return processedAttributes } export const processProofAttributes = ( + t: TFunction<'translation', undefined>, request?: AnonCredsProofRequest, - credentials?: AnonCredsCredentialsForProofRequest, - credentialRecords?: CredentialExchangeRecord[], + credentials?: AnonCredsCredentialsForProofRequest, // credentials that 100% validate the proof request + credentialRecords?: CredentialExchangeRecord[], // all the credentials in the wallet groupByReferent?: boolean ): { [key: string]: ProofCredentialAttributes } => { const processedAttributes = {} as { [key: string]: ProofCredentialAttributes } - const requestedProofAttributes = request?.requested_attributes const retrievedCredentialAttributes = credentials?.attributes const requestNonRevoked = request?.non_revoked // non_revoked interval can sometimes be top level + // no proof or credential attributes, nothing to process, leave early if (!requestedProofAttributes || !retrievedCredentialAttributes) { return {} } + for (const key of Object.keys(retrievedCredentialAttributes)) { const altCredentials = [...(retrievedCredentialAttributes[key] ?? [])] .sort(credentialSortFn) .map((cred) => cred.credentialId) const credentialList = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn) - const { name, names, non_revoked, restrictions } = requestedProofAttributes[key] + // system assumes one of these values is present in a proof request const proofCredDefId = credDefIdFromRestrictions(restrictions) const proofSchemaId = schemaIdFromRestrictions(restrictions) + // No credentials satisfy proof request, process attribute errors if (credentialList.length <= 0) { - const missingAttributes = addMissingDisplayAttributes(requestedProofAttributes[key]) + const missingAttributes = addMissingDisplayAttributes(requestedProofAttributes[key], credentialRecords ?? []) const missingCredGroupKey = groupByReferent ? key : missingAttributes.credName if (!processedAttributes[missingCredGroupKey]) { processedAttributes[missingCredGroupKey] = missingAttributes @@ -497,7 +538,6 @@ export const processProofAttributes = ( processedAttributes[missingCredGroupKey].attributes?.push(...(missingAttributes.attributes ?? [])) } } - //iterate over all credentials that satisfy the proof for (const credential of credentialList) { let credName = key @@ -547,6 +587,7 @@ export const processProofAttributes = ( } } } + return processedAttributes } @@ -827,6 +868,7 @@ export const retrieveCredentialsForProof = async ( const filtered = filterInvalidProofRequestMatches(anonCredsCredentialsForRequest, descriptorMetadata) const processedAttributes = processProofAttributes( + t, anonCredsProofRequest, filtered, fullCredentials, @@ -851,7 +893,7 @@ export const retrieveCredentialsForProof = async ( const proofRequest = format.request?.anoncreds ?? format.request?.indy const proofFormat = credentials.proofFormats.anoncreds ?? credentials.proofFormats.indy - const attributes = processProofAttributes(proofRequest, proofFormat, fullCredentials, groupByReferent) + const attributes = processProofAttributes(t, proofRequest, proofFormat, fullCredentials, groupByReferent) const predicates = processProofPredicates(proofRequest, proofFormat, fullCredentials, groupByReferent) const groupedProof = Object.values(mergeAttributesAndPredicates(attributes, predicates)) @@ -1098,6 +1140,18 @@ export function isChildFunction(children: ReactNode | ChildFn): children i return typeof children === 'function' } +// Fetches the credential definition id for a given record, returns null if ID is not set +const getCredentialDefinitionIdForRecord = (record: CredentialExchangeRecord): string | null => { + // assumes record is anonCred + return record.metadata.get('_anoncreds/credential')?.credentialDefinitionId ?? null +} + +// Fetches the schema id for a given record, returns null if ID is not set +const getCredentialSchemaIdForRecord = (record: CredentialExchangeRecord): string | null => { + // assumes record is anonCred + return record.metadata.get('_anoncreds/credential')?.schemaId ?? null +} + export function getCredentialEventRole(record: CredentialExchangeRecord) { switch (record.state) { // assuming only Holder states are supported here diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap index 7b8f058406..941fafe6ed 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/Connection.test.tsx.snap @@ -564,15 +564,6 @@ exports[`Connection Screen No connection, show to proof 1`] = ` } - ListHeaderComponent={ - - } data={Array []} getItem={[Function]} getItemCount={[Function]} @@ -592,18 +583,6 @@ exports[`Connection Screen No connection, show to proof 1`] = ` viewabilityConfigCallbackPairs={Array []} > - - - @@ -809,6 +788,7 @@ exports[`Connection Screen Valid goal code aries.vc.issue extracted, show to off "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": "text/plain", "name": "student_first_name", @@ -823,6 +803,7 @@ exports[`Connection Screen Valid goal code aries.vc.issue extracted, show to off "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": "text/plain", "name": "student_last_name", @@ -837,6 +818,7 @@ exports[`Connection Screen Valid goal code aries.vc.issue extracted, show to off "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": "text/plain", "name": "expiry_date", diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap index 8a1dc6b89a..7148e4f670 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap @@ -166,6 +166,7 @@ exports[`CredentialOffer Screen accepting a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "name", @@ -180,6 +181,7 @@ exports[`CredentialOffer Screen accepting a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "date", @@ -194,6 +196,7 @@ exports[`CredentialOffer Screen accepting a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "degree", @@ -208,6 +211,7 @@ exports[`CredentialOffer Screen accepting a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "birthdate_dateint", @@ -222,6 +226,7 @@ exports[`CredentialOffer Screen accepting a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "timestamp", @@ -1551,6 +1556,7 @@ exports[`CredentialOffer Screen declining a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "name", @@ -1565,6 +1571,7 @@ exports[`CredentialOffer Screen declining a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "date", @@ -1579,6 +1586,7 @@ exports[`CredentialOffer Screen declining a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "degree", @@ -1593,6 +1601,7 @@ exports[`CredentialOffer Screen declining a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "birthdate_dateint", @@ -1607,6 +1616,7 @@ exports[`CredentialOffer Screen declining a credential 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "timestamp", @@ -3161,6 +3171,7 @@ exports[`CredentialOffer Screen renders correctly 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "name", @@ -3175,6 +3186,7 @@ exports[`CredentialOffer Screen renders correctly 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "date", @@ -3189,6 +3201,7 @@ exports[`CredentialOffer Screen renders correctly 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "degree", @@ -3203,6 +3216,7 @@ exports[`CredentialOffer Screen renders correctly 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "birthdate_dateint", @@ -3217,6 +3231,7 @@ exports[`CredentialOffer Screen renders correctly 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "timestamp", diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/ProofDetails.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/ProofDetails.test.tsx.snap index 7e26e80cff..3eb27d702d 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/ProofDetails.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/ProofDetails.test.tsx.snap @@ -474,6 +474,7 @@ exports[`ProofDetails Screen with a verified proof record renders correctly when "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "first_name", @@ -488,6 +489,7 @@ exports[`ProofDetails Screen with a verified proof record renders correctly when "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "second_name", @@ -1025,6 +1027,7 @@ exports[`ProofDetails Screen with a verified proof record renders correctly when "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "first_name", @@ -1039,6 +1042,7 @@ exports[`ProofDetails Screen with a verified proof record renders correctly when "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "second_name", diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/ProofRequestDetails.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/ProofRequestDetails.test.tsx.snap index b83ac42fb3..af74f59ce7 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/ProofRequestDetails.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/ProofRequestDetails.test.tsx.snap @@ -229,6 +229,7 @@ exports[`ProofRequestDetails Screen Renders correctly 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": "First Name", "mimeType": undefined, "name": "student_first_name", @@ -247,6 +248,7 @@ exports[`ProofRequestDetails Screen Renders correctly 1`] = ` "credentialId": undefined, "encoding": undefined, "format": undefined, + "hasError": undefined, "label": "Last Name", "mimeType": undefined, "name": "student_last_name", diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/W3cProofRequest.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/W3cProofRequest.test.tsx.snap index 6253f673cd..1639952fb0 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/W3cProofRequest.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/W3cProofRequest.test.tsx.snap @@ -127,6 +127,7 @@ exports[`displays a proof request screen ProofRequest Screen, W3C displays a pro "credentialId": "8eba4449-8a85-4954-b11c-e0590f39cbdb", "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "email", @@ -146,6 +147,7 @@ exports[`displays a proof request screen ProofRequest Screen, W3C displays a pro "credentialId": "8eba4449-8a85-4954-b11c-e0590f39cbdb", "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "time", @@ -529,6 +531,7 @@ exports[`displays a proof request screen ProofRequest Screen, W3C displays a pro "credentialId": "8eba4449-8a85-4954-b11c-e0590f39cbdb", "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "email", @@ -548,6 +551,7 @@ exports[`displays a proof request screen ProofRequest Screen, W3C displays a pro "credentialId": "8eba4449-8a85-4954-b11c-e0590f39cbdb", "encoding": undefined, "format": undefined, + "hasError": undefined, "label": undefined, "mimeType": undefined, "name": "time", @@ -811,40 +815,6 @@ exports[`displays a proof request screen ProofRequest Screen, W3C displays a pro } - ListHeaderComponent={ - - - - - ProofRequest.MissingCredentials - - - - } data={ Array [ Object { @@ -895,41 +865,6 @@ exports[`displays a proof request screen ProofRequest Screen, W3C displays a pro viewabilityConfigCallbackPairs={Array []} > - - - - - ProofRequest.MissingCredentials - - - - - -  - - - ProofRequest.NotAvailableInYourWallet - - + + + + Age + + + -  - - - Age + ProofRequest.PredicateLe 18 -