LearnCard Documentation
GithubStatusSupportLaunch App
  • 🚀Introduction
    • What is LearnCard?
    • Use Cases & Possibilities
    • Ecosystem Architecture
  • ⚡Quick Start
    • Setup & Prerequisites
    • Your First Integration
  • 📚Tutorials
    • Create a Credential
    • Create a Boost
    • Create a ConsentFlow
    • Create a Connected Website
    • Send xAPI Statements
    • Listen to Webhooks
  • ✅How-To Guides
    • Verify My Issuer
    • Connect Systems
      • Connect a Website
      • Connect a Game
    • Implement Flows
      • Claim Data after Guardian Consent
      • Connect via CHAPI
        • ⭐CHAPI Wallet Setup Guide
        • ↔️Translating to CHAPI documentation
        • 🖥️Demo Application
        • 🔰Using LearnCard to Interact with a CHAPI Wallet
        • 📝Cheat Sheets
          • Issuers
          • Wallets
    • Deploy Infrastructure
      • Remote Key Management
      • Generate API Tokens
      • Signing Authority
      • Connect to Independent Network
      • Build a Plugin
  • 🛠️SDKs & API Reference
    • LearnCard Wallet SDK
      • Authentication
      • Usage Examples
      • SDK Reference
      • Plugin API Reference
      • Integration Strategies
      • Deployment
      • Troubleshooting
      • Changelog
    • LearnCloud Network API
      • Authentication
      • Usage Examples
      • Architecture
      • Notifications & Webhooks
      • Profiles
      • Profile Managers
      • Credentials
      • Boosts
      • Presentations
      • Storage
      • Contracts
      • DID Metadata
      • Claim Hooks
      • Auth Grants
      • Utilities
      • Models
      • OpenAPI
    • LearnCloud Storage API
      • Authentication
      • Usage Examples
      • Architecture
      • Storage
      • Index
      • User
      • Custom Storage
      • Utilities
      • Models
      • xAPI Reference
    • Plugins
      • Crypto
      • DIDKit
      • DID Key
      • Dynamic Loader
      • VC
        • Expiration Sub-Plugin
      • VC-Templates
      • VC-API
      • Ceramic
      • IDX
      • VPQR
      • Ethereum
      • CHAPI
      • LearnCard Network
      • LearnCloud
      • LearnCard
      • Simple Signing
      • Claimable Boosts
    • LearnCard CLI
  • 🧠Core Concepts
    • Identities & Keys
      • Decentralized Identifiers (DIDs)
      • Seed Phrases
      • Network Profiles
      • Signing Authorities
      • Trust Registries
    • Credentials & Data
      • Verifiable Credentials (VCs)
      • Credential Lifecycle
      • Schemas, Types, & Categories
      • Building Verifiable Credentials
      • Boost Credentials
      • Getting Started with Boosts
      • Credential URIs
      • xAPI Data
      • General Best Practices & Troubleshooting
    • Consent & Permissions
      • ConsentFlow Overview
      • Consent Contracts
      • User Consent & Terms
      • Consent Transactions
      • Auto-Boosts
      • Writing Consented Data
      • Accessing Consented Data
      • GameFlow Overview
    • Network & Interactions
      • Network Vision & Principles
      • Key Network Procedures
      • Core Interaction Workflows
    • Architecture & Principles
      • Control Planes
      • Plugin System
      • Auth Grants and API Tokens
  • 🔗Development
    • Contributing
Powered by GitBook
On this page
  • Plugins Are:
  • Plugin Interface
  • Adding Plugins
  • Example: Expiration Plugin
  • Interacting with Plugins
  • Plugin Data Types
  • Graph of Plugins

Was this helpful?

  1. Core Concepts
  2. Architecture & Principles

Plugin System

What is a plugin?

PreviousControl PlanesNextAuth Grants and API Tokens

Last updated 1 month ago

Was this helpful?

The LearnCard Plugin System is the modular foundation that enables extensibility of the LearnCard Core through encapsulated units of functionality. This document explains how plugins are structured, loaded, and used within the LearnCard ecosystem.

Plugins Are:

  • Core bundles of execution.

  • Have no hard requirements they must conform to.

  • Atomic.

  • Not categorical.

  • Typically fall into larger execution workflows. See .

Plugins can implement arbitrary functionality, or optionally choose to implement any number of Control Planes. Via a common instantiation pattern, it is also easily possible for plugins to depend on other plugins via their exposed interfaces, as well as to happily hold private and public fields.

The LearnCard Core is designed with minimal base functionality, which is extended through plugins that provide specific capabilities. The plugin system enables developers to include only the features they need, resulting in more lightweight and focused implementations.

Plugins are at the heart of LearnCard. The base LearnCard wallet without any plugins attached to it can do, well... nothing!

Additionally, because plugins and plugin hierarchies work entirely through interfaces rather than explicit dependencies, any plugin can be easily hotswapped by another plugin that implements the same interface, and plugins implementing Control Planes can easily be stacked on top of each other to further enhance the functionality of that Control Plane for the wallet.

Plugin Interface

A LearnCard plugin follows a standard interface structure which includes:

  • name: A unique identifier string

  • displayName: A human-readable name for UI display

  • description: A description of the plugin's functionality

  • methods: An object containing the functions provided by the plugin

The type system uses generics to track which plugins are added to a LearnCard instance, ensuring type safety when accessing plugin methods.

