@krebitdao/reputation-passport
Version:
Krebit SDK for Verified Credentials
594 lines (593 loc) • 24.9 kB
JavaScript
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;
}
}