Skip to content

Commit

Permalink
feat: add createVC RPC method (#141)
Browse files Browse the repository at this point in the history
* feat: update types

* feat: add createVC RPC method

* fix: add bip44CoinTypeNode

* test: add params tests

* chore: fix tests

* feat: add createVC to connector

* fix: wrong return type

* fix: pr feedback

* chore: move queryVCsRequestResult[] type in test

* fix: change it.todo in tests

* chore: update remaining snaps packages to 0.28

* fix: add patch for snaps-types

---------

Co-authored-by: martines3000 <domajnko.martin@gmail.com>
  • Loading branch information
2 people authored and pseudobun committed Apr 6, 2023
1 parent c7b0bfe commit 2b23f93
Show file tree
Hide file tree
Showing 18 changed files with 1,638 additions and 800 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
},
"pnpm": {
"patchedDependencies": {
"cross-fetch@3.1.5": "patches/cross-fetch@3.1.5.patch"
"cross-fetch@3.1.5": "patches/cross-fetch@3.1.5.patch",
"@metamask/snaps-types@0.28.0": "patches/@metamask__snaps-types@0.28.0.patch"
}
}
}
19 changes: 19 additions & 0 deletions packages/connector/src/snap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
AvailableMethods,
AvailableVCStores,
CreateVCRequestParams,
CreateVPRequestParams,
DeleteVCsOptions,
MetaMaskSSISnapRPCRequest,
Expand All @@ -15,6 +16,7 @@ import {
import { Result } from '@blockchain-lab-um/utils';
import {
DIDResolutionResult,
VerifiableCredential,
VerifiablePresentation,
W3CVerifiableCredential,
} from '@veramo/core';
Expand Down Expand Up @@ -254,6 +256,22 @@ export async function resolveDID(
return sendSnapMethod({ method: 'resolveDID', params: { did } }, this.snapId);
}

/**
* Create a Verifiable Presentation
*/
export async function createVC(
this: MetaMaskSSISnap,
params: CreateVCRequestParams
): Promise<Result<VerifiableCredential>> {
return sendSnapMethod(
{
method: 'createVC',
params,
},
this.snapId
);
}

export class MetaMaskSSISnap {
protected readonly snapOrigin: string;

Expand Down Expand Up @@ -287,6 +305,7 @@ export class MetaMaskSSISnap {
getSnapSettings: getSnapSettings.bind(this),
getAccountSettings: getAccountSettings.bind(this),
resolveDID: resolveDID.bind(this),
createVC: createVC.bind(this),
};
};
}
34 changes: 34 additions & 0 deletions packages/snap/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h1>Hello, Snaps!</h1>
<option value="EthereumEip712Signature2021">eip</option>
</select>
<button id="getVP" class="getVP">Get VP</button>
<button id="getVC" class="getVC">Get VC</button>
<br />
<hr />
<select id="didMethod">
Expand Down Expand Up @@ -131,6 +132,8 @@ <h1>Hello, Snaps!</h1>
'button.getAvailableMethods'
);

const createVCBtn = document.querySelector('button.getVC');

const popups = document.querySelector('button.togglePopups');
const infura = document.querySelector('button.changeInfuraToken');
const resolveDID = document.querySelector('button.resolveDID');
Expand All @@ -155,6 +158,8 @@ <h1>Hello, Snaps!</h1>

getVPBtn.addEventListener('click', getVp);

createVCBtn.addEventListener('click', createVC);

switchMethodBtn.addEventListener('click', switchMethod);

getDIDBtn.addEventListener('click', getDID);
Expand Down Expand Up @@ -428,6 +433,35 @@ <h1>Hello, Snaps!</h1>
}
}

async function createVC() {
try {
const vc = JSON.parse(document.getElementById('VC').value);

console.log('VC', vc);
const pF = document.getElementById('proofFormat').value;
console.log('selected pf:', pF);

const response = await ethereum.request({
method: `wallet_snap_${snapId}`,
params: {
method: 'createVC',
params: {
minimalUnsignedCredential: vc,
proofFormat: pF,
options: {
save: true,
store: ['snap'],
},
},
},
});
console.log(response);
} catch (err) {
console.error(err);
alert('Problem happened: ' + err.message || err);
}
}

