@sybil-center/sdk v1.0.0
SybilCenter SDK
In one line you can issue a Verifiable Credential
proving that your user owns a social media or blockchain account.
Supported are Twitter, Discord, GitHub and Ethereum.
import { EthProofProvider, type IEIP1193Provider, Sybil } from "@sybil-center/sdk";
const sybil = new Sybil({ apiKey: "APIkey from https://app.sybil.center/devportal" });
const injected = "ethereum" in window && (window.ethereum as IEIP1193Provider);
if (!injected) throw new Error(`No window.ethereum available`);
const proofProvider = new EthProofProvider(injected);
const credential = await sybil.credential("twitter-account", await proofProvider.proof()); // This returns a Verifiable
// CredentialInstall
npm install @sybil-center/sdk # Feel free to use pnpm or yarnContent
- API keys
- Issuer id (did:key)
- Supported Verifiable Credentials
- Set credential expiration date
- Choose subject props
- Your custom property
- Verify credential
- What is ProofProvider?
- What is subjectId?
- What is SignFn?
- What is SubjectProof?
- What is SDK Issuer?
- Control under issuing process
- Use SDK on Frontend-Backend
- Raw HTTP API
- How to get type and const?
- Demo app
API keys
API keys allow you to communicate with the sybil.center server app.
API keys includes two keys api key and secret key.
You should use api key on the Frontend application, and do not show
your secret key to third parties. You should use secret key if
You have Backend application and want to integrate with sybil.center server app.
How to implement credential issue process with Fronted-Backend applications
see in Use SDK on Frontend-Backend section.
You can getAPI keys from developer portal.
Cool fact: developer portal use sybil SDK to issue EthereumAccount credential
and generate API keys.
In SDK you should use API keys as shown in the code below:
// On frontend applicatoin
const sybil = new Sybil({
apiKey: "API key from https://app.sybil.center/devportal",
// DO NOT USE secret key on frontend !!!
});
// On backend application
const sybil = new Sybil({
apiKey: "API key from https://app.sybil.center/devportal",
secretKey: "Secret key from https://app.sybil.center/devportal"
});Note: If you put both API keys when initialize sybil instance
from new Sybil, in SDK secret key has priority
under api key, therefore when SDK execute request
to sybil.center server app it uses secret key to authorize request
(Issuer.canIssue request is exception).
For that reason DO NOT USE secret key on frontend app!!!
If you want to integrate with the sybil.center server app without SDK, you should
put api key or api secret into Authorization header as
Bearer <Your API key or SercetKey>
Note: API keys will be linked to your ethereum address,
therefore if your API keys will be compromised you should create new API keys
for new ethereum account.
Issuer id (did:key)
Sybil center issuer id: did:key:z6MkvHAzbMgT1dCUutMCxaAUTuQUfhWyn9bDBL43nrmsybiL
This identifier presents in issued credentials in credential.issuer.id field.
Supported Verifiable Credentials
Discord Account
Description: prove that user is the owner of the Discord account
Credential type: DiscordAccount
Designator in sybil instane: discord-account
Authorization: true (what it means?)
Props (represents in credential.credentialSubject.discord):
id:type: stringdescription: permanent unique Discord account identifier
username:type: stringdescription: the user's username, not unique across the Discord platform
discriminator:type: stringdescripton: the user's 4-digit discord-tag
Example:
// Issue credential by sybil.credential
const credential = await sybil.credential(
"discord-account",
await proofProvider.proof()
);
// Issue credential by issuer
const ethAddress = await proofProvider.getAddress();
const issuer = sybil.issuer("discord-account");
const challenge = await issuer.getChallenge({
subjectId: `did:pkh:eip155:1${ethAddress}`
});
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await proofProvider.sign({
message: challenge.issueMessage
});
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
credential = {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"credentialSubject": {
"discord": {
"discriminator": "0001",
"id": "711603072933036111",
"username": "Cool exmaple"
},
"id": "did:pkh:eip155:1:<address>"
},
"issuanceDate": "2023-05-07T09:59:01.806Z",
"issuer": {
"id": "did:key:<public key>"
},
"type": [
"VerifiableCredential",
"DiscordAccount"
],
"proof": {
"type": "JsonWebSignature2020",
"created": "2023-05-07T09:59:01.818Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "...",
"jws": "..."
}
};Ethereum Account
Description: prove that user is the owner of the Ethereum account
Credential type: EthereumAccount
Designator in sybil instane: ethereum-account
Authorization: false (what it means?)
Props (represents in credential.credentialSubject.ethereum):
chainId:type: stringdescription: chain id according to spec
address:type: stringdescription: user ethereum account address
Example:
// Issue credential by sybil instance
const credential = await sybil.credential(
"ethereum-account",
await proofProvider.proof()
);
// Issue credential by Issuer
const ethAddress = await proofProvider.getAddress();
const issuer = sybil.issuer("ethereum-account");
const challenge = await issuer.getChallenge({
subjectId: `did:pkh:eip155:1${ethAddress}`
});
const signature = await proofProvider.sign({
message: challenge.issueMessage
});
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
// Result credential
credential = {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"credentialSubject": {
"ethereum": {
"address": "<address>",
"chainId": "eip155:1"
},
"id": "did:pkh:eip155:1:<address>"
},
"issuanceDate": "2023-05-07T10:06:51.950Z",
"issuer": {
"id": "did:key:<public key>"
},
"type": [
"VerifiableCredential",
"EthereumAccount"
],
"proof": {
"type": "JsonWebSignature2020",
"created": "2023-05-07T10:06:51.961Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "...",
"jws": "..."
}
};GitHub Account
Description: prove that user is the owner of the GitHub account
Credential type: GitHubAccount
Designator in sybil instane: github-account
Authorization: true (what it means?)
Props (represents in credential.credentialSubject.github):
id:type: numberdescription: permanent unique GitHub account identifier
username:type: stringdescription: current GitHub account username akalogin
userPage:type: stringdescripton: url to current GitHub user profile
Example:
// Issue credential by sybil instance
const credential = await sybil.credential(
"github-account",
await proofProvider.proof()
);
// Issue credential by Issuer
const ethAddress = await proofProvider.getAddress();
const issuer = sybil.issuer("github-account");
const challenge = await issuer.getChallenge({
subjectId: `did:pkh:eip155:1${ethAddress}`
});
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await proofProvider.sign({
message: challenge.issueMessage
});
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
// Result credential
credential = {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"credentialSubject": {
"github": {
"id": 00000001,
"userPage": "https://github.com/example",
"username": "example"
},
"id": "did:pkh:eip155:1:<address>"
},
"issuanceDate": "2023-05-07T09:46:39.726Z",
"issuer": {
"id": "did:key:<public key>"
},
"type": [
"VerifiableCredential",
"GitHubAccount"
],
"proof": {
"type": "JsonWebSignature2020",
"created": "2023-05-07T09:46:39.739Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "...",
"jws": "..."
}
};Twitter Account
Description: prove that user is the owner of the Twitter account
Credential type: TwitterAccount
Designator in sybil instane: twitter-account
Authorization: true (what it means?)
Props (represents in credential.credentialSubject.twitter):
id:type: stringdescription: permanent unique Twitter account identifier
username:type: stringdescription: current Twitter account username
Examples:
// Issue credential by sybil instance
const credential = await sybil.credential(
"twitter-account",
await proofProvider.proof()
);
// Issue credential by Issuer
const ethAddress = await proofProvider.getAddress();
const issuer = sybil.issuer("twitter-account");
const challenge = await issuer.getChallenge({
subjectId: `did:pkh:eip155:1${ethAddress}`
});
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await proofProvider.sign({
message: challenge.issueMessage
});
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
// Result credential
credential = {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"credentialSubject": {
"id": "did:pkh:eip155:1:<address>",
"twitter": {
"id": "1111111111",
"username": "example-username"
}
},
"issuanceDate": "2023-05-07T09:20:12.899Z",
"issuer": {
"id": "did:key:<public key>"
},
"type": ["VerifiableCredential", "TwitterAccount"],
"proof": {
"type": "JsonWebSignature2020",
"created": "2023-05-07T09:20:12.909Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "...",
"jws": "..."
}
};Note
If you call sybil.credential method with any of the credential designators, type of resulting credential is provided
for you by TypeScript. To get, for example, Twitter username from credential as in excerpt above, you would
use credential.credentialSubject.twitter.username.
Set credential expiration date
You can set credential expiration date.
In code below you can see how to set expiration date
const expirationDate = new Date(2015, 7, 30, 12, 0, 0);
// Issue credential by sybil.credential
const credential = await sybil
.credential("discord-account", await proofProvider.proof(), {
expirationDate: expirationDate
});
credential?.expirationDate === expirationDate;
// Issue credential by issuer
const solanaAddress = await proofProvider.getAddress();
const issuer = sybil.issuer("discord-account");
const challenge = await issuer.getChallenge({
subjectId: `solana:${solanaAddress}`,
expirationDate: expirationDate
});
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await proofProvider.sign({
message: challenge.issueMessage
});
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
credential?.expirationDate === expirationDate;Choose subject props
In section Supported credentials
in each supported credential you can see Props field.
You can give your users the opportunity to choose which props
will be in credential.
In the example below you can see how to achieve this goal.
Example:
// Issue credential by sybil.credential
const credential = await sybil
.credential("github-account", await proofProvider.proof(), {
props: ["id"]
});
// Issue credential by issuer
const address = await getEthAddress();
const issuer = sybil.issuer("github-account");
const challenge = await issuer.getChallenge({
subjectId: `did:pkh:eip155:1:${address}`,
props: ["id"]
});
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await sign(challenge.issueMessage);
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
// Credential result
credential.credentialSubject.github?.id !== undefined;
credential.credentialSubject.github?.username === undefined;
credential.credentialSubject.github?.userPage === undefined;Note: If you do not define props in sybil.credential or Issuer.getChallenge
methods in the final credential all supported props will be present (e.g
if you do not define props in sybil.credential for github-account
credential.credentialSubject.github object will contain id, username,
userPage fields). If you put into props an empty list [],
subject will NOT contain any props (e.g credential.credentialSubject.github
will NOT contain id, username, userPage).
Your custom property
You can put custom property to Verifiable Credential.
Property will be placed in credentialSubject field of issued credential. It can be the object you want.
Example:
// Issue credential by sybil.credential
const credential = await sybil
.credential("discord-account", await proofProvider.proof(), {
custom: { hello: "sybil" } // put here what ever object you want
});
// Issue credential by issuer
const address = await getEthAddress()
const issuer = sybil.issuer("discord-account");
const challenge = await issuer.getChallenge({
subjectId: `polygon:${address}`,
custom: { hello: "sybil" }
});
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await sign(challenge.issueMessage);
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
// credential
credential = {
"@context": ["https://www.w3.org/2018/credentials/v1"],
credentialSubject: {
custom: { hello: "sybil" }, // custom object you put
discord: {
discriminator: "1111",
id: "111111111111111111",
username: "Sybil User"
},
id: "did:pkh:eip155:137:0xb9Baa2979F62c806Ca3fE8f6932E82Bb416112aA"
},
issuanceDate: "2023-03-14T12:30:44.883Z",
issuer: {
id: "did:key:z6Mkeq4TyTdAND6JTMKuCVWtiPi7V4vnLK8LnWSqEQrQz2Gr"
},
type: ["VerifiableCredential", "DiscordAccount"],
proof: {
type: "JsonWebSignature2020",
created: "2023-03-14T12:30:44.887Z",
proofPurpose: "assertionMethod",
verificationMethod: "did:key:z6Mkeq4TyTdAND6JTMKuCVWtiPi7V4vnLK8LnWSqEQrQz2Gr#z6Mkeq4TyTdAND6JTMKuCVWtiPi7V4vnLK8LnWSqEQrQz2Gr",
jws: "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2VxNFR5VGRBTkQ2SlRNS3VDVld0aVBpN1Y0dm5MSzhMbldTcUVRclF6MkdyI3o2TWtlcTRUeVRkQU5ENkpUTUt1Q1ZXdGlQaTdWNHZuTEs4TG5XU3FFUXJRejJHciJ9..Oyyo53msJrdV_j8LJeuyUCC81X4s9fr_Ly1g6AQtueQoViXtD0iTMXoGn4SxyoKoLFwZQCEOMcoo_McNjM68AQ"
}
}Issuing and authorization
During issuing some credentials user have to authorize in other services.
You have opportunity to set authorize window property and redirect url
where user will be redirected after authorization.
By default, if you use the sybil.credential method, authorized window
open in the middle of the user screen as popul
and after authorization user will be redirected to default page that will be closed
automatically after redirection.
Example:
// Issue credential by sybil.credential
const credential = await sybil
.credential("discord-account", signer.sign, {
redirectUrl: "https://exaple.com",
windowFeature: `
popup,
width=700,
height=700,
left=300,
top=100,
status=no,
location=no`,
});
// Issue credential by issuer
const ethAddress = await proofProvider.getAddress();
const issuer = sybil.issuer("discord-account");
const challenge = await issuer.getChallenge({
subjectId: `ethereum:${ethAddress}`,
redirectUrl: "https://exmaple.com" // redirectUrl is optional
});
provideAuthPopupToUser(challenge.authUrl); // implement it on your own
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await proofProvider.sign({
message: challenge.issueMessage
});
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});Credentials that use an authorization process
have the Authorization flag set to true value
in Supported credentials
section
Verify credential
You can verify credentials using SDK as shown in the example below:
const { isVerified } = await sybil.verify(credential);
// if credential verified `isVerified` === trueWhat is ProofProvider?
Main purpose of proof providers is to create compatibility between wallet providers and our SDK.
Proof provider is an object that implements SubjectProofProvider interface as shown below:
type SubjectProof = {
subjectId: string;
signFn: SignFn;
}
interface SubjectProofProvider {
/** Sign message function */
sign: SignFn;
/** Returns signer address */
getAddress(): Promise<string>
/** creates Subject proof to issue verifiable credential */
proof(): Promise<SubjectProof>
}As you can see in example above, SubjectProofProvider.proof method
returns SubjectProof. To understand what is SubjectProof
see What is SubjectProof?,
What is subjectId? and
What is SignFn?.
In this version of SDK you can use next implementations of SubjectProofProvider interface:
EthProofProvider- for Ethereum wallets. Usage example shown below:
import { EthProofProvider, type IEIP1193Provider } from "@sybil-center/sdk";
const proofProvider = () => {
const injected = "ethereum" in window && (window.ethereum as IEIP1193Provider);
if (!injected) throw new Error(`Only injected provider is supported`);
return new EthProofProvider(injected);
};
const credential = await sybil
.credential("twitter-account", await proofProvider().proof())SolanaProofProvider- for Solana wallets (specifically forPhantom). Usage example shown below:
import { SolanaProofProvider, SolanaProvider } from "@sybil-center/sdk";
const proofProvider = (): SolanaProofProvider => {
const injected = "phantom" in window && (window.phantom as any);
if (injected) {
return new SolanaProofProvider(injected.solana as SolanaProvider);
}
window.open("https://phantom.app/", "_blank");
throw new Error(`Only injected provider is supported`);
};
const credential = await sybil
.credential("discord-account", await proofProvider().proof());Also see What is subjectId? and What is SignFn?
What is subjectId?
subject id in SDK similar to did (did spec) which refers to a credential subject,
but SDK subject id has got aliases to improve development experience.
For example, ethereum:<address> & eip155:1:<address> are
did:pkh:eip155:1:<address> aliases. All aliases have prefix and adress parts, therefore
subject id can be represented as prefix:<address>.
In example above, ethereum, eip155:1 and did:pkh:eip155:1 are prefixes.
Any SDK subject id will be transformed to did:pkh representation (did-pkh spec)
on server side, e.gethereum:<address> will be transformed
to did:pkh:eip155:1:<address>. The transformed subject id will be
used as credentialSubject.id in issued credential.
Prefix (subject id) transform matching are shown in code below
(left side prefix, will be transformed to right side prefix):
const mapPrefix = new Map<Prefix, Prefix>([
["bitcoin", "did:pkh:bip122:000000000019d6689c085ae165831e93"],
["bip122:000000000019d6689c085ae165831e93", "did:pkh:bip122:000000000019d6689c085ae165831e93"],
["did:pkh:bip122:000000000019d6689c085ae165831e93", "did:pkh:bip122:000000000019d6689c085ae165831e93"],
["celo", "did:pkh:eip155:42220"],
["eip155:42220", "did:pkh:eip155:42220"],
["did:pkh:eip155:42220", "did:pkh:eip155:42220"],
["ethereum", "did:pkh:eip155:1"],
["eip155:1", "did:pkh:eip155:1"],
["did:pkh:eip155:1", "did:pkh:eip155:1"],
["polygon", "did:pkh:eip155:137"],
["eip155:137", "did:pkh:eip155:137"],
["did:pkh:eip155:137", "did:pkh:eip155:137"],
["solana", "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ"],
["solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ"],
["did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ"]
]);
/*
* For example:
* 'solana:<address>', 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:<address>'
* and 'did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:<address>'
* will be transformed to 'did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:<address>'
*/What is SignFn?
signFn is function to sign issue message and returns base64 encoded signature.
It's got the next type:
/** Sign func take message as input, returns base64 encoded signature */
export type SignFn = (args: { message: string }) => Promise<string>;Implementation example:
async function solanaSign(args: { message: string }): Promise<string> {
const encodedMessage = new TextEncoder().encode(args.message);
const { signature } = await solanaProvider.request({
method: "signMessage",
params: {
message: encodedMessage,
display: "utf8"
}
});
return u8a.toString(u8a.fromString(signature, "base58btc"), "base64");
}
async function ethereumSign(args: { message: string }): Promise<string> {
const message = args.message;
const address = await getEthAddress();
const hex = u8a.toString(u8a.fromString(message), "hex");
const rawSignature = await ethProvider.request<`0x${string}`>({
method: "eth_sign",
params: [address, hexMessage]
});
const hexSign = signature.substring(2).toLowerCase();
const bytesSign = u8a.fromString(hexSign, "hex");
return u8a.toString(bytesSign, "base64");
}Note
As you can see in examples above, all signatures are transformed to base64
representation
What is SubjectProof?
SubjectProof is object which contains subject id (What is SubjectId?)
and signFn (What is SignFn).
signFn has to provide signature associated with subject id.
SignProof type represented in code below:
type SubjectProof = {
subjectId: string;
signFn: SignFn;
}SubjectProof uses to provide credential issuing from sybil.credential method:
You can use ProofProvider to create SubjectProof or create SubjectProof on your own.
How to create your own SubjectProof shown in examples below.
React and wagmi example:
import { useAccount, useNetwork, useSignMessage } from "wagmi";
import * as u8a from "uint8arrays";
export function useSubjectProof() {
const { address } = useAccount();
const { signMessageAsync } = useSignMessage();
const { chain } = useNetwork();
const signMessage = async (args: { message: string }): Promise<string> => {
const message = args.message;
const sign = await signMessageAsync({ message: message });
return u8a.toString(u8a.fromString(sign.substring(2), "hex"), "base64");
};
return { subjectId: `eip155:${chain?.id}:${address}`, signFn: signMessage };
}
/* ============================================================================= */
import { useSubjectProof } from "hooks";
const { subjectId, signFn } = useSubjectProof();
const credential = await sybil.credential("discord-account", {
subjectId: subjectId,
signFn: signFn
});JavaScript example:
// This is 'EthProofProvider' implementation.
// Here you can see how to provide 'SubjectProof' object
// in 'EthProofProvider.proof' method
export interface IEIP1193Provider {
enable?: () => Promise<void>;
request<T = unknown>(args: RequestArguments): Promise<T>;
}
export class EthProofProvider implements SubjectProofProvider {
constructor(private readonly provider: IEIP1193Provider) {
this.sign = this.sign.bind(this);
this.getAddress = this.getAddress.bind(this);
}
async proof(): Promise<SubjectProof> {
const chainId = await this.getChainId();
const address = await this.getAddress();
return {
subjectId: `did:pkh:eip155:${chainId}:${address}`,
signFn: this.sign
}
}
async sign(args: { message: string }): Promise<string> {
const message = args.message;
const address = await this.getAddress();
const hex = u8a.toString(u8a.fromString(message), "hex");
return await this.#signMessage(address, hex)
}
async getAddress(): Promise<string> {
const accounts = (await this.provider.request<string[]>({
method: "eth_accounts",
}));
const account = accounts[0];
if (!account) {
throw new Error(`Enable Ethereum provider`);
}
return account;
}
async getChainId(): Promise<number> {
return Number(
await this.provider.request<string>({
method: "net_version"
})
);
}
/**
* Return signature as base64 string
* @param address - ethereum 0x<address>
* @param hexMessage - message as hex string
*/
async #signMessage(address: string, hexMessage: string): Promise<string> {
try {
return this.#normalizeSignature(
await this.provider.request<`0x${string}`>({
method: "eth_sign",
params: [address, hexMessage]
})
);
} catch (err) {
const reason = err as Error;
if ("code" in reason && (reason.code === -32602 || reason.code === -32601)) {
return this.#normalizeSignature(
await this.provider.request<`0x${string}`>({
method: "personal_sign",
params: [address, hexMessage],
})
);
}
throw reason;
}
}
#normalizeSignature(signature: string): string {
const hexSign = signature.substring(2).toLowerCase();
const bytesSign = u8a.fromString(hexSign, "hex");
return u8a.toString(bytesSign, "base64");
}
}What is SDK Issuer ?
SDK Issuer is interface that provide communication process to issue credential.
Issuer interface represented in code blow:
import type {
Challenge,
ChallengeReq,
Credential,
IssueReq,
Options,
SubjectProof
} from "../types/index.js";
export interface Issuer<
TCredential = Credential,
TOptions = Options,
TChallengeReq = ChallengeReq,
TChallenge = Challenge,
TIssueReq = IssueReq
> {
/**
* Aggregates {@link getChallenge}, {@link canIssue}, {@link issue},
* to provides issue credential process and returns `credential`.
* @param subjectProof - object contains `subjectId` and `signFn`
* @param options - influence on result `credential` and/or issue process
*/
issueCredential(subjectProof: SubjectProof, options?: TOptions): Promise<TCredential>;
/**
* Sends challengeReq to receive appropriate challenge request.
* Challenge contains `sessionId` and `issueMessage` fields, which are required
* @param challengeReq - entity that contains data to receive appropriate challenge
*/
getChallenge(challengeReq: TChallengeReq): Promise<TChallenge>;
/**
* Method used to check the status of the application to issue credential
* @param sessionId - session identifier
*/
canIssue(sessionId: string): Promise<boolean>;
/**
* Sends issueReq and receive credential
* @param issueReq - contains `sessionId` and `signature`.
* `signature` creates from `challenge.issueMessage`
*/
issue(issueReq: TIssueReq): Promise<TCredential>;
}Each time when you invoke sybil.credential method SDK chooses appropriate Issuer
(e.g sybil.credential("twitter-account", subjectProof) will choose TwitterAccountIssuer
implementation) and uses Issuer.issueCredentialmethod under the hood,
which aggregates getChallenge, canIssue, issue methods to issue appropriate credential.
You can get specific SDK Issuer from subil instance as shown in code below:
const issuer = sybil.issuer("twitter-account"); // get TwitterAccountIssuerHow can it be helpful see in Control under issuing process section.
Control under issuing process
sybil.credential method does some work under the hood, and help
you issue credentials in one line. But you can't manage the issuing process
of credentials. If you need more control under the issuing process, you should use
specific SDK Issuer implementation (What is SDK Issuer ?)
In code below you can see how to get and use Issuer implementation:
// 1-st example
// (more flexible, but part of the logic you should implement on your own)
const issuer = sybil.issuer("twitter-account");
const address = await getAddress();
const challenge = await issuer.getChallenge({
subjectId: `ethereum:${address}`
});
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await sign(challenge.issueMessage);
const credential = await issuer.issue({
sessionId: challenge.sessionId,
signature: signature
});
// 2-nd exmaple
// (simple implimentation but less flexible)
const addres = await getAddress();
const issuer = sybil.issuer("twitter-account");
const credential = await issuer.issueCredential({
subjectId: `ethereum:${address}`,
signFn: sign
});
// 3-rd exmaple
// using sybil instance
const address = await getAddress();
const credential = sybil.credential("twitter-account", {
subjectId: `eip155:1:${address}`,
signFn: sign
});
// All these example lead to the same resultUse SDK on Frontend - Backend
You can use SDK on backend additionally with frontend to reach more security
during the credential issuing process. Note that sybil.credential
and Issuer.issueCredential (What is SDK Issuer?) methods are
valid only for frontend application.
On backend, you should use Issuer implementations to provide
credential issue process.
In code below you can see example of implementation credential issuing process
with backend and frontend:
/* Frontend (get challenge phase) */
const frontendSybil = new Sybil({
// Note: on frontend use ONLY `apiKey`!!!
apiKey: "API key from https://app.sybil.center/devportal"
});
const address = await getEthAddress();
const challenge = await fetch(new URL("https://backend/github-account/challenge"), {
method: "POST",
body: JSON.stringify({
subjectId: `ethereum:${address}`
})
}).then((resp) => resp.json());
/* Backend (get challenge phase) */
const backendSybil = new Sybil({
// Note: on backend you can use `secretKey`
apiKey: "API key from https://app.sybil.center/devportal",
secretKey: "Secret key from https://app.sybil.center/devportal"
});
server.post("/github-account/challenge", async (req) => {
const subjectId = req.body.subjectId;
const issuer = backendSybil.issuer("github-account");
const challenge = await issuer.getChallenge({
subjectId: subjectId
});
return challenge;
});
/* Fronend (prepare issue credential request phase) */
const issuer = frontendSybil.issuer("github-account");
provideAuthPopupToUser(challenge.authUrl);
await waitUntilTrue(await issuer.canIssue(challenge.sessionId));
const signature = await sign(challenge.issueMessage);
await fetch(new URL("https://backend/github-account/issue"), {
method: "POST",
body: JSON.stringify({
signature: signature,
sessionId: challenge.sessionId
})
});
/* Backend (issue credential phase) */
server.post("/github-account/issue", async (req) => {
const issueReq = req.body;
const issuer = backendSybil.issuer("github-account");
const credential = await issuer.issue({
signature: issueReq.signature,
sessionId: issueReq.sessionId
});
return credential;
});As you can see in the example above, Backend-Frontend implementation is more complicated
than only Frontend implementation, but this alternative is more secure and
gives you opportunity to use secretKey on Backend and do not worry that third party
can steal your API key (see API keys section).
Now you can use SDK only on Node.js, but we are going
to extend our SDK for other programming languages.
If you really want to use our issuer service on other programming language,
this swagger API docs can help you
Raw HTTP API
You can integrate with our sybil.centre service to issue verifiable credentials
for the programming language you want,
using these swagger API docs
How to get type and const?
If you need any type or const from SDK you can easy
to get it from @sybil-center/sdk/types module
Demo App
The repository also contains demo React/Next.js application, showing how to use the SDK in your application.
Please, consult demo-app README about how to run a demo locally, and where to look for an example of code in React.