LearnCard Developer Docs
  • 🚀Get Started
    • 👋Welcome
    • ⭐Who are you?
      • Learners & Employees
      • Traditional Educator
      • Non-Traditional Educator
      • Assessment Provider
      • Employer
      • App Developer & EdTech
      • DAO & Communities
      • Content Creators
      • Research Institutions
      • NGOs & Governments
      • Plugfest Partner
        • Guide for Interop Issuers
          • 🤽Creating an Interop Issuer
        • Guide for Interop Wallets
    • Protocol Overview
      • The Internet of Education
      • The Learning Economy
      • Learner & Employee Privacy
      • 22nd Century Education
      • The Open Credential Network
      • PVCs
  • 🔰LearnCard SDK
    • What is LearnCard?
      • Why a Universal Wallet?
      • Architectural Patterns
      • Production Deployment Guide
      • Troubleshooting Guide
    • LearnCard Core
      • Quick Start
        • Create New Credentials
          • Creating Verifiable Credentials for LearnCard
          • Achievement Types and Categories
          • Custom Types
          • Understanding Boosts
          • Creating Boost Credentials
        • Sign & Send Credentials
        • Accept & Verify Credentials
        • Share & Present Credentials
      • Construction
        • Managing Seed Phrases
        • initLearnCard
        • DIDKit
        • learnCardFromSeed
        • emptyLearnCard
        • IDX Config
      • Control Planes
        • ID
        • Read
        • Store
        • Index
        • Cache
        • Context
      • Plugins
        • Adding Plugins
        • Official Plugins
          • Dynamic Loader
          • Crypto
          • DIDKit
          • DID Key
          • VC
            • Expiration Sub-Plugin
          • VC Resolution
          • VC-Templates
          • VC-API
          • Ceramic
          • IDX
          • VPQR
          • Ethereum
          • CHAPI
          • LearnCard Network
          • LearnCloud
          • LearnCard
          • Claimable Boosts
        • Writing Plugins
          • The Simplest Plugin
          • The Plugin Type
          • The LearnCard Type
          • Implementing Control Planes
          • Implementing Methods
          • The Implicit LearnCard
          • Depending on Plugins
          • Private Fields
          • Publishing a Plugin to NPM
      • URIs
      • CHAPI
        • ⭐CHAPI Wallet Setup Guide
        • ↔️Translating to CHAPI documentation
        • 🖥️Demo Application
        • 🔰Using LearnCard to Interact with a CHAPI Wallet
        • 📝Cheat Sheets
          • Issuers
          • Wallets
      • LearnCard UX
        • Quick Start
        • Components
          • Verifiable Credentials
            • VC Thumbnail
            • VC Thumbnail, Mini
          • LearnCards
            • LearnCard Front
            • LearnCard Back
        • API
      • LearnCard Bridge
      • API
      • Migration Guide
    • LearnCard Network
      • LearnCard Network API
        • Authentication
        • Auth Grants and API Tokens
        • Profile
        • Credentials
        • Boosts
        • Presentations
        • Storage
        • Signing Authorities
        • Notifications
        • API Docs
        • Launch Your Own Network
      • 🔌Connect Your Application
    • ConsentFlow
      • Setting Up ConsentFlow with an Independent Network
    • GameFlow
      • Sending xAPI Statements
        • xAPI URIs
      • Reading xAPI Statements
        • Advanced xAPI Statement Queries
      • Consentful "Claim Later" Flow
  • 🚀Applications
    • LearnCard
    • SuperSkills!
      • SuperSkills! SDK
        • Digital Wallets
        • Issuing into SuperSkills!
        • 🦸Creating a SuperSkills! Issuer
    • Metaversity
    • Admin Dashboard
  • 🔗Resources
    • Github
    • Community
    • 💅Custom Development
    • Contact Our Team
    • Learning Economy
  • 🤖LearnCard Services
    • LearnCard CLI
    • Discord Bot
    • Metamask Snap
  • 💸LearnBank SDK
    • Why LearnBank?
  • 📊LearnGraph SDK
    • Why LearnGraph?
