@hashgraph/hedera-identify-snap
Version:
A snap for managing Decentralized Identifiers(DIDs)
249 lines (225 loc) • 8.23 kB
text/typescript
/*-
*
* Hedera Identify Snap
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { rpcErrors } from '@metamask/rpc-errors';
import { DialogParams, divider, heading, text } from '@metamask/snaps-sdk';
import { ProofFormat, W3CVerifiableCredential } from '@veramo/core';
import { sha256 } from 'js-sha256';
import cloneDeep from 'lodash.clonedeep';
import {
IDataManagerQueryResult,
IDataManagerSaveArgs,
IDataManagerSaveResult,
ISaveVC,
QueryMetadata,
SaveOptions,
} from '../../plugins/veramo/verifiable-creds-manager';
import {
CreateVCRequestParams,
CreateVCResponseResult,
} from '../../types/params';
import { IdentifySnapParams } from '../../types/state';
import { HederaUtils } from '../../utils/HederaUtils';
import { SnapUtils } from '../../utils/SnapUtils';
import { getVeramoAgent } from '../../veramo/agent';
export class SaveVCFacade {
/**
* Function to create vc.
*
* @param identifySnapParams - Identify snap params.
*/
public static async createVC(
identitySnapParams: IdentifySnapParams,
vcRequestParams: CreateVCRequestParams,
): Promise<CreateVCResponseResult> {
const { state } = identitySnapParams;
const { snapEvmAddress, network, identifier, method } =
state.currentAccount;
// Get Veramo agent
const agent = await getVeramoAgent(state);
// GET DID
const { did } = identifier;
const {
vcKey = 'vcData',
vcValue,
credTypes = [],
options,
} = vcRequestParams || {};
const { store = 'snap' } = options || {};
const optionsFiltered = { store } as SaveOptions;
const panelToShow = [
heading('Create Verifiable Credential'),
text('Would you like to create and save the following VC in the snap?'),
divider(),
text(
JSON.stringify({
[vcKey]: vcValue,
}),
),
];
const dialogParams: DialogParams = {
type: 'confirmation',
content: await SnapUtils.generateCommonPanel(
origin,
network,
panelToShow,
),
};
const confirmed = await SnapUtils.snapDialog(dialogParams);
if (!confirmed) {
const errMessage = 'User rejected the transaction';
console.error(errMessage);
throw rpcErrors.transactionRejected(errMessage);
}
const issuanceDate = new Date();
// Set the expiration date to be 1 year from the date it's issued
const expirationDate = cloneDeep(issuanceDate);
expirationDate.setFullYear(
issuanceDate.getFullYear() + 1,
issuanceDate.getMonth(),
issuanceDate.getDate(),
);
const credential = new Map<string, unknown>();
credential.set('issuanceDate', issuanceDate.toISOString()); // the entity that issued the credential+
credential.set('expirationDate', expirationDate.toISOString()); // when the credential was issued
credential.set('type', credTypes);
const issuer: { id: string; hederaAccountId?: string } = { id: did };
const credentialSubject: { id: string; hederaAccountId?: string } = {
id: did, // identifier for the only subject of the credential
[vcKey]: vcValue, // assertion about the only subject of the credential
};
const accountState = state.accountState[snapEvmAddress][network];
if (HederaUtils.validHederaNetwork(network) || method === 'did:hedera') {
const hederaAccountId = accountState.accountInfo.accountId;
issuer.hederaAccountId = hederaAccountId;
credentialSubject.hederaAccountId = hederaAccountId;
}
credential.set('issuer', issuer); // the entity that issued the credential
credential.set('credentialSubject', credentialSubject);
// Generate a Verifiable Credential
const verifiableCredential: W3CVerifiableCredential =
await agent.createVerifiableCredential({
credential: JSON.parse(JSON.stringify(Object.fromEntries(credential))),
// digital proof that makes the credential tamper-evident
proofFormat: 'jwt' as ProofFormat,
});
// Save the Verifiable Credential to all the stores the user requested for
const saved: IDataManagerSaveResult[] = await agent.saveVC({
data: [
{
vc: verifiableCredential,
id: sha256(JSON.stringify(verifiableCredential)),
},
] as ISaveVC[],
options: optionsFiltered,
accessToken:
accountState.accountConfig.identity.googleUserInfo.accessToken,
});
if (saved.length === 0) {
throw new Error('Failed to save the VC');
}
// Retrieve the created Verifiable Credential
const result: CreateVCResponseResult = {
data: verifiableCredential as W3CVerifiableCredential,
metadata: {
id: saved[0].id,
store: saved.map((res) => res.store),
} as QueryMetadata,
};
return result;
}
/**
* Function to save vc.
*
* @param identifySnapParams - Identify snap params.
*/
public static async saveVC(
identitySnapParams: IdentifySnapParams,
vcRequestParams: IDataManagerSaveArgs,
): Promise<IDataManagerSaveResult[]> {
const { state } = identitySnapParams;
const { snapEvmAddress, network } = state.currentAccount;
const { data: verifiableCredentials, options } = vcRequestParams || {};
const { store = 'snap' } = options || {};
const optionsFiltered = { store } as SaveOptions;
const header = 'Save Verifiable Credentials';
const prompt = `Are you sure you want to save the following VCs in ${
typeof store === 'string' ? store : store.join(', ')
}?`;
const description = `Number of VCs submitted is ${(
verifiableCredentials as W3CVerifiableCredential[]
).length.toString()}`;
const vcsWithMetadata: IDataManagerQueryResult[] = [];
// Iterate through vcs
(verifiableCredentials as W3CVerifiableCredential[]).forEach(
function (vc, index) {
vcsWithMetadata.push({
data: vc,
metadata: {
id: `External VC #${(index + 1).toString()}`,
store: 'snap',
},
});
},
);
const dialogParams: DialogParams = {
type: 'confirmation',
content: await SnapUtils.generateVCPanel(
origin,
network,
header,
prompt,
description,
vcsWithMetadata,
),
};
const confirmed = await SnapUtils.snapDialog(dialogParams);
if (!confirmed) {
const errMessage = 'User rejected the transaction';
console.error(errMessage);
throw rpcErrors.transactionRejected(errMessage);
}
// Save the Verifiable Credential
const accountState = state.accountState[snapEvmAddress][network];
const filteredCredentials: W3CVerifiableCredential[] = (
verifiableCredentials as W3CVerifiableCredential[]
).filter((x: W3CVerifiableCredential) => {
const vcObj = JSON.parse(JSON.stringify(x));
const subjectDid: string = vcObj.credentialSubject.id;
const subjectAccount = subjectDid.split(':')[4];
return snapEvmAddress === subjectAccount;
});
// Save the Verifiable Credential to all the stores the user requested for
const agent = await getVeramoAgent(state);
const saved: IDataManagerSaveResult[] = await agent.saveVC({
data: (filteredCredentials as W3CVerifiableCredential[]).map(
(x: W3CVerifiableCredential) => {
return { vc: x } as ISaveVC;
},
) as ISaveVC[],
options: optionsFiltered,
accessToken:
accountState.accountConfig.identity.googleUserInfo.accessToken,
});
if (saved.length === 0) {
throw new Error('Failed to save the VC');
}
return saved;
}
}