UNPKG

@krebitdao/reputation-passport

Version:

Krebit SDK for Verified Credentials

594 lines (593 loc) 24.9 kB
import { CeramicClient } from '@ceramicnetwork/http-client'; import { TileDocument } from '@ceramicnetwork/stream-tile'; import { DIDSession } from 'did-session'; import localStore from 'store2'; import { lib } from '../lib/index.js'; import { utils } from '../utils/index.js'; import { config } from '../config/index.js'; export class Passport { constructor(props) { this.connect = async (currentSession, defaultChainId) => { if (currentSession) { const session = await DIDSession.fromSession(currentSession); this.idx = await lib.ceramic.authDIDSession({ client: this.ceramic, session }); } else { this.idx = await lib.ceramic.authDIDSession({ client: this.ceramic, address: this.address, ethProvider: this.ethProvider, defaultChainId }); } this.did = this.idx.id.toLocaleLowerCase(); this.ens = await lib.ens.lookupAddress(this.address); this.uns = await lib.uns.lookupAddress(this.address); return this.did; }; this.isConnected = async () => { const currentSession = localStore.get('did-session'); if (!currentSession) return false; const session = await DIDSession.fromSession(currentSession); if (session.hasSession && session.isExpired) return false; this.idx = await lib.ceramic.authDIDSession({ client: this.ceramic, session }); this.did = this.idx.id.toLocaleLowerCase(); return this.idx.authenticated; }; this.getReputation = async () => { //from subgraph if (!this.address) return; const balance = await lib.graph.erc20BalanceQuery(this.address); return balance ? balance.value : 0; }; this.read = async (value) => { if (value.startsWith('0x')) { this.address = value.toLocaleLowerCase(); let defaultDID = await lib.orbis.getDefaultDID(this.address); this.did = defaultDID ? defaultDID : `did:pkh:eip155:1:${value}`; this.ens = await lib.ens.lookupAddress(this.address); this.uns = await lib.uns.lookupAddress(this.address); } else if (value.startsWith('did:pkh:eip155:')) { this.did = value.toLocaleLowerCase(); this.address = value.match(utils.regexValidations.address)[0]; this.ens = await lib.ens.lookupAddress(this.address); this.uns = await lib.uns.lookupAddress(this.address); } else if (value.endsWith('.eth')) { const ensInfo = await this.readEns(value); this.address = ensInfo?.address.toLocaleLowerCase(); let defaultDID = await lib.orbis.getDefaultDID(this.address); this.did = defaultDID ? defaultDID : `did:pkh:eip155:1:${this.address}`; this.ens = ensInfo?.ens; this.uns = await lib.uns.lookupAddress(this.address); } else { const unsInfo = await this.readUns(value); this.address = unsInfo?.address.toLocaleLowerCase(); let defaultDID = await lib.orbis.getDefaultDID(this.address); this.did = defaultDID ? defaultDID : `did:pkh:eip155:1:${this.address}`; this.uns = unsInfo?.uns; this.ens = await lib.ens.lookupAddress(this.address); } if (!this.did || !this.address) { throw new Error('Invalid did or address'); } const ceramicClient = new CeramicClient(this.currentConfig.ceramicUrl); this.idx = lib.ceramic.publicIDX({ client: ceramicClient }); this.ceramic = ceramicClient; }; this.readEns = async (name) => { const address = await lib.ens.resolveName(name); if (address) { return { ens: name, address }; } else { throw new Error('No resolved address for that ENS domain'); } }; this.readUns = async (name) => { const address = await lib.uns.resolveName(name); if (address) { return { uns: name, address }; } else { throw new Error('No resolved address for that ENS domain'); } }; // basiProfile from ceramic this.getProfile = async () => { if (!this.idx) throw new Error('Not connected'); try { const content = await this.idx.get('basicProfile', this.did); if (content) { return content; } } catch (err) { throw new Error(err); } }; // basiProfile from ceramic this.updateProfile = async (profile) => { if (!this.idx) throw new Error('Not connected'); try { const content = await this.idx.set('basicProfile', profile); if (content) { return content; } } catch (err) { throw new Error(err); } }; // claimedCredentials from ceramic this.addVerifiableCredential = async (w3cCredential) => { if (!this.isConnected()) throw new Error('Not connected'); console.log('Saving VerifiableCredential on Ceramic...'); const stream = await TileDocument.create(this.idx.ceramic, w3cCredential, { schema: this.idx.model.getSchemaURL('VerifiableCredential'), family: 'krebit', controllers: [this.idx.id], tags: w3cCredential.type }); return stream.id.toUrl(); }; // get credential from ceramic this.getCredential = async (vcId) => { if (!this.idx) throw new Error('Not connected'); if (!vcId.startsWith('ceramic://')) return null; const stream = await TileDocument.load(this.idx.ceramic, vcId); return stream.content; }; this.getClaimValue = async (w3cCredential) => { if (w3cCredential.credentialSubject.encrypted === 'hash') { const claimedCredential = await this.getCredential(w3cCredential.id); if (claimedCredential) { return await this.getClaimValue(claimedCredential); } else { throw new Error(`Could not retrieve claimed credential value`); } } else { return JSON.parse(w3cCredential.credentialSubject.value); } }; // claimedCredentials from ceramic this.checkCredentialStatus = async (vcId) => { if (!this.isConnected()) throw new Error('Not connected'); console.log('Checking VerifiableCredential from Ceramic...'); const w3cCredential = await this.getCredential(vcId); let result = null; const issuedList = await this.idx.get('issuedCredentials', w3cCredential.issuer.id); if (issuedList && issuedList.issued) { const issued = issuedList.issued ? issuedList.issued : []; if (!issued.includes(vcId)) { result = 'Issued'; } } const revokedList = await this.idx.get('revokedCredentials', w3cCredential.issuer.id); if (revokedList && revokedList.issued) { const revoked = revokedList.revoked ? revokedList.revoked : []; if (!revoked.includes(vcId)) { result = 'Revoked'; } } const now = new Date(); if (w3cCredential.expirationDate >= now.toISOString()) { result = 'Expired'; } return result; }; // claimedCredentials from ceramic this.updateVerifiableCredential = async (credentialId, w3cCredential) => { if (!this.isConnected()) throw new Error('Not connected'); console.debug('Updating VerifiableCredential on Ceramic...', w3cCredential); const stream = await TileDocument.load(this.idx.ceramic, credentialId); return await stream.update(w3cCredential); }; // issuedCredentials in ceramic this.addIssued = async (w3cCredential) => { if (!this.isConnected()) throw new Error('Not connected'); if (w3cCredential.issuer.ethereumAddress.toLowerCase() != this.address.toLowerCase()) throw new Error('Not by this address'); if (w3cCredential.issuer.id.toLowerCase() != this.did.toLowerCase()) throw new Error('Not by this did'); // Upload attestation to Ceramic try { const vcId = await this.addVerifiableCredential(w3cCredential); let result = null; if (vcId) { const content = await this.idx.get('issuedCredentials'); if (content && content.issued) { const current = content.issued ? content.issued : []; if (!current.includes(vcId)) { current.push(vcId); result = await this.idx.merge('issuedCredentials', { issued: current }); } } else { result = await this.idx.set('issuedCredentials', { issued: [vcId] }); } } if (result) { return vcId; } } catch (err) { throw new Error(err); } }; // issuedCredentials in ceramic this.revokeCredential = async (vcId) => { if (!this.isConnected()) throw new Error('Not connected'); // Upload attestation to Ceramic try { await this.removeIssued(vcId); let result = null; if (vcId) { const content = await this.idx.get('revokedCredentials'); if (content && content.revoked) { const current = content.revoked ? content.revoked : []; console.log('current:', current); if (!current.includes(vcId)) { current.push(vcId); console.log('current push:', current); result = await this.idx.merge('revokedCredentials', { revoked: current }); } } else { result = await this.idx.set('revokedCredentials', { revoked: [vcId] }); } } if (result) { return vcId; } } catch (err) { throw new Error(err); } }; //remove from issuedCredentials in ceramic this.removeIssued = async (vcId) => { if (!this.isConnected()) throw new Error('Not connected'); try { const credential = await this.getCredential(vcId); credential.credentialSubject.value = '{"removed":true}'; await this.updateVerifiableCredential(vcId, credential); let result = null; const content = await this.idx.get('issuedCredentials'); if (content && content.issued) { const current = content.issued ? content.issued : []; if (current.includes(vcId)) { current.splice(current.indexOf(vcId), 1); result = await this.idx.merge('issuedCredentials', { issued: current }); } } if (result) { return result; } } catch (err) { throw new Error(err); } }; // issuedCredentials from ceramic this.getIssued = async (type) => { if (!this.idx) throw new Error('Not connected'); try { let result = []; const content = await this.idx.get('issuedCredentials', this.did); if (content && content.issued) { const current = content.issued ? content.issued : []; result = await Promise.all(await current.map(async (vcId) => { let vcStream = await this.ceramic.loadStream(vcId); if (vcStream) { if (type) { if (vcStream.content.type.includes(type)) return { ...vcStream.content, vcId: vcId }; } else { return { ...vcStream.content, vcId: vcId }; } } })); } return result.filter(c => c != null); } catch (err) { throw new Error(err); } }; // claimedCredentials from ceramic this.addClaim = async (w3cCredential) => { if (!this.isConnected()) throw new Error('Not connected'); if (w3cCredential.credentialSubject.ethereumAddress.toLowerCase() != this.address.toLowerCase()) throw new Error('Not for this address'); if (w3cCredential.credentialSubject.id.toLowerCase() != this.did.toLowerCase()) throw new Error('Not for this did'); // Upload attestation to Ceramic try { let result = null; const vcId = await this.addVerifiableCredential(w3cCredential); if (vcId) { const content = await this.idx.get('claimedCredentials'); if (content && content.claimed) { const current = content.claimed ? content.claimed : []; if (!current.includes(vcId)) { current.push(vcId); result = await this.idx.merge('claimedCredentials', { claimed: current }); } } else { result = await this.idx.set('claimedCredentials', { claimed: [vcId] }); } } if (result) { return vcId; } } catch (err) { throw new Error(err); } }; // claimedCredentials from ceramic this.removeClaim = async (vcId) => { if (!this.isConnected()) throw new Error('Not connected'); try { const credential = await this.getCredential(vcId); credential.credentialSubject.value = '{"removed":true}'; await this.updateVerifiableCredential(vcId, credential); let result = null; const content = await this.idx.get('claimedCredentials'); if (content && content.claimed) { const current = content.claimed ? content.claimed : []; if (current.includes(vcId)) { current.splice(current.indexOf(vcId), 1); result = await this.idx.merge('claimedCredentials', { claimed: current }); } } if (result) { return result; } } catch (err) { throw new Error(err); } }; // claimedCredentials from ceramic, filter by type this.getClaims = async (type) => { if (!this.idx) throw new Error('Not connected'); try { let result = []; const content = await this.idx.get('claimedCredentials', this.did); if (content && content.claimed) { const current = content.claimed ? content.claimed : []; result = await Promise.all(await current.map(async (vcId) => { let vcStream = await this.ceramic.loadStream(vcId); if (vcStream) { if (type) { if (vcStream.content.type.includes(type)) return { ...vcStream.content, vcId: vcId }; } else { return vcStream.content; } } })); } return result.filter(c => c != null); } catch (err) { throw new Error(err); } }; // heldCredentials in ceramic this.addCredential = async (w3cCredential) => { if (!this.isConnected()) throw new Error('Not connected'); if (w3cCredential.credentialSubject.ethereumAddress.toLowerCase() != this.address.toLowerCase()) throw new Error('Not for this address'); if (w3cCredential.credentialSubject.id.toLowerCase() != this.did.toLowerCase()) throw new Error('Not for this did'); // Upload attestation to Ceramic try { const vcId = await this.addVerifiableCredential(w3cCredential); let result = null; if (vcId) { const content = await this.idx.get('heldCredentials'); if (content && content.held) { const current = content.held ? content.held : []; if (!current.includes(vcId)) { current.push(vcId); result = await this.idx.merge('heldCredentials', { held: current }); } } else { result = await this.idx.set('heldCredentials', { held: [vcId] }); } } if (result) { return vcId; } } catch (err) { throw new Error(err); } }; // heldCredentials in ceramic this.removeCredential = async (vcId) => { if (!this.isConnected()) throw new Error('Not connected'); try { const credential = await this.getCredential(vcId); if (credential.id.startsWith('ceramic://')) { await this.removeClaim(credential.id); } let result = null; const content = await this.idx.get('heldCredentials'); if (content && content.held) { const current = content.held ? content.held : []; if (current.includes(vcId)) { current.splice(current.indexOf(vcId), 1); result = await this.idx.merge('heldCredentials', { held: current }); } } if (result) { return result; } } catch (err) { throw new Error(err); } }; // heldCredentials from ceramic, filter by type this.getCredentials = async (type, tag) => { if (!this.idx) throw new Error('Not connected'); try { let result = []; const content = await this.idx.get('heldCredentials', this.did); if (content && content.held) { const current = content.held ? content.held : []; result = await Promise.all(current.map(async (vcId) => { let vcStream = await this.ceramic.loadStream(vcId); if (vcStream) { if (type) { if (vcStream.content.credentialSubject.type === type) return { ...vcStream.content, vcId: vcId }; } else if (tag) { if (vcStream.content.type.includes(tag)) return { ...vcStream.content, vcId: vcId }; } else { return { ...vcStream.content, vcId: vcId }; } } })); } return result.filter(c => c != null); } catch (err) { throw new Error(err); } }; this.getSkills = async () => { const credentials = await this.getCredentials(); return credentials.flatMap(credential => credential.type); }; // registeredCredentials from subgraph this.getStamps = async (props) => { const { first, type, claimId } = props; const where = { credentialSubjectDID: this.did.toLowerCase(), credentialSubjectAddress: this.address.toLowerCase() }; if (type) where['_type_contains_nocase'] = type; if (claimId) where['claimId'] = claimId; //Get verifications from subgraph return await lib.graph.verifiableCredentialsQuery({ first: first ? first : 10, orderBy: 'issuanceDate', orderDirection: 'desc', where }); }; // write to my ceramic this.createDocument = async (content, tags, schema, family = 'krebit') => { if (!this.isConnected()) throw new Error('Not connected'); console.log('Saving document on Ceramic...'); try { const stream = await TileDocument.create(this.idx.ceramic, { family, controllers: [this.idx.id], tags, schema }); return stream.id.toString(); } catch (err) { throw new Error(err); } }; const currentConfig = config.update(props); this.currentConfig = currentConfig; const ceramicClient = new CeramicClient(this.currentConfig.ceramicUrl); this.ceramic = ceramicClient; this.address = props?.address?.toLocaleLowerCase(); this.ethProvider = props?.ethProvider; } }