Powered by GitBook
On this page
  • Prerequisites
  • Step 1: Create a Service Profile
  • Step 2: Create a ConsentFlow Contract
  • Step 3: Add a "Connect Your Wallet" Button to Your Website
  • Step 4: Handle the Redirect After User Consent
  • Step 5: Reading Data from Connected LearnCards
  • Step 6: Sending Credentials to Users
  • Best Practices
  • Step 7: Working with Boosts
  • Troubleshooting

Was this helpful?

  1. LearnCard SDK
  2. ConsentFlow

Setting Up ConsentFlow with an Independent Network

ConsentFlow contracts allow third-party applications to read from and write to a LearnCard with the consent of the LearnCard owner. This guide will walk you through setting up a ConsentFlow with an Independent Network (instead of the default LearnCard Network) and Independent Wallet (instead of the default LearnCard App) to connect your external, 3rd party application.

Prerequisites

  • Node.js environment

  • Access to an Independent Network endpoint

  • Access to an Independent Wallet Application

Step 1: Create a Service Profile

First, you need to initialize the LearnCard CLI and create a network LearnCard that points to your Independent Network:

// Install and start LearnCard CLI
// pnpm dlx @learncard/cli

// Initialize LearnCard with your Independent Network
const networkLearnCard = await initLearnCard({ 
    seed: '[your secure key]', 
    network: 'https://network.independent.example.org/trpc'  // Point to your Independent Network
})

// Create a service profile
const serviceProfile = {
  displayName: 'Your App Name',
  profileId: 'your-app-unique-id',
  image: 'https://example.com/your-app-logo.jpg',
};

// Register the service profile
await networkLearnCard.invoke.createServiceProfile(serviceProfile);

Your service profile represents your application in the LearnCard ecosystem. Make sure to use a unique profileId and provide a clear displayName and image to help users recognize your service.

Step 2: Create a ConsentFlow Contract

Next, create a ConsentFlow contract that specifies what data your application needs to read from and write to users' LearnCard wallets:

const consentFlowContract = {
    "name": "Your App Integration",
    "subtitle": "Connect your LearnCard to Your App",
    "description": "This connection allows Your App to access and issue credentials on your LearnCard.",
    "image": "https://example.com/your-app-logo.jpg",
    "redirectUrl": "https://your-app.com/callback", // Where to redirect after consent
    "contract": {
        "read": {
            "anonymize": true, // Set to false if you need identifiable information
            "credentials": {
                "categories": {
                    // Specify credential categories your app needs to read
                    "Merit Badge": {
                        "required": true
                    },
                    "Skill": {
                        "required": false
                    }
                }
            },
            "personal": {
                "Name": {
                    "required": false
                }
            }
        },
        "write": {
            "credentials": {
                "categories": {
                    // Specify credential categories your app will write
                    "Merit Badge": {
                        "required": true
                    },
                    "Skill": {
                        "required": false
                    }
                }
            },
            "personal": { 
                "SomeCustomID": {
                    "required": true
                }
            }
        }
    }
};

// Create the contract and get its URI
const contractUri = await networkLearnCard.invoke.createContract(consentFlowContract);

// Generate consent flow URLs for your Independent Wallet connected to your Network
const productionUrl = `https://wallet.independent.example.org/consent-flow?uri=${contractUri}`;

console.log("Production ConsentFlow URL:", productionUrl);

Available Credential Categories

When specifying credential categories in your contract, ensure you are using categories supported by your network. For example, many networks support a subset of the following categories:

  • Achievement

  • Accommodation

  • Accomplishment

  • Course

  • ID

  • Job

  • Learning History

  • Membership

  • Merit Badge

  • Skill

  • Social Badge

  • Work History

Step 3: Add a "Connect Your Wallet" Button to Your Website

Add a button or link on your application that directs users to the ConsentFlow URL:

<a href="https://wallet.independent.example.org/consent-flow?uri=YOUR_CONTRACT_URI" 
   class="connect-button">
   Connect Your Wallet
</a>

When users click this button, they'll be directed to the ConsentFlow consent page where they can review and approve the permissions your app is requesting.

Step 4: Handle the Redirect After User Consent

After a user consents to your contract, they'll be redirected to the URL specified in your contract's redirectUrl parameter with their DID added as a query parameter:

https://your-app.com/callback?did=did:method:profile-id

In your application, implement a handler for this callback:

