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

Rifeljm/#1699 verifiable credentials #1785

Merged
merged 9 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/api-react/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export const {
useGetVCListQuery,
useSpendVCMutation,
useAddVCProofsMutation,
useGetProofsForRootMutation,
useGetProofsForRootQuery,
useRevokeVCMutation,
} = wallet;

Expand Down
4 changes: 2 additions & 2 deletions packages/api-react/src/services/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1411,7 +1411,7 @@ export const walletApi = apiWithTag.injectEndpoints({

addVCProofs: mutation(build, VC, 'addVCProofs'),

getProofsForRoot: mutation(build, VC, 'getProofsForRoot'),
getProofsForRoot: query(build, VC, 'getProofsForRoot'),

revokeVC: mutation(build, VC, 'revokeVC'),
}),
Expand Down Expand Up @@ -1525,6 +1525,6 @@ export const {
useGetVCListQuery,
useSpendVCMutation,
useAddVCProofsMutation,
useGetProofsForRootMutation,
useGetProofsForRootQuery,
useRevokeVCMutation,
} = walletApi;
16 changes: 8 additions & 8 deletions packages/api/src/wallets/VC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@ type TransactionType = {
};

export default class VCWallet extends Wallet {
async getVC(args: { vcId: number }) {
async getVC(args: { vcId: string }) {
return this.command<{
success: boolean;
vcRecord: VCType;
}>('vc_get_vc', args);
}>('vc_get', args);
}

async getVCList(args: { start: number; count: number }) {
async getVCList(args: { start?: number; count?: number }) {
return this.command<{
proofs: any;
success: boolean;
vcRecords: VCRecordType[];
}>('vc_get_vc_list', args);
}>('vc_get_list', args);
}

async spendVC(args: {
Expand All @@ -101,22 +101,22 @@ export default class VCWallet extends Wallet {
return this.command<{
success: boolean;
transactions: TransactionType[];
}>('vc_spend_vc', args);
}>('vc_spend', args);
}

async addVCProofs(proofs: string) {
return this.command<{
proofs: any;
}>('add_vc_proofs', { proofs });
}>('vc_add_proofs', { proofs });
}

async getProofsForRoot(root: string) {
return this.command<{ proofs: any }>('get_proofs_for_root', { root });
return this.command<{ proofs: any }>('vc_get_proofs_for_root', { root });
}

async revokeVC(args: { vcParentId: string; fee: number; reusePuzhash: boolean }) {
return this.command<{
transactions: TransactionType[];
}>('vc_revoke_vc', args);
}>('vc_revoke', args);
}
}
1 change: 1 addition & 0 deletions packages/core/src/theme/dark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export default createTheme({
main: Color.Neutral[100],
border: Color.Neutral[300],
accent: Color.Neutral[800],
background: Color.Neutral[900],
},
},
mode: 'dark',
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/theme/light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export default createTheme({
main: Color.Neutral[300],
border: Color.Neutral[400],
accent: Color.Neutral[900],
background: Color.Neutral[300],
},
},
},
Expand Down
3 changes: 3 additions & 0 deletions packages/gui/src/components/app/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Plot from '../plot/Plot';
import Pool from '../pool/Pool';
import Settings from '../settings/Settings';
import SettingsPanel from '../settings/SettingsPanel';
import VCs from '../vcs/VCs';
import AppProviders from './AppProviders';
import AppStatusHeader from './AppStatusHeader';

