Hey there! 👋 Let's build a CHAPI Wallet App together with LearnCard!
Pre-Reqs
pnpm
For this example, we will be using pnpm as a package manager, so let's make sure we have it installed!
npmi-gpnpm
Astro
Astro is a wonderful new Javascript framework that we will be using to build this app. If you haven't heard of or used Astro before, have no fear! It is very similar to React, and allows you to import and use React components when necessary, so if you've used React before, you should feel very comfortable using Astro!
Vite
Under the hood, Astro is using Vite to bundle and serve files. If you haven't heard of Vite before, I highly recommend checking it out, and definitely consider using it any time you need to make a new website as a replacement for Webpack! We will only need to do a tiny bit of Vite configuration, so have no fear, Vite is only here to make our development process faster!
ESBuild
Under the hood, Vite is using ESBuild to transpile files. This means that ESBuild is ultimately responsible for stripping out TypeScript types, and for converting common js and esm modules back and forth. We won't need to much configuration with ESBuild at all for this site, however, it is definitely important to be aware that it exists under the hood if you find yourself running into issues!
Boilerplate
Astro
We will use Astro to create this app, so let's go ahead and begin!
pnpmcreateastrochapi-example> Just the basics (recommended)> Would you like to install pnpm dependencies? (recommended) > Y> Would you like to initialize a new git repository? (optional) > Y> How would you like to setup TypeScript?> Strict (recommended)
React and Tailwind
Great! Now let's cd in and start setting up some boilerplate. To start, let's add support for React and Tailwind
We are going to be importing from a few common directories. To make this easier to do, let's set up some quick TS aliases! Open up the tsconfig.json file and add the following:
CHAPI requires us to serve even our dev server over HTTPS, so let's set that up now!
Hint: This plugin will cause an Insecure warning to appear when visiting our site! This is nothing to worry about, and you may simple click "Proceed anyway" and safely ignore that warning.
Vite (the bundler that Astro uses under the hood!) has a hard time running @learncard/core out of the box due to some deep dependencies relying on the node.js standard library. Fortunately, it is fairly straightforward to polyfill! Let's add that now
Great! With all that boilerplate out of the way, we can now finally begin the real dev work!
Landing Page
Let's pop open a terminal and fire up the dev server!
pnpmdev
Then, open up a browser and go to https://localhost:3000 (take great care to make sure you're using https and not http!) You should see a screen like this:
Hint: If you get a warning about the site being insecure, that is okay! You may just click "proceed anyway" and continue your local development.
Let's remove all this default content and get a basic skeleton app for a simple wallet.
It's not much, but it's a start! Let's stop that loading text from lying to us and actually add in a wallet! For the purposes of this demo app, let's hardcode the seed '1234'. In a real app, you would never want to hardcode the seed used for the wallet, preferring instead to use a truly random source to generate a seed, then securely storing it, but for now, we will be fine simply hardcoding '1234'.
To begin, let's install @learncard/init, @learncard/types, and @learncard/chapi-plugin
This will update our UI to reveal that a wallet has been loaded! Great!
Once we've got CHAPI set up and we're able to store credentials in our wallet, we'll come back to this page and display the credentials we've stored, but for now, we can call it a day on this Landing Page! Phew! 😅
CHAPI
In order to set up CHAPI, we'll need to do a few things:
Install/run the web-credential-polyfill
Run the installHandler method
Host a public manifest.json file
Host a public wallet service worker
Host a storage endpoint for users to visit when storing a credential via CHAPI
Let's run through those now!
Install/run the web-credential-polyfill
This is implicitly done for us when calling initLearnCard, so we're already done here!
Run the installHandler method
Our first real step! To do this, simply call the installChapiHandler method on the wallet object!
This part is a bit tricky. What we need to do is decide on a route to use as a service worker, and then encode that inside of our manifest.json file. Let's use /wallet-worker:
This is where the really interesting bit happens! This service worker will actually be a full route that uses LearnCard to call some special methods. The actual content displayed on this route doesn't matter very much. However, we will need to make an important decision here: What should the user see when being asked to store a credential? In this case, we will redirect them to another route that we will create at /store.
src/pages/wallet-worker.astro
---import Layout from"@layouts/Layout.astro";---<Layouttitle="wallet-worker"> <h1>LearnCard CHAPI Example Wallet Worker</h1> <h3>You probably shouldn't see this page...</h3></Layout><script> import { initLearnCard } from "@learncard/init"; const learnCard = await initLearnCard(); learnCard.invoke.activateChapiHandler({ store: async () => {return { type:"redirect", url:`${window.location.origin}/store` }; }, });</script>
If you try to visit this page manually, there's not much to see, and you may notice that errors appear in your browser's console. However, when reaching this page via CHAPI, something very important happens:
This small snippet of code tells CHAPI that we'd like to display the /store page when a site requests to store a credential with our software. Let's make that /store page now!
Host a storage endpoint for users to visit when storing a credential via CHAPI
We've finally reached our first page that will have enough javascript to justify using React! Let's setup a basic astro page that renders a React component:
src/pages/store.astro
---import Layout from'@layouts/Layout.astro';import CredentialStorage from'@components/CredentialStorage';---<Layouttitle="Store a Credential"> <CredentialStorageclient:only="react" /></Layout>
Now, let's build out the CredentialStorage component! Let's start with a basic React component:
Now, let's figure out how to retrieve and display the requested credential!
Retrieving the Credential
In order to retrieve the credential, we can use the receiveChapiEvent method on a LearnCard wallet. This method is asynchronous, so we'll need to use state and an effect for this to work:
Because learnCard.invoke.receiveChapiEvent can technically be run on both a store and get page, we need to make TypeScript happy by checking that this is actually a store event. This is done by the if ("credential" in _event") check.
Displaying the Credential
In order to display the credential, we will use the VCCard component from @learncard/react. Before we can do that, however, we will need to first extract the credential from the raw event we received from CHAPI. To do this, we will create a helper that grabs a Verifiable Credential from a Verifiable Presentation:
The final step for this page is to allow the user to either add an id and store this credential, or to reject this credential without storing it. Let's add that now!
Phew! That was a lot of code! But now let's test it out! Head on over to https://playground.chapi.io/issuer and try issuing yourself a credential! You should ultimately land on a page like this:
Managing Credentials
Great! We've now set up a full CHAPI flow for storing credentials into a wallet! But what good is it to store credentials that you can't see? Let's head back to our Landing Page and add some Credential Management.
CredentialListItem
We will want to display our credentials in an unordered list, so let's start with what a given list item will look like! To make things simple, we'll just display its title and add a delete button to allow the user to remove the credential from their wallet.