- Kirby provides the inspiration (and name) for a library we are developing at Civil, which allows publishers to easily add web3 functionality to their sites.
- As we set out to build this framework, we soon discovered that a core challenge would lay in the ability to support many different types of wallets. Similar to Kirby, our framework provides a wrapper around core web3 functionality and enhances their abilities. Wallets are the first implementation, and soon we will provide abstractions for Layer 2 payments and self-sovereign identity.
- At its core, a wallet is simply a private key that is able to sign transactions. A user interface is then built on top to allow the user to see recent transactions, sign/reject transactions, and other tools to interact with their account. While the humble wallet has brought us this far, I believe there is an opportunity to drastically enhance dapp user experience by combining the best features from different types of wallets.
In general, there are 3 main types of wallets:
The key idea of an “injected web3 provider” is that there would be either a browser extension or wrapping browser that provides a web3 instance to the application. All the application needs to do is access “window.web3” in the code without knowing/caring about what the actual implementation is (MetaMask, Gnosis Safe, mobile wallet, etc).
The challenge with injected web3 providers is that they are prefaced on the fact that a useralready has a wallet installed. Otherwise, this presents a fairly significant user-experience challenge:
Install this browser extension / software even before I have fully conveyed my value prop. 😬
IFrame / SDK based
This is the main challenge that have given rise to the iframe-based wallets. They allow a user to get started without having to install any software. Portis has done an incredible job making it super easy to get started on a web3 application — just enter your email and password and you have a wallet. The user does not need to install any extensions or software.
Fortmatic uses a similar approach, and I have no doubt we’ll continue to see more examples of these wallets. However, the main challenge is that the application needs to add support for the wallet since it likely is loaded by an SDK (devkit). This serves as a barrier to entry for wallet makers since they need to convince apps to build in support.
A “burner wallet” is the simplest of them all. Generate a random private key, save it in local storage, and boom — you’re onboarded. Users can generate a burner wallet key for every app that they use. While this is the easiest to use of them all, the application has unfettered access to the key and if the user clears their cache everything on the wallet is gone forever.
This all boils down to the same issue, and why we need wall-apps
There are many different types of wallets that serve many different users’ needs and preferences. Application developers need to add support for all of these different wallets, or force their users to conform to supported ones. Trade-offs need to be made between ease-of-use, user preference, and broadest adoption. While MetaMask might be the safest, it requires a user to install an extension. Portis might be the easiest to get started, but what if my user already has MetaMask? The new hot wallet on the block might be the best wallet in existence, but will it gain enough traction to make it worth support in my application?
Kirby allows us to create “wall-apps” — interfaces that introduce programmable logic in between the applications and the wallet. This serves as an abstraction layer so that any application can support any wallet, and users are free to choose which wallet they use and customize behavior from site to site. Having a layer between the application and the underlying wallet is extremely powerful. Since we are no longer tied to a single wallet implementation, we can get creative. A wall-app might generate a unique burner wallet for each site, and then use AZTEC to transfer value from your main wallet to your burner in order to enhance user privacy. A wall-app might auto-sign transactions for sites the user trusts. A wall-app can show a consolidated transaction history for all of the user’s wallets. I can’t wait to find out what developers build with this toolkit.
How it works
The communication between parent and child is facilitated through the use of a plugin architecture. Generally, a plugin will have both a Parent and Child component, using common message types to communicate across the IFrame. For those familiar with Redux, a plugin has a reducer to maintain state, and middleware that receives all Kirby events and can intercept and manipulate if necessary. Application developers are able to interact with the plugin’s exposed methods and access state using Redux selectors.
The result is that applications and wallets are now decoupled. Wall-apps exist in a demilitarized zone where the parent application in unaware of the logic within the wall-app, and wallets are unaware that they exist within a wall-app. Developers can add functionality to a wall-app to extend the core behavior and tailor to certain use cases. Users are free to choose those that best suit their needs.
Ethereum Plugin Walkthrough
Let’s take a look at the Ethereum Provider plugin to see how it works in detail. On startup, the Ethereum plugin creates a Provider that treats the iframe postMessage as an RPC server. It will then create a new web3 instance and expose that to the application. From the application’s standpoint, it is identical to any other web3 provider. Transactions are sent in exactly the same way. When an RPC request is generated, the Provider will then call postMessage to the child iframe, with the type WEB3_REQUEST, a requestID, and a payload with the RPC request.
Kirby on the child app is listening for events from the parent. Every message from the parent is dispatched, and WEB3_REQUEST will be picked up via the ChildEthereumPlugin middleware. On startup, the ChildEthereumPlugin will initialize a “concrete” web3 provider (MetaMask, Portis, a burner, etc). When it receives a web3 request from the parent it will send it directly to the concrete web3 provider. The response will then be sent back over postMessage to the Parent.
The reverse then happens on the parent side — it listens for events from the child and then dispatches it out to be picked up by the middleware. When it sees a response for the provided requestID the promise is then resolved.
All the application sees is a web3 provider, and can call methods just as if it was an injected provider. However, under the hood, the RPC calls are being routed through iframe demilitarized zone and to a concrete web3 provider.
While this was a simple example, child apps can be infinitely flexible. In a subsequent post, we will take a look at how we can use the Signature Interceptor Plugin combined with Burner Wallets to allow users to accept or reject signing data using a Burner Wallet.
Run with Kirby yourself
This was just a quick look at how the development of Kirby is progressing. While not quite ready for production, we are making great strides and would love to get more folks to kick the tires to add Kirby to their app or to make their own wall-app. Our current efforts are focused on getting the Ethereum functionality 100% working (including 1-click login). From there we will be working on Verified Claims issuance and storage. I imagine the next thing after that would be Layer 2 / Connext integration.
The API is moving quickly and might change, but don’t let that stop you from digging in. I would love to hear ideas for plugins and use cases.
If you are interested in adding Kirby to your app, you can copy the code from the demo app:
If you would like to build your own wall-app:
If you would like to build a plugin this is a good place to start:
Also a quick note to those not using React or Ethereum: the core was designed to be view-framework and blockchain agnostic. I imagine it would be very easy to build a plugin for Algorand or any other blockchain or build a view layer in Angular.