Adding Plugins

In order to create a fully functioning LearnCard, you will need to add some plugins. Specifically, you will (probably) want to add at least one plugin that implements each Control Plane.

In code, constructing a LearnCard completely from scratch looks like this:

const baseLearnCard = await initLearnCard({ custom: true });
const didkitLearnCard = await baseLearnCard.addPlugin(await getDidKitPlugin());
const didkeyLearnCard = await didkitLearnCard.addPlugin(await getDidKeyPlugin(didkitLearnCard, 'a', 'key'));
// repeat for any more plugins you'd like to add

However, you don't have to start from scratch! Each instantiation function is completely able to add bespoke plugins to it:

const emptyLearnCard = await initLearnCard();
const customLearnCard = await emptyLearnCard.addPlugin(CustomPlugin);

Example: Expiration Plugin

Let's examine how the Expiration Plugin is implemented to understand a typical plugin structure:

The Expiration Plugin extends the credential verification process to check expiration dates:

  1. It depends on the VC Plugin's verifyCredential method

  2. It calls the original method to perform base verification

  3. It adds additional checks for credential expiration

  4. It returns an enhanced verification result

This demonstrates how plugins can build upon each other's functionality in a composable manner.

Interacting with Plugins

After initialization, applications interact with plugins through the LearnCard instance's invoke property. The method calls follow this pattern:

This unified interface allows applications to access all plugin functionality through a single entry point, abstracting away the details of which plugin provides which method.

Plugin Data Types

Plugins often work with specific data types, particularly for credential operations. The core types used across plugins include:

Type
Description

UnsignedVC

Unsigned Verifiable Credential data structure

VC

Signed Verifiable Credential with proof

UnsignedVP

Unsigned Verifiable Presentation

VP

Signed Verifiable Presentation with proof

Proof

Cryptographic proof structure

Profile

Entity profile information

These types are defined in the @learncard/types package and used consistently across all plugins to ensure interoperability.

Graph of Plugins

To learn more about , as well as find recommended Plugins for each Control Plane, click !

🧠
Control Planes
here
Control Planes
https://github.com/learningeconomy/LearnCard/blob/942bb5f7/packages/learn-card-core/src/types/LearnCard.ts#L1-L7
import { LearnCard, Plugin } from 'types/wallet';

export type GetPlugins<LC extends LearnCard<any, any, any>> = LC['plugins'];

export type AddPlugin<LC extends LearnCard<any, any, any>, P extends Plugin> = LearnCard<
    [...GetPlugins<LC>, P]
>;
https://github.com/learningeconomy/LearnCard/blob/942bb5f7/packages/learn-card-types/src/vc.ts#L174-L177
export const VCValidator = UnsignedVCValidator.extend({
    proof: ProofValidator.or(ProofValidator.array()),
});
export type VC = z.infer<typeof VCValidator>;
https://github.com/learningeconomy/LearnCard/blob/942bb5f7/packages/plugins/expiration/src/index.ts#L10-L33
export const expirationPlugin = (
    learnCard: LearnCard<any, any, VerifyExtension>
): ExpirationPlugin => ({
    name: 'Expiration',
    displayName: 'Expiration Extension',
    description: "Adds a check to make sure credentials aren't expired when verifying them",
    methods: {
        verifyCredential: async (_learnCard, credential, options) => {
            const verificationCheck = await learnCard.invoke.verifyCredential(credential, options);

            if (credential.expirationDate && new Date() > new Date(credential.expirationDate)) {
                verificationCheck.errors.push('expiration error: Credential is expired');
            } else if (credential.validFrom && new Date() < new Date(credential.validFrom)) {
                verificationCheck.errors.push('expiration error: Credential is not valid yet');
            } else if (credential.validUntil && new Date() > new Date(credential.validUntil)) {
                verificationCheck.errors.push('expiration error: Credential is no longer valid');
            } else {
                verificationCheck.checks.push('expiration');
            }

            return verificationCheck;
        },
    },
});
https://github.com/learningeconomy/LearnCard/blob/942bb5f7/packages/learn-card-types/src/vc.ts#L129-L158
export const UnsignedVCValidator = z
    .object({
        '@context': ContextValidator,
        id: z.string().optional(),
        type: z.string().array().nonempty(),
        issuer: ProfileValidator,
        credentialSubject: CredentialSubjectValidator.or(CredentialSubjectValidator.array()),
        refreshService: RefreshServiceValidator.or(RefreshServiceValidator.array()).optional(),
        credentialSchema: CredentialSchemaValidator.or(
            CredentialSchemaValidator.array()
        ).optional(),

        // V1
        issuanceDate: z.string().optional(),
        expirationDate: z.string().optional(),
        credentialStatus: CredentialStatusValidator.or(
            CredentialStatusValidator.array()
        ).optional(),

        // V2
        name: z.string().optional(),
        description: z.string().optional(),
        validFrom: z.string().optional(),
        validUntil: z.string().optional(),
        status: CredentialStatusValidator.or(CredentialStatusValidator.array()).optional(),
        termsOfUse: TermsOfUseValidator.or(TermsOfUseValidator.array()).optional(),
        evidence: VC2EvidenceValidator.or(VC2EvidenceValidator.array()).optional(),
    })
    .catchall(z.any());
export type UnsignedVC = z.infer<typeof UnsignedVCValidator>;