Expand Down Expand Up @@ -45,6 +46,7 @@ export default function AppRouter() {
<Route path="dashboard/wallets/*" element={<Wallets />} />
<Route path="dashboard/offers/*" element={<CreateOffer />} />
<Route path="dashboard/nfts/*" element={<NFTs />} />
<Route path="dashboard/vc/*" element={<VCs />} />
<Route path="dashboard/*" element={<Navigate to="wallets" />} />
<Route path="dashboard/settings/*" element={<Settings />} />
</Route>
Expand All @@ -64,6 +66,7 @@ export default function AppRouter() {
<Route path="dashboard/wallets/*" element={<Wallets />} />
<Route path="dashboard/offers/*" element={<CreateOffer />} />
<Route path="dashboard/nfts/*" element={<NFTs />} />
<Route path="dashboard/vc/*" element={<VCs />} />
<Route path="dashboard/settings/*" element={<Settings />} />
<Route path="dashboard/plot/*" element={<Plot />} />
<Route path="dashboard/farm/*" element={<Farm />} />
Expand Down
11 changes: 11 additions & 0 deletions packages/gui/src/components/dashboard/DashboardSideBar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useLocalStorage } from '@chia-network/api-react';
import { Flex, SideBarItem } from '@chia-network/core';
import {
Farming as FarmingIcon,
Expand All @@ -8,6 +9,7 @@ import {
Offers as OffersIcon,
Tokens as TokensIcon,
Settings as SettingsIcon,
VC as VCIcon,
} from '@chia-network/icons';
import { Trans } from '@lingui/macro';
import { Box } from '@mui/material';
Expand Down Expand Up @@ -41,6 +43,7 @@ export type DashboardSideBarProps = {

export default function DashboardSideBar(props: DashboardSideBarProps) {
const { simple = false } = props;
const [enableVerifiableCredentials] = useLocalStorage<boolean>('enable-verifiable-credentials', false);

return (
<StyledRoot>
Expand All @@ -57,6 +60,14 @@ export default function DashboardSideBar(props: DashboardSideBarProps) {
title={<Trans>NFTs</Trans>}
data-testid="DashboardSideBar-nfts"
/>
{enableVerifiableCredentials && (
<SideBarItem
to="/dashboard/vc"
icon={VCIcon}
title={<Trans>Credentials</Trans>}
data-testid="DashboardSideBar-vc"
/>
)}
<SideBarItem
to="/dashboard/offers"
icon={OffersIcon}
Expand Down
2 changes: 2 additions & 0 deletions packages/gui/src/components/settings/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import RemovePassphrasePrompt from './RemovePassphrasePrompt';
import SetPassphrasePrompt from './SetPassphrasePrompt';
import SettingsDerivationIndex from './SettingsDerivationIndex';
import SettingsStartup from './SettingsStartup';
import SettingsVerifiableCredentials from './SettingsVerifiableCredentials';

export default function SettingsPanel() {
const openDialog = useOpenDialog();
Expand Down Expand Up @@ -187,6 +188,7 @@ export default function SettingsPanel() {
)}
{addPassphraseOpen && <SetPassphrasePrompt onSuccess={setPassphraseSucceeded} onCancel={closeSetPassphrase} />}
<PassphraseFeatureStatus />
<SettingsVerifiableCredentials />
</Flex>
</SettingsApp>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useLocalStorage } from '@chia-network/api-react';
import { Flex, SettingsLabel } from '@chia-network/core';
import { Trans } from '@lingui/macro';
import { FormGroup, FormControlLabel, Grid, Switch } from '@mui/material';
import React from 'react';

export default function SettingsStartup() {
const [enableVerifiableCredentials, toggleVerifiableCredentials] = useLocalStorage<boolean>(
'enable-verifiable-credentials',
false
);
return (
<Grid container>
<Grid item>
<Flex flexDirection="column" gap={1}>
<SettingsLabel>
<Trans>Verifiable Credentials</Trans>
</SettingsLabel>

<FormGroup>
<FormControlLabel
control={
<Switch
checked={enableVerifiableCredentials}
onChange={() => toggleVerifiableCredentials(!enableVerifiableCredentials)}
/>
}
label={<Trans>Enable Verifiable Credentials</Trans>}
/>
</FormGroup>
</Flex>
</Grid>
</Grid>
);
}
166 changes: 166 additions & 0 deletions packages/gui/src/components/vcs/VCCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { useGetTimestampForHeightQuery, useRevokeVCMutation } from '@chia-network/api-react';
import { Truncate, Button, useOpenDialog, ConfirmDialog, AlertDialog, Flex } from '@chia-network/core';
import { Trans } from '@lingui/macro';
import { Box, Card, Typography, Table, TableRow, TableCell } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import moment from 'moment';
import React from 'react';
import { useNavigate } from 'react-router-dom';

import { didToDIDId } from '../../util/dids';

type RenderPropertyProps = {
children?: JSX.Element | string | null;
label: JSX.Element;
};

function RenderProperty(props: RenderPropertyProps) {
const { label, children } = props;
return (
<Box>
<Typography sx={{ fontSize: '12px' }}>{label}</Typography>
<Box>{children}</Box>
</Box>
);
}

export default function VCCard(props: { vcRecord: any; isDetail?: boolean; proofs?: any }) {
const { vcRecord, isDetail, proofs } = props;
const { data: mintedTimestamp, isLoading: isLoadingMintHeight } = useGetTimestampForHeightQuery({
height: vcRecord.confirmedAtHeight,
});
const navigate = useNavigate();
const [revokeVC] = useRevokeVCMutation();
const theme: any = useTheme();
const openDialog = useOpenDialog();

function renderProofs() {
if (isDetail && proofs && Object.keys(proofs).length > 0) {
const proofsRows = Object.keys(proofs).map((key) => (
<TableRow key={key} sx={{ background: 'rgba(255, 255, 255, 0.3)' }}>
<TableCell sx={{ border: '1px solid rgba(255, 255, 255, 0.5)' }}>{key}</TableCell>
<TableCell sx={{ border: '1px solid rgba(255, 255, 255, 0.5)' }}>{proofs[key]}</TableCell>
</TableRow>
));
return <Table sx={{ width: 'inherit', margin: '5px 0 25px 0' }}>{proofsRows}</Table>;
}
return isDetail && (!proofs || Object.keys(proofs).length === 0) ? <Trans>No</Trans> : <Trans>Yes</Trans>;
}

function renderProperties() {
return (
<>
<RenderProperty
label={
<Typography sx={{ fontSize: '12px' }}>
<Trans>Coin ID</Trans>
</Typography>
}
>
<Truncate tooltip copyToClipboard>
{vcRecord.vc.launcherId}
</Truncate>
</RenderProperty>
<RenderProperty label={<Trans>Issued</Trans>}>
{!isLoadingMintHeight && mintedTimestamp ? moment(mintedTimestamp.timestamp * 1000).format('LLL') : null}
</RenderProperty>
{vcRecord.vc.proofProvider && (
<RenderProperty label={<Trans>Issuer DID</Trans>}>
<Truncate tooltip copyToClipboard>
{didToDIDId(vcRecord.vc.proofProvider)}
</Truncate>
</RenderProperty>
)}
<RenderProperty label={<Trans>Proofs</Trans>}>
{vcRecord.vc.proofHash ? renderProofs() : <Trans>No</Trans>}
</RenderProperty>
</>
);
}

function renderViewDetailButton() {
if (isDetail) return null;

return (
<Button variant="outlined" sx={{ width: '100%' }}>
<Trans>View Credential Data</Trans>
</Button>
);
}

async function openRevokeVCDialog() {
const confirmed = await openDialog(
<ConfirmDialog
title={<Trans>Confirm Revoke</Trans>}
confirmTitle={<Trans>Yes, Revoke</Trans>}
confirmColor="secondary"
cancelTitle={<Trans>Cancel</Trans>}
>
<Flex flexDirection="column" gap={3}>
<Typography variant="body1">
<Trans>Are you sure you want to revoke this Verifiable Credential?</Trans>
</Typography>
</Flex>
</ConfirmDialog>
);
if (confirmed) {
const revokedResponse = await revokeVC({ vcParentId: vcRecord.vc.coin.parentCoinInfo });
if (revokedResponse.data?.success) {
await openDialog(
<AlertDialog title={<Trans>Verifiable Credential Revoked</Trans>}>
<Trans>Transaction sent to blockchain successfully.</Trans>
</AlertDialog>
);
navigate('/dashboard/vc');
} else {
openDialog(
<AlertDialog title={<Trans>Error</Trans>}>
{revokedResponse?.error?.data?.error ? <Box>{revokedResponse?.error?.data?.error}</Box> : null}
</AlertDialog>
);
}
}
}

function renderRevokeVCButton() {
if (!isDetail) return null;
return (
<Button variant="outlined" sx={{ width: '100%' }} onClick={() => openRevokeVCDialog()}>
<Trans>Revoke VC</Trans>
</Button>
);
}

return (
<Card
variant="outlined"
sx={{
display: 'flex',
flexDirection: 'column',
padding: '25px',
borderRadius: '15px',
border: `1px solid ${theme.palette.colors.default.border}`,
width: '100%',
cursor: 'pointer',
}}
onClick={() => {
navigate(`/dashboard/vc/${vcRecord.vc.launcherId}`);
}}
>
<Box
sx={{
background: theme.palette.colors.default.background,
borderRadius: '15px',
padding: '25px',
'> div': {
marginBottom: '10px',
},
}}
>
{renderProperties()}
{renderViewDetailButton()}
{renderRevokeVCButton()}
</Box>
</Card>
);
}