npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@idos-network/idos-sdk

v0.3.4

Published

idOS JavaScript SDK

Downloads

425

Readme

idOS JavaScript SDK

NPM License

Installation

Get our NPM package and its dependencies with pnpm or the equivalent of your package manager of choice:

pnpm add @idos-network/idos-sdk ethers near-api-js

Only add ethers or near-api-js in accordance with the chains your dApp uses.

💡 Tip

If you use near-api-js, make sure you have a Buffer polyfill. See https://github.com/near/near-api-js/issues/757.

Quickstart

Create a container anywhere on your page, and ensure it's displayed when assigned the visible class.

<div id="idos-container"></div>
div#idos-container {
  display: none;
}

/* Style this however you like. */
div#idos-container.visible {
  display: block;
  width: 160px;
}

Import the SDK and initialize it with a selector for the container:

import { idOS } from "@idos-network/idos-sdk";

const idos = await idOS.init({enclaveOptions: {container: "#idos-container"}});

Get your user's address and confirm they have an idOS profile. If not, redirect them to your Issuer.

const hasProfile = await idos.hasProfile(address);
if (!hasProfile) window.location = "https://kyc-provider.example.com/enroll";

Connect your user's signer to complete the setup.

await idos.setSigner("EVM", signer); // e.g. ethers.Signer

You're all set!

const credentials = await idos.data.list("credentials");
console.log(credentials);
// [{ id: "4f4d...", issuer: "Fractal ID", type: "human" }, ...]

const { id } = credentials[0];
const { content } = await idos.data.get("credentials", id);
const isValid = await idOS.verifiableCredentials
  .verify(content)
  .catch((e) => false);

🛈 Note

For more examples and data queries, see:

💡 Tip

Need support? Please follow this process.

Diving deeper

Initialization and the #idos-container

import { idOS } from "@idos-network/idos-sdk";

const idos = await idOS.init({enclaveOptions: {container: "#idos-container"}});

After importing the SDK, you initialize it with a selector string for a DOM node. Make sure to add it to your page:

<div id="idos-container"></div>

This container will be used by the SDK to load the idOS secure enclave during initialization. The 📁 idos-enclave is a sandboxed browser context, used to safekeep a keyring for cryptographic operations users need to perform. When the enclave requires user interaction, it uses this container to render UI such as the 🔓 Unlock idOS button.

To avoid surprising your UI, the SDK doesn't make itself visible and sets no CSS properties. Instead, it toggles the visible class on this container. This means you retain control over your UI, and need to define what "visible" means, for example:

#idos-container {
  display: none;
}

#idos-container.visible {
  display: block;
}

This barebones setup is enough to get you started, but you can naturally style and animate the container as you like, for example within a toast component.

Our 📁 idos-example-dapp shows an example of blending this into a UI. It wraps the container and floats it over the page, and animates its opacity when the visible class is applied. You can see it below (pulsating forcefully to illustrate the point):

The main reason the SDK controls this HTML element is to remove the burden of opening up a new top-level window without being blocked by the browser because it was identified as an unwanted pop-up. Since all SDK users would need to go through the delicate process of getting these details right, we implemented it in the SDK.

Other initialization options

The enclaveOptions's container is the only required option, but there are a few other aspects of the SDK you're able to control during initialization.

nodeUrl

The most obvious one is to which network to connect: production, or playground. These can be found, respectively, at:

  • https://nodes.idos.network (default)
  • https://nodes.playground.idos.network

Here's an example of using the playground network:

const idos = await idos.init({
  nodeUrl: "https://nodes.playground.idos.network",
  enclaveOptions: {container: "#idos-container"},
});

dbId

This concept is very internal to the idOS nodes, and the right value gets automatically discovered during initialization.

Unless you know what you're doing (e.g., deploying a new idOS network with a Kwil schema that's not called idos), omit this field.

Grant options (evmGrantsOptions and nearGrantsOptions)

This is only relevant if you use idos.grants.* methods.

In order for the SDK to know which access grants contract to use, we need to provide evmGrantsOptions or nearGrantsOptions, depending on which network the dApp is deployed on.

The default values come from the .env file the SDK is built with. Assuming that file is available as a global env object, here are the default values for each options object:

const idos = await idos.init({
  enclaveOptions: {container: "#idos-container"},
  evmGrantsOptions: {
    contractAddress: env.VITE_IDOS_EVM_DEFAULT_CONTRACT_ADDRESS,
    chainId: env.VITE_IDOS_EVM_DEFAULT_CHAIN_ID,
  },
  nearGrantsOptions: {
    network: env.VITE_IDOS_NEAR_DEFAULT_NETWORK;
    contractId: env.VITE_IDOS_NEAR_DEFAULT_CONTRACT_ID;
    rpcUrl: env.VITE_IDOS_NEAR_DEFAULT_RPC_URL;
  },
});

You can take a look at the current contract deployments on github.com/idos-network/idos-access-grants.

enclaveOptions

So far, we've only used container from enclaveOptions. There are a few more fields that you can set:

  • theme?: "light" | "dark": Forces a specific theme for the enclave pop-up. By default, this is discovered through the media query prefers-color-scheme.
  • mode?: "new" | "existing": Forces a specific verbiage to be shown on the enclave pop-up. The default is existing, but issuers can set it to new to show messages that are more helpful for new users. Unless you're an issuer, this should not be supplied.
  • url?: string: URL of the enclave pop-up. Unless you're developing your own enclave, this should not be supplied.
  • throwOnUserCancelUnlock?: boolean: Controls the SDK's reaction to the user closing the enclave pop-up. The default, false, keeps the 🔓 Unlock idOS button visible so the user can click it again and finish the unlocking process. If this value is true, the SDK will hide the button and raise whatever error it got from the enclave pop-up.

Using hasProfile

You can check if your user has an idOS profile associated with their address by using await idos.hasProfile(address). This can be done without a signature, and confirms that calls to setSigner should succeed.

const hasProfile = await idos.hasProfile(address);

If your user does not have an idOS profile, you'll have to first redirect them to your credential provider. Here's an example:

if (!hasProfile) window.location = "https://kyc-provider.example.com/enroll";

The setSigner flow and supported wallets

const { humanId } = await idos.setSigner("EVM", signer);

Besides hasProfile, all other queries to idOS nodes require a valid signature. These are performed by your user's wallet, whose signer must be passed to the SDK via the setSigner method. Your user's wallet might need to be triggered, so you should be mindful of when in your user's journey you call this method.

When called, setSigner will try to connect to the idOS nodes, sign a Sign-In With Ethereum (SIWE) message for authentication, and make a call to get some basic information about the user.

🛈 Note for NEAR

Because idOS thinks in terms of signing keys, but NEAR thinks in terms of accounts that can be controlled by multiple signing keys, the SDK needs to discover the signing key that's currently being used. This requires a signed message from the user.

Here's an example of what that looks like with Meteor:

Here's an example of what signing a SIWE message looks like with Metamask:

During this whole process, the SDK tries to use the browser's local storage to remember this signer's address (and public key, for NEAR signers) to avoid repeating this process unless necessary.

The idOS currently supports two classes of signers:

  • Ethereum/EVM wallets (like MetaMask or Trust Wallet) producing EIP-191 secp256k1 signatures (aka personal_sign)
  • NEAR/NVM wallets (like MyNearWallet or Meteor) producing NEP-413 ed25519 signatures (aka signMessage)

Exploring the user's data