// Example Express.js route handler
app.get('/callback', (req, res) => {
  const userDid = req.query.did;
  
  if (!userDid) {
    return res.status(400).send('Missing DID parameter');
  }
  
  // Store the DID in your database, associating it with the user's account
  storeUserDid(currentUser.id, userDid)
    .then(() => {
      res.redirect('/dashboard?connection=success');
    })
    .catch(error => {
      console.error('Failed to store user DID:', error);
      res.status(500).send('Failed to complete connection');
    });
});

// Function to store the DID in your database
async function storeUserDid(userId, did) {
  // Implementation depends on your database
  await db.users.update({
    where: { id: userId },
    data: { learnCardDid: did }
  });
}

Step 5: Reading Data from Connected LearnCards

To read data from users who have consented to your contract:

// Retrieve ConsentFlow data for your contract
async function getLearnCardData(contractUri, options = {}) {
  let data = await networkLearnCard.invoke.getConsentFlowData(contractUri, options);
  
  // Process the returned records
  for (const record of data.records) {
    // Access shared credentials by category
    const meritBadges = record.credentials.categories['Merit Badge'] || [];
    
    for (const credentialUri of meritBadges) {
      // Read the credential
      const credential = await networkLearnCard.read.get(credentialUri);
      // Process the credential
      console.log('Retrieved credential:', credential);
    }
    
    // Access personal information if shared
    const userName = record.personal['Name'];
    
    // Store or process the retrieved data
  }
  
  // Handle pagination if there are more records
  if (data.hasMore) {
    // Get the next page
    const nextPageData = await getLearnCardData(contractUri, { cursor: data.cursor });
    // Combine with current data
    data.records = [...data.records, ...nextPageData.records];
  }
  
  return data;
}

Step 6: Sending Credentials to Users

Option A: Using LearnCard SDK

To issue credentials to connected users with the LearnCard SDK:

// Function to send a credential to a user by their DID
async function sendCredentialToUser(did, credentialData) {
  // Extract the profileId from the DID
  // This is a simplified example - you'll need to implement proper DID resolution
  const profileId = did.split(':').pop();
  
  // Create a new credential
  const newCredential = networkLearnCard.invoke.newCredential({
        type: 'boost', 
        boostName: 'Hello from External App',
        boostImage: 'https://placehold.co/400x400?text=External+App', 
        achievementType: 'Achievement',
        achievementName:'Connected External App',
        achievementDescription: 'Awarded for connecting to a 3rd party app.',
        achievementNarrative: 'Created and connected a full external app.'
        achievementImage: 'https://placehold.co/400x400?text=External+App',   
    });
  
  // Issue the credential
  const vc = await networkLearnCard.invoke.issueCredential(newCredential);
  
  // Send the credential to the user (encrypted for security)
  const encrypt = true;
  await networkLearnCard.invoke.sendCredential(profileId, vc, encrypt);
  
  return vc;
}

Option B: Using HTTP Endpoints

If you prefer not to use the LearnCard npm packages, you can send credentials directly using HTTP endpoints. This approach is useful for integrations in languages other than JavaScript or in environments where installing npm packages might be challenging.

/**
 * Send a credential to a user by their profileId using HTTP endpoints
 * @param {string} profileId - The user's profile ID
 * @param {object} credential - The credential to send
 * @param {string} apiBaseUrl - The base URL for the network API
 * @param {string} secretToken - Your authorization token
 */
