One of Web3’s biggest issues is getting around the centralization of app stores and their stringent terms on the commission requirements for digital asset payments and transfers.
Progressive Web Apps (PWAs) in my opinion are the best solution as it allows you to install dApps directly on to a mobile device without using the app store intermediary.
This code was inspired by Friend.Tech and the UX flow which goes like this:
- Visit website and receives custom instructions on how to install app
- User adds to home screen or installs app then opens using the icon
- A new wallet address is generated and the user is prompted to fund it
- The balance is checked until it receives funds and is ready to go
Demo (use a mobile): https://jamesbachini.com/misc/pwa/
The code is of course open-sourced on Github:
https://github.com/jamesbachini/Web3-PWA-Boilerplate
Wallet Management
The Web3 PWA Boilerplate utilizes Ethers.js v6 in combination with a custom JSON RPC provider to offer a seamless experience. This design means there is no requirement for the connection of an external wallet, effectively simplifying the process for users that aren’t crypto native and offering a more streamlined solution.
Once a user’s wallet is generated it can be backed up by exporting the private key, restored using an existing private key or replaced with a new private key.
Silent Transactions
The users is prompted to deposit funds to their wallet address which in this case is testnet Goerli ETH.
Once the wallet is funded, the system allows transactions to be signed using ethers and subsequently broadcasted using a JSON RPC provider. This supports a rich user experience by facilitating easy transaction management within the framework without opening up external wallets to sign transactions every time the dApp needs to write on-chain data.
File Structure
The Web3 PWA Boilerplate is written in plain HTML, JS and CSS. You will likely want to import it into a framework like React but the barebones structure is in three main files.
- The index.html welcomes users with custom instructions for installation. It checks the browser user agent and displays instructions for Android or iPhone/iPad on how to install.
- The app.html provides the core entry page for the dApp. Once installed on the users device or added to home screen this will open up when the user taps the app icon.
- The manifest.json file outlines the default options and icons for the progressive web application.
There are no service workers in this PWA because we need to be online to fetch and send data to decentralized networks.
Security
To achieve a non-technical user experience this Web3 PWA makes a compromise on security by storing keys within the app. Things like injected XSS attacks become very dangerous in this scenario and developers should be careful about sanitizing user inputs and on-chain data.
While the current structure stores the private key for the temporary wallet within localStorage, this could present potential security concerns and it’s worth exploring more secure local storage alternatives to further enhance this aspect of the dApp.
Code Walkthrough
index.html
In the header we link to the manifest file
<link rel="manifest" href="./manifest.json">
Then the only other interesting thing is the user agent check which we do in Javscript at the bottom of the file.
const ua = navigator.userAgent.toLowerCase();
if (ua.includes('iphone') || ua.includes('ipad')) {
We can search online for the latest user agents that we want to support and if you want to add more custom options for obscure browsers you can add them as if else statements.
App.html
We first import ethers.js v6 and then generate a new private key if one doesn’t already exist. If the user has used the app before they’ll already have a private key and it is loaded.
// load existing wallet
tmpWallet = new ethers.Wallet(localStorage.privateKey);
// create new wallet
tmpWallet = ethers.Wallet.createRandom();
From there we have some basic functions to export, restore and create new keys which are fairly straightforward. Then we have a function which checks the wallet balance on a loop. We use a custom JSON RPC provider which you can find at chainlist.
const provider = new ethers.JsonRpcProvider('https://rpc.ankr.com/eth_goerli');
const balanceInWei = await provider.getBalance(tmpWallet.address);
Note that in production you would probably want to use a private RPC provider to get around rate limits on public nodes.
manifest.json
The manifest.json file sets out the app icons, splash screens and behavior.
- short_name This is a short version of the name of your web app. If the name doesn’t fit on the user’s display screen, the system will use the short_name instead.
- name This attribute indicates the full name of your web app.
- icons This is an array that provides images that represent the web app in various contexts (e.g., when it is installed to the home screen of a device).
- theme_color This parameter specifies the default theme color for your web app. This color is sometimes used by the mobile device’s task switcher and by the browser’s address bar (chrome).
- background_color This parameter denotes the expected background color of the web app. It is used by the browser when the application is launched.
- display This attribute describes the preferred display mode for the website or web application. The ‘standalone‘ value suggests that the web app is expected to look and behave like a standalone app.
- orientation This property sets the default orientation for all the website’s top level browsing contexts. ‘portrait’ value suggests the application is best viewed in a portrait mode.
- scope This attribute defines the set of URLs that the browser can visit within the app
- start_url This is set to app.html to load this page when the dApp first starts
I hope this boilerplate is useful and serves as a starting point for how we can create these progressive web apps in web3. Also if no one has coined it already you heard about “progressive web dapps” here first.