Now that we're successfully authenticated on idOS, we can now perform operations on user data. The entities that a user controls are:

  • Wallets: the wallets that the user has declared as being able to control their idOS profile.
  • Credentials: the credentials of a user. Their contents are encrypted (for the user's encryption key), but it also has some public fields for inspection.
  • Attributes: free form key-value entries. You can use this to store public attribute about the user.

All of these can be created, retrieved, updated, or deleted. The only notable exceptions is deleting shared credentials with timelocks still active (more on this when we explain Access Grants).

Here's an example of listing a user's credentials:

const credentials = await idos.data.list("credentials");
console.log(credentials);
// [{ id: "4f4d...", issuer: "FractalID", type: "human" }, ...]

Decrypting the user credential content

🛑 Caution

This is only meant to be used on admin-like dApps (like https://dashboard.idos.network/).

If the user hasn't granted you an Access Grant, the user hasn't consented to you getting a copy of the data. We'll be covering Access Grant in a following section.

For now, please use idOS responsibly and respect the user's will and data sovereignty. In order to protect the user, we're planning on changing how this admin-like access works in the near future, so please don't rely on it.

Today, as a shortcut, we decrypt the credential's content on get:

const { content } = await idos.data.get("credentials", credentials[0].id);

The manual version on this shortcut looks like this:

const credential = await idos.data.get(
  "credentials",
  credentials[0].id,
  false, // `false` here means "don't ask the user to decrypt the contents"
);

const content = await idos.enclave.decrypt(
  credential.content,
  credential.encryption_public_key,
)

This call needs to operate with the user's encryption key. This is a responsibility of the Enclave, which we'll explain in the next section.

Now you have access to the decrypted credential contents. If it's a W3C Verifiable Credential, you can check it's authenticity with:

await idOS.verifiableCredentials.verify(content)

This function always returns true or raises an Error detailing what went wrong with the verification process.

Unlocking the idOS enclave

Credential contents stored in the idOS are encrypted such that only its owner (your user) can make sense of it. Since key management is neither a common nor an expectable practice among non-technical folks, this key is derived from the user's password/passkey. The key derivation process is handled by the idOS secure enclave to enable users to perform authenticated asymmetric ECC encryption / decryption.

Since the SDK does have access to this key, it delegates decryption workloads to the enclave when responding to data requests involving. This happens transparently when you use the SDK to read encrypted data from the idOS.

After the user clicks the 🔓 Unlock idOS button, a secure dialog opens for the user to choose their preferred unlocking method.

If the user chooses Password, they'll be prompted to enter it.

If they choose Passkey, we'll use their platform authenticator (you can learn more about passkeys here).

The selected auth method will not have a bearing on the encryption capabilities.

Intermission: who are you?

Like mentioned before, in order to lawfully obtain a copy of the user's credentials, the user must grant you an Access Grant. We'll expand on that concept next, but first you need to have some preparation measures in place.

There are two things that a dApp needs to have setup:

  • A grantee: a key for a chain account you control. It's used both to identify you as the recipient of an Access Grant and to authenticate you when making calls to idOS nodes. On EVM chains, this is an EOA (Externally-Owned Account, controlled by anyone with its private key), and on NEAR this is a full access public key. For now, the idOS doesn't support having contract wallets as grantees.
  • An encryptionPublicKey: a key you control that'll be used to decrypt credential contents shared with you. This should be a nacl.box.keyPair.

🛑 Caution

Make sure you don't lose access to either secret keys. Otherwise, you won't be able to authenticate or decrypt credential contents. The idOS team won't be able to help you.

Access Grants

An Access Grant means: I, owner (the user), have given you, grantee (the dApp), access to the record identified by dataId, and I understand I won't be able to revoke said access before lockedUntil has passed. The contents of dataId are a copy of the credential/attribute that has its contents encrypted to the encryption key provided (by the dApp) during its creation.

By acquiring an Access Grant, a dApp ensures that it'll have a copy of the user's data (either a credential or an attribute) until the UNIX timestamp on lockedUntil has passed. This is especially relevant to be able to fulfill compliance obligations.

This is achieved by combining two mechanisms:

  • On idOS, by asking the user to share a credential/attribute, which creates a copy of its current state, encrypted to the receiverPublicKey you provide. The id of this copy is what's called dataId.
  • On the blockchain you're using, by creating an Access Grant entry in a Smart Contract on the chain you're using.

The combination of doing these two operations is bundled in idos.grants.create, and that's the intended API for common usage. Here's an example of what that looks like:

// These don't necessarily have to come from a server, but it's the typical setup.
const {
  encryptionPublicKey,
  lockTimeSpanSeconds,
  grantee,
} = await yourBackendService.getInfo();

const { grant: { dataId } } = await idos.grants.create(
  "credentials",
  credentials[0].id,
  grantee,
  Math.floor(Date.now() / 1000) + lockTimeSpanSeconds,
  encryptionPublicKey,
);

💡 Tip

See a working example idos-example-dapp

To avoid any doubts, let's go over the Access Grant fields:

  • owner: on EVM chains this is a wallet address the user controls, and on NEAR this is a full access public key from an account the user controls.
  • grantee: on EVM chains this is the dApp's grantee address (like explained in the previous section), and on NEAR this is a full access public key.
  • dataId: the id of the record copy (either a credential or an attribute) that is going to be shared.
  • lockedUntil: the earliest UNIX timestamp when the contract will allow the Access Grant to be revoked. Any timestamp in the past, notably "0", means it's revocable at any time.

Filtering credentials

One common problem about credentials is: if the dApp can only access a credential's contents after it has an Access Grant for it, how does the dApp know which credential will fulfill its compliance needs?

idos.enclave.filterCredentials is a function that allows you to ask the user's enclave to filter all the user's credentials to only return the ones your dApp is interested in asking an Access Grant for. A filtering criteria for pick and omit should be passed. This should be the paths of the private fields by which a credential should be matched. pick requires the path to have the provided value, omit requires the path to not have the provided value.

const entries = await idos.enclave.filterCredentials(credentials, {
  pick: {
    "credentialSubject.identification_document_country": "DE"
  },
  omit: {
    "credentialSubject.identification_document_type": "passport",
  },
});

In this example, entries will be a list of credentials where the "credentialSubject.identification_document_country" is "DE" and "credentialSubject.identification_document_type" is not "passport".

You can also use idos.grants.shareMatchingEntry, a helper function that:

  • Gets all the user's credentials
  • Can filter by public fields
  • Calls idos.enclave.filterCredentials
  • Calls idos.grants.create with the first matching credential

Here's an example similar to the previous one, but that also filters a couple of public fields:

await idos.grants.shareMatchingEntry(
  "credentials",
  {
    credential_level: "basic",
    credential_type: "kyc",
  },
  {
    pick: {
      "credentialSubject.identification_document_country": "DE"
    },
    omit: {
      "credentialSubject.identification_document_type": "passport",
    },
  },
  grantee,
  Math.floor(Date.now() / 1000) + lockTimeSpanSeconds, // timelock
  encryptionPublicKey,
);

Checking Access Grant contents

By now, we have used the idOS to secure a copy of the data we need to operate.

If you wish to consult it, you'll need to use the grantee and nacl.box.keyPair we've prepared before. Because these are secret, we need call some code in a private place (i.e., a backend, or maybe scripts you run locally).

Here's an example of how you could achieve that with 📁 idos-sdk-server-dapp for an EVM grantee:

import { idOSGrantee } from "@idos-network/idos-sdk-server-dapp";
import { ethers } from "ethers";

const granteeSigner = new ethers.Wallet(
  process.env.EVM_GRANTEE_PRIVATE_KEY,
  new ethers.JsonRpcProvider(process.env.EVM_NODE_URL),
);

const idosGrantee = await idOSGrantee.init({
  chainType: "EVM",
  granteeSigner,
  encryptionSecret: process.env.ENCRYPTION_SECRET_KEY,
});

// This assumes we got `dataId` (from a request body, a script argument, etc).
const contents = await idosGrantee.getSharedCredentialContentDecrypted(dataId);

💡 Tip

See a working example backend on idos-example-dapp/api. It has two flavors:

Delegated Access Grants

A delegated Access Grant (dAG) is a way of creating / revoking an Access Grant by somebody else other than the user. This is especially relevant for dApps who want to subsidize the cost of transaction necessary to create an AG.

Here's a diagram comparing the two cases side-by-side:

This is accomplished by getting the user's signature a specific message, generated with the contract's insert_grant_by_signature_message method, that can then be used to call the contract's insert_grant_by_signature method.

The message building function is exposed as idos.grants.messageForCreateBySignature. Submitting the resulting message and its user signature is exposed as idosGrantee.createBySignature.

🛑 Caution

This is not implemented for NEAR yet. If you want to use dAGs today, you'll have to call the right contract directly.

Quick reference

Importing and initializing

import { idOS } from "@idos-network/idos-sdk";

const idos = await idOS.init({ enclaveOptions: {container: "css selector"} });

EVM signer setup

const CHAIN_TYPE = "EVM";
const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = await provider.getSigner();
const address = await signer.getAddress();

NEAR signer setup

const CHAIN_TYPE = "NEAR";
const {
  defaultContractId: contractId,
  contractMethods: methodNames,
  defaultNetwork: network,
} = idOS.near;

const selector = await setupWalletSelector({
  network,
  modules: [setupMeteorWallet(), setupMeteorWallet()],
});

!selector.isSignedIn() &&
  (await new Promise((resolve) => {
    const modal = setupModal(selector, { contractId, methodNames });

    modal.on("onHide", resolve);
    modal.show();
  }));

const signer = selector.wallet();
const address = (await signer.getAccounts())[0].accountId

Profile checking and setSigner

const hasProfile = await idos.hasProfile(address);
if (!hasProfile) window.location = "https://kyc-provider.example.com/enroll";
const { humanId } = await idos.setSigner(CHAIN_TYPE, signer);

Credentials

// Get all credentials
const credentials = await idos.data.list("credentials");

//Get all credentials that match a condition
const credentials = await idos.data.list("credentials", { issuer: "Fractal ID" };

// Get the credential details
const { id } = credentials.find(c => c.credential_type === "basic");
const { content } = await idos.data.get("credentials", id);

// Validate that a credential is well signed
const isValid = await idOS.verifiableCredentials.verify(content).catch(e => false);

Creating / updating / deleting data

const { id } = await idos.data.create("attributes", {
  attribute_key: "highScore",
  value: "10",
});

await idos.data.update("attributes", { id, value: "1000" });

await idos.data.delete("attributes", id);

Access Grant creation / revocation / list

Here's some example code of creating, revoking and listing Access Grants.

// Decide on the credential you want to create an Access Grant for
const credentialId = credentials[0].id;

// Share a credential by creating an access grant
const { grant } = await idos.grants.create(
  "credential",
  credentialId,
  grantee,
  timelock,
  receiverPublicKey
);

// Revoke an access grant
await idos.grants.revoke(
  "credentials",
  credentialId,
  grantee,
  dataId,
  timelock
);

// List all grants that match a criteria
await idos.grants.list({
  owner,
  dataId,
  grantee,
});

// Share a credential that matches the filtering criteria.
await idos.grants.shareMatchingEntry(
  "credentials",
  {
    credential_level: "basic",
    credential_type: "kyc",
  },
  {
    pick: {
      "credentialSubject.identification_document_country": "DE",
    },
    omit: {},
  },
  grantee,
  0, // timelock
  "zleIscgvb3usjyVqR4OweNM2oXwmzADJVO3g7byuGk8=", // receiverPublicKey
);

Creating a dAG on EVM

/*
 * Client side.
 */

// Create a share (duplicate) in the idOS and get its id:
const { id: dataId } = await idos.data.share(tableName, recordId, receiverPublicKey);

// Get a message that needs to be signed by the user:
const message = await idos.grants.messageForCreateBySignature({
  owner,
  grantee,
  dataId,
  lockedUntil
});

// The dApp should ask the user to sign this message:
const { signature } = await wallet.signMessage({ message, recipient, nonce });

/*
 * Server side.
 *
 * !! NOT IMPLEMENTED FOR NEAR YET !!
 */
import { idOSGrantee } from "@idos-network/idos-sdk-server-dapp";
import { ethers } from "ethers";

const granteeSigner = new ethers.Wallet(
  process.env.EVM_GRANTEE_PRIVATE_KEY,
  new ethers.JsonRpcProvider(process.env.EVM_NODE_URL),
);

// Initialize the idOSGrantee
const idosGrantee = await idOSGrantee.init({
  chainType: "EVM",
  granteeSigner,
  encryptionSecret: process.env.ENCRYPTION_SECRET_KEY
});

// Create the dAG
await idosGrantee.createBySignature({
  // These values need to be the same you used to generate the signed message
  owner,
  grantee,
  dataId,
  lockedUntil,
  // This is the signature you got from the user.
  signature,
})

Developing the SDK locally

Create an .env.local file in the root folder of the SDK package and add the needed environment variables (you can reference .env.production for the variable names).

Run:

pnpm dev

This will run a dev server with watch mode that will rebuild every time any of the source files are changed.

You can also create a production build by running the following command in the root folder of the SDK package:

pnpm build

This will create a PRODUCTION build of the SDK using the .env.production file.