async function sendCredentialViaHttp(profileId, credential, apiBaseUrl, secretToken) {
  const endpoint = `${apiBaseUrl}/api/credential/send/${profileId}`;
  
  try {
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        "Authorization": `Bearer ${secretToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ credential })
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to send credential:', error);
    throw error;
  }
}

Example Usage with a Complete Credential

// First, extract the profileId from the user's DID that you stored
const profileId = userDid.split(':').pop(); // Simplified example

// Define a complete credential
const credential = {
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
  ],
  "id": "https://yourapp.com/credentials/1234",
  "type": [
    "VerifiableCredential",
    "OpenBadgeCredenial"
  ],
  "issuer": "did:example:issuer",
  "issuanceDate": new Date().toISOString(),
  "name": "Course Completion",
  "credentialSubject": {
    "id": userDid, // The user's DID
    "type": ["AchievementSubject"],
    "achievement": {
      "id": "https://yourapp.com/achievements/course-completion",
      "type": ["Achievement"],
      "name": "Course Completion",
      "description": "Successfully completed the Introduction to Blockchain course",
      "criteria": {
        "narrative": "Completed all modules with a score of 85% or higher",
      },
      "image": "https://yourapp.com/badges/blockchain-intro.png"
    }
  }
};

// Send the credential
const apiBaseUrl = "https://network.independent.example.org/api"; // Your network's base URL
const secretToken = "YOUR_SECRET_TOKEN"; // Your authorization token

sendCredentialViaHttp(profileId, credential, apiBaseUrl, secretToken)
  .then(response => {
    console.log("Credential sent successfully:", response);
  })
  .catch(error => {
    console.error("Failed to send credential:", error);
  });

Implementation in Other Languages

Python Example

import requests
import json
from datetime import datetime, timedelta

def send_credential_via_http(profile_id, credential, api_base_url, secret_token):
    """
    Send a credential to a user by their profileId using HTTP endpoints
    
    Args:
        profile_id (str): The user's profile ID
        credential (dict): The credential to send
        api_base_url (str): The base URL for the network API
        secret_token (str): Your authorization token
        
    Returns:
        dict: The response from the API
    """
    endpoint = f"{api_base_url}/api/credential/send/{profile_id}"
    
    headers = {
        "Authorization": f"Bearer {secret_token}",
        "Content-Type": "application/json"
    }
    
    try:
        response = requests.post(
            endpoint,
            headers=headers,
            data=json.dumps({"credential": credential})
        )
        
        response.raise_for_status()  # Raise an exception for HTTP errors
        
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error sending credential: {e}")
        raise

Best Practices

  1. Security: Always use a strong, secure seed for your LearnCard initialization and keep your secret tokens secure.

  2. Privacy: Only request access to the credential categories and personal information that your application actually needs.

  3. User Experience: Clearly explain to users why your application needs access to their LearnCard and what benefits they'll receive.

  4. Error Handling: Implement robust error handling for all LearnCard operations and HTTP requests.

  5. Persistence: Store the contract URI securely - you'll need it for all future operations with that contract.

  6. Authentication: When using HTTP endpoints, ensure proper authentication mechanisms are in place to protect sensitive operations.

  7. Validation: Always validate credential data before sending to ensure it meets the required format and schema.

Step 7: Working with Boosts

Boosts are a powerful feature that allow you to create reusable credential templates that can be sent to multiple recipients while maintaining analytics and management capabilities.

What is a Boost?

A Boost is a registered credential template in the network that can be:

  • Sent to multiple recipients

  • Tracked with analytics

  • Managed through permissions

  • Organized into categories

Why Use Boosts Instead of Direct Credential Sending

Understanding the difference between Boosts and direct credential sending is important:

Feature
Direct Credential Sending
Boosts

Analytics

No analytics available

Track how many users claimed, viewed analytics

Recipients

One credential per send operation

Same template can be sent to multiple users

Management

No central management

Can edit, update, and manage permissions

Organization

No categorization

Can be organized by type and category

Permissions

Limited control

Fine-grained permission management

Creating a Boost Using HTTP Endpoints

You can create a Boost using the HTTP API:

/**
 * Create a boost using HTTP endpoints
 * @param {object} boostData - The boost definition
 * @param {string} apiBaseUrl - The base URL for the network API
 * @param {string} secretToken - Your authorization token
 */
async function createBoostViaHttp(boostData, apiBaseUrl, secretToken) {
  const endpoint = `${apiBaseUrl}/api/boost/create`;
  
  try {
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        "Authorization": `Bearer ${secretToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(boostData)
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to create boost:', error);
    throw error;
  }
}

Example Boost Creation

Here's a complete example of creating a merit badge Boost:

javascriptCopy// Define the boost data
const meritBadgeBoost = {
  "name": "Knot Master",
  "type": "ext:KnotMaster",
  "category": "Social Badge",
  "status": "LIVE", // DRAFT or LIVE
  "credential": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json",
      "https://ctx.learncard.com/boosts/1.0.0.json"
    ],
    "attachments": [],
    "credentialSubject": {
      "achievement": {
        "achievementType": "ext:KnotMaster",
        "criteria": {
          "narrative": "The Friendship Knot Fanatic Badge is awarded to the Scout demonstrating a consistent and passionate dedication to tying friendship knots."
        },
        "description": "For the Scout tying friendship knots every day!",
        "id": "urn:uuid:123",
        "image": "https://cdn.filestackcontent.com/CKa9uvnqTrWYHlG2B5af",
        "name": "Friendship Knot Fanatic",
        "type": [
          "Achievement"
        ]
      },
      "type": [
        "AchievementSubject"
      ]
    },
    "display": {
      "backgroundColor": "",
      "backgroundImage": "",
      "displayType": "",
      "emoji": {
        "activeSkinTone": "",
        "imageUrl": "",
        "names": [],
        "unified": "",
        "unifiedWithoutSkinTone": ""
      }
    },
    "groupID": "",
    "image": "https://cdn.filestackcontent.com/CKa9uvnqTrWYHlG2B5af",
    "issuanceDate": "2025-04-04T19:31:38.373Z",
    "name": "Friendship Knot Fanatic",
    "skills": [],
    "type": [
      "VerifiableCredential",
      "OpenBadgeCredential",
      "BoostCredential"
    ]
  }
};