async function isInit() {
try {
const response = await ethereum.request({
Expand Down
4 changes: 2 additions & 2 deletions packages/snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@
"@commitlint/cli": "^17.4.4",
"@commitlint/config-conventional": "^17.4.4",
"@ianvs/prettier-plugin-sort-imports": "^3.7.1",
"@metamask/snaps-cli": "^0.27.1",
"@metamask/snaps-webpack-plugin": "^0.27.1",
"@metamask/snaps-cli": "0.28.0",
"@metamask/snaps-webpack-plugin": "0.28.0",
"@swc/core": "^1.3.41",
"@swc/jest": "^0.2.24",
"@types/jest": "^29.5.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/blockchain-lab-um/ssi-snap.git"
},
"source": {
"shasum": "zrdDax9K5PJ5r0htMsR5+W1SQgKNlcDO7RHdlrO5znk=",
"shasum": "WQrcgBAa+RcZcmRSvghbTti+ndXmaSli2si3up114NQ=",
"location": {
"npm": {
"filePath": "dist/snap.js",
Expand Down
11 changes: 11 additions & 0 deletions packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getDid } from './rpc/did/getDID';
import { resolveDID } from './rpc/did/resolveDID';
import { switchMethod } from './rpc/did/switchMethod';
import { togglePopups } from './rpc/snap/configure';
import { createVC } from './rpc/vc/createVC';
import { createVP } from './rpc/vc/createVP';
import { deleteVC } from './rpc/vc/deleteVC';
import { queryVCs } from './rpc/vc/queryVCs';
Expand All @@ -16,6 +17,7 @@ import { getAvailableVCStores } from './rpc/vcStore/getAvailableVCStores';
import { setVCStore } from './rpc/vcStore/setVCStore';
import { getAddressKeyDeriver } from './utils/keyPair';
import {
isValidCreateVCRequest,
isValidCreateVPRequest,
isValidDeleteVCRequest,
isValidQueryRequest,
Expand Down Expand Up @@ -69,6 +71,15 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
);
res = await saveVC(apiParams, request.params);
return ResultObject.success(res);
case 'createVC':
isValidCreateVCRequest(
request.params,
apiParams.account,
apiParams.state
);
apiParams.bip44CoinTypeNode = await getAddressKeyDeriver(apiParams);
res = await createVC(apiParams, request.params);
return ResultObject.success(res);
case 'createVP':
isValidCreateVPRequest(
request.params,
Expand Down
44 changes: 44 additions & 0 deletions packages/snap/src/rpc/vc/createVC.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CreateVCRequestParams } from '@blockchain-lab-um/ssi-snap-types';
import { copyable, divider, heading, panel, text } from '@metamask/snaps-ui';
import { VerifiableCredential } from '@veramo/core';
import { ApiParams } from 'src/interfaces';

import { snapConfirm } from '../../utils/snapUtils';
import { veramoCreateVC, veramoSaveVC } from '../../utils/veramoUtils';

export async function createVC(
params: ApiParams,
createVCParams: CreateVCRequestParams
): Promise<VerifiableCredential> {
const { minimalUnsignedCredential, proofFormat, options } = createVCParams;

const { store = 'snap' } = options || {};
const { save } = options || {};

const vc = await veramoCreateVC(params, {
minimalUnsignedCredential,
proofFormat,
options,
});
if (save === true) {
const content = panel([
heading('Save VC'),
text('Would you like to save the following VC?'),
divider(),
text(`Store(s): ${typeof store === 'string' ? store : store.join(', ')}`),
text(`VC:`),
copyable(JSON.stringify(vc, null, 2)),
]);

if (await snapConfirm(snap, content)) {
await veramoSaveVC({
snap: params.snap,
ethereum: params.ethereum,
verifiableCredential: vc,
store,
});
}
}

return vc;
}
65 changes: 65 additions & 0 deletions packages/snap/src/utils/params.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
AvailableVCStores,
CreateVCRequestParams,
CreateVPRequestParams,
DeleteVCsRequestParams,
QueryVCsRequestParams,
Expand Down Expand Up @@ -343,3 +344,67 @@ export function isValidVerifyDataRequest(

throw new Error('Invalid VerifyData request');
}

export function isValidCreateVCRequest(
params: unknown,
account: string,
state: SSISnapState
): asserts params is CreateVCRequestParams {
const param = params as CreateVCRequestParams;
if (
param !== null &&
typeof param === 'object' &&
'minimalUnsignedCredential' in param &&
param.minimalUnsignedCredential !== null &&
typeof param.minimalUnsignedCredential === 'object'
) {
// Check if proofFormat is valid
if (
'proofFormat' in param &&
param.proofFormat !== null &&
!isSupportedProofFormat(param.proofFormat as string)
) {
throw new Error('Proof format not supported');
}
if (
'options' in param &&
param.options !== null &&
param.options?.save !== undefined &&
param.options?.save !== null &&
typeof param.options?.save !== 'boolean'
) {
throw new Error('Save is not a boolean');
}

if (
'options' in param &&
param.options !== null &&
typeof param.options === 'object' &&
'store' in param.options &&
param.options?.store !== null
) {
if (typeof param.options?.store === 'string') {
if (!isAvailableVCStores(param.options?.store)) {
throw new Error(`Store ${param.options?.store} is not supported!`);
}
if (!isEnabledVCStore(account, state, param.options?.store)) {
throw new Error(`Store ${param.options?.store} is not enabled!`);
}
} else if (
Array.isArray(param.options?.store) &&
param.options?.store.length > 0
) {
(param.options?.store as [string]).forEach((store) => {
if (!isAvailableVCStores(store))
throw new Error(`Store ${store} is not supported!`);
if (!isEnabledVCStore(account, state, store as AvailableVCStores))
throw new Error(`Store ${store} is not enabled!`);
});
} else throw new Error('Store is invalid format');
}

return;
}

throw new Error('Invalid CreateVC request');
}
40 changes: 40 additions & 0 deletions packages/snap/src/utils/veramoUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
AvailableVCStores,
CreateVCRequestParams,
CreateVPRequestParams,
QueryVCsOptions,
QueryVCsRequestResult,
Expand All @@ -12,6 +13,8 @@ import { MetaMaskInpageProvider } from '@metamask/providers';
import { SnapsGlobalObject } from '@metamask/snaps-types';
import { copyable, divider, heading, panel, text } from '@metamask/snaps-ui';
import {
CredentialPayload,
ICreateVerifiableCredentialArgs,
IIdentifier,
IVerifyResult,
MinimalImportableKey,
Expand Down Expand Up @@ -264,3 +267,40 @@ export async function veramoVerifyData(args: {
return { verified: false, error: error as Error } as IVerifyResult;
}
}

export async function veramoCreateVC(
params: ApiParams,
createVCParams: CreateVCRequestParams
): Promise<VerifiableCredential> {
const { state, snap, ethereum } = params;
const { minimalUnsignedCredential, proofFormat = 'jwt' } = createVCParams;

// Get Veramo agent
const agent = await getAgent(snap, ethereum);
// GET DID
const identifier = await veramoImportMetaMaskAccount(params, agent);
const credentialPayload = minimalUnsignedCredential;
credentialPayload.issuer = identifier.did;

const config = state.snapConfig;

const createVCArgs: ICreateVerifiableCredentialArgs = {
credential: credentialPayload as CredentialPayload,
proofFormat,
save: false,
};
const content = panel([
heading('Create VC'),
text('Would you like to create a VC from the following data?'),
divider(),
text(`Data:`),
copyable(JSON.stringify(createVCArgs.credential, null, 2)),
]);

if (config.dApp.disablePopups || (await snapConfirm(snap, content))) {
const vc = await agent.createVerifiableCredential(createVCArgs);
return vc;
}

throw new Error('User rejected create VC request');
}
Loading

0 comments on commit 2b23f93

Please sign in to comment.