// Create the boost
const apiBaseUrl = "https://network.independent.example.org/api"; // Your network's base URL
const secretToken = "YOUR_SECRET_TOKEN"; // Your authorization token

createBoostViaHttp(meritBadgeBoost, apiBaseUrl, secretToken)
  .then(response => {
    console.log("Boost created successfully with URI:", response.uri);
    // Store this URI for future use when sending the boost
  })
  .catch(error => {
    console.error("Failed to create boost:", error);
  });

Sending a Boost to Recipients

Once you've created a Boost, you can send it to recipients using their profileId:

/**
 * Send a boost to a user by their profileId using HTTP endpoints
 * @param {string} profileId - The recipient's profile ID
 * @param {string} boostUri - The URI of the boost to send
 * @param {object} credential - Optional: customized credential for this recipient
 * @param {object} options - Optional: additional options like skipNotification
 * @param {string} apiBaseUrl - The base URL for the network API
 * @param {string} secretToken - Your authorization token
 */
async function sendBoostViaHttp(profileId, boostUri, credential, options, apiBaseUrl, secretToken) {
  const endpoint = `${apiBaseUrl}/api/boost/send/${profileId}`;
  
  const payload = {
    uri: boostUri,
    credential: credential // Optional: if you want to customize the credential for this recipient
  };
  
  if (options) {
    payload.options = options;
  }
  
  try {
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        "Authorization": `Bearer ${secretToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(payload)
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to send boost:', error);
    throw error;
  }
}

Best Practices for Working with Boosts

  1. Create Reusable Templates: Design your Boosts to be reusable across multiple recipients.

  2. Use Meaningful Categories: Organize Boosts into logical categories for easier management.

  3. Set Appropriate Permissions: Define who can issue, edit, and manage your Boosts.

  4. Use DRAFT Status: When creating a new Boost, set it to DRAFT until you're ready to start issuing.

Troubleshooting

  • Connection Issues: Ensure your network endpoint is correctly configured and accessible.

  • Missing DIDs: Verify that users are completing the consent process and your redirect handler is properly extracting the DID.

  • Credential Read Failures: Check that your contract has the necessary read permissions for the credential categories you're trying to access.

  • Credential Write Failures: Verify that your contract has the necessary write permissions and that you're using the correct profileId when sending credentials.

  • HTTP Errors: For HTTP endpoint issues, check your authorization token, ensure the correct endpoint URL, and verify your payload structure meets the API requirements.

  • Authentication Problems: Ensure you're using the correct authorization token and that it hasn't expired.

  • Profile ID Format: When extracting profileId from a DID, make sure you're using the correct format required by the network.

  • Boost Creation Failures: Verify that all required fields are provided and properly formatted in your Boost creation request.

  • Boost Sending Issues: Ensure the Boost URI is valid and that you have permission to issue the Boost.

By following this guide, you should be able to successfully integrate LearnCard with your application using an Independent Network, allowing users to connect their LearnCards and exchange credentials securely.

PreviousConsentFlowNextGameFlow

Last updated 28 days ago

Was this helpful?

🔰