Despite the advancement of dApps’ capabilities over the past year, adoption has been slowed by terrible user experience. Users are required to complete a complicated and onerous series of steps—download a wallet, learn about gas costs, obtain tokens to pay gas, save seed phrases, and more. This poses a significant hurdle for users new to the blockchain, or those who are just uncomfortable with holding crypto. Often, they just give up.
To solve this problem, walletless dApps on Flow have emerged. With this approach, users can easily sign up for dApps using credentials they are already comfortable with (social logins, email accounts). This allows them to get started quickly, without needing to understand the complexities of wallets or blockchain.
In this two-part series, we’ll explore how walletless dApps on Flow work. We'll look at some use cases and walk you through the steps of building and deploying your wallet-less web3 dApp using the Flow Wallet API and account abstraction.
Here in part one, we'll focus on building the backend for our walletless dApp. In part two, we'll wrap up the walkthrough by building the front end.
docker-compose.yml
FileFlow is a highly scalable blockchain with a design philosophy that prioritizes mainstream use: easy user logins, mobile-ready, fast development time, 99.99% up-time, and more. It’s made for dApps that have real-world usage
Account abstraction (AA) on Flow falls right into this philosophy. AA, in combination with hybrid custody, creates a walletless onboarding experience for users. What does this mean?
Typically, accounts on a blockchain are owned either by a user with a private key—called an externally owned account (EOA)—or by a smart contract—called a contract account.
Account abstraction combines these ideas. It allows the user to control the wallet while abstracting away the idea of the wallet altogether by letting the contract also have control. On Flow, this is called Hybrid Custody.
There are lots of advantages to this “account delegation.” Since one account (the child/EOA account) can delegate control to another account (the parent/app account), developers can provide users with a seamless onboarding and in-app experience while giving them a sense of actual ownership and self-sovereignty. This is particularly useful for new users who are not familiar with the intricacies of blockchain technology and can help increase adoption and engagement with the application.
For example, developers can build apps that create wallets for users, manage keys and transactions, and even abstract away the blockchain altogether. But in the end, the user still has control and ownership over any assets in the wallet.
Other use cases include:
All this together makes it much easier for users to get started with web3 dApps.
So let’s next walk through creating a walletless dApp. We’ll build a dApp that integrates Google social login/signup and creates a Flow wallet for the user on signup time.
Here is a quick breakdown of what we’re going to do in part one of this walkthrough:
Then, in part two:
Are you ready? Let’s go!
To make our lives easier, we’ll use the Flow Wallet API. This is a REST HTTP service that allows developers to quickly integrate wallet functionality into dApps. This API was developed in Golang, so knowledge of Go will help you, though proficiency is unnecessary to make this app work! The Flow Wallet API is currently not maintained. However, at the time of writing, the API was working perfectly.
Clone the project folder with the following command:
$ git clone https://github.com/flow-hydraulics/flow-wallet-api.git
To facilitate the development, we will use Docker. If you don't have it installed on your OS, you can find instructions for it at this link: https://docs.docker.com/engine/install/
From the terminal, navigate to the newly created flow-wallet-api
directory.
I recommend taking the time to review the folder structure. This API was very well done and is a complete code with unit tests included! To make this article as short as possible, we will focus on executing the code.
Note: If your computer is a Mac using an M1 chip, you will need to change the Dockerfile
file in the docker/wallet/
folder. If you are not using an M1, you can skip this section.
In Dockerfile
, edit the first line of code. We will change the Golang version used by the container to:
FROM golang:1.20rc1-alpine3.17 AS dependencies
You will also need to change the value of the GOARCH
field:
GOARCH=arm64
That's it! These are the required changes in Mac M1 operating systems.
Before we can spin up the Flow Wallet API, we need to make some configuration changes. The first step is to rename the .env.example
file to .env
. The application will use this file to import the environment variables.
Within the .env
file, we need to change some values.
Since we will be running the application over Flow’s Testnet network, we need to change the environment variables FLOW_WALLET_ACCESS_API_HOST
and FLOW_WALLET_CHAIN_ID
. In the .env
file, comment out the following lines:
# emulator
# FLOW_WALLET_ACCESS_API_HOST=localhost:3000
# FLOW_WALLET_CHAIN_ID=flow-emulator
Then, uncomment the following lines:
# testnet
FLOW_WALLET_ACCESS_API_HOST=access.testnet.nodes.onflow.org:9000
FLOW_WALLET_CHAIN_ID=flow-testnet
With this change, we’ve modified the configuration of the application to use the Testnet network.
To use the Testnet, we need to create a Testnet account and update the FLOW_WALLET_ADMIN_ADDRESS
and FLOW_WALLET_ADMIN_PRIVATE_KEY
fields inside the .env
file.
To create a Flow Testnet account, you must first install the Flow CLI on your operating system. You can find instructions on how to do this here.
With Flow CLI installed, run the command:
$ flow keys generate
This command will generate an asymmetric public-private key pair. Save the private key somewhere. Then, copy the public key—we will use it in the next step to create an account on the Flow blockchain!
Open the browser on the Flow faucet: https://testnet-faucet.onflow.org/
In the first input, paste in the public key you just generated. Leave the rest with the default settings, then perform the CAPTCHA verification and click the Create Account button.
This will generate a Testnet Address.
Copy the address and use it as a value for the FLOW_WALLET_ADMIN_ADDRESS
field in the .env
file. Also, update the FLOW_WALLET_ADMIN_PRIVATE_KEY
field with the private key that you generated in the previous step.
In my case, it would look like this:
FLOW_WALLET_ADMIN_ADDRESS=0x7cc7be2796e8cf29
FLOW_WALLET_ADMIN_PRIVATE_KEY=73bc408436c14befd74cb01fa4c54217c6c860ff97df1a77a3063a2807c7067f
We’re ready! Our Testnet account has been created. This is the admin account that will execute, sign, and pay for the creation of new wallets for our application.
Within an account, it is possible to have several proposal keys. As we will call several transactions with the same account to avoid concurrency problems, we will need to create new proposal keys for the admin account. You can read more about proposal keys here.
We’ll change the configuration in .env
to create 10 new proposal keys. For our tests, this is a sufficient amount. However, if you start having concurrency problems when executing transactions, you can change this value and restart the application.
FLOW_WALLET_ADMIN_PROPOSAL_KEY_COUNT=10
We’ll also add a property to the .env
file to disable idempotency middleware. Since we are just testing the application (and using it to facilitate our post requests), we will leave it disabled by setting the following value to true
. However, it is recommended to activate it in a production environment.
FLOW_WALLET_DISABLE_IDEMPOTENCY_MIDDLEWARE=true
docker-compose.yml
FileSince we made some changes to our .env
(including using the Testnet network and disabling idempotency middleware), We can remove a few lines of unnecessary code in the docker-compose.yml
file. Our application will not be using the redis
and emulator
containers. So, we can remove them.
After removing them, your docker-compose.yml
file will look like this:
version: "3.9"
networks:
private:
services:
db:
image: postgres:13-alpine
environment:
POSTGRES_DB: wallet
POSTGRES_USER: wallet
POSTGRES_PASSWORD: wallet
networks:
- private
ports:
- "5432:5432"
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready --username=${POSTGRES_USER:-wallet} --dbname=${POSTGRES_DB:-wallet}",
]
interval: 10s
timeout: 5s
retries: 10
api:
build:
context: .
dockerfile: ./docker/wallet/Dockerfile
target: dist
network: host # docker build sometimes has problems fetching from alpine's CDN
networks:
- private
ports:
- "3000:3000"
env_file:
- ./.env
environment:
FLOW_WALLET_DATABASE_DSN: postgresql://wallet:wallet@db:5432/wallet
FLOW_WALLET_DATABASE_TYPE: psql
depends_on:
db:
condition: service_healthy
Note that we also removed some lines from the api
service:
FLOW_WALLET_ACCESS_API_HOST
and FLOW_WALLET_CHAIN_ID
in the environment section.redis
and emulator
in the depends_on
section
We spin up our containers with the following command:
$ docker compose up
When we run the above command, Docker will spin up the following:
flow-wallet-api-db-1
: This is the PostgreSQL database container where all data will be stored, including the users’ private keys. Remember that we are only running the application in a test environment, using the Flow Testnet network. If you use this API in production (Mainnet), protect the users’ private keys. One option is to use key management system services. The Flow Wallet API has easy and fast integration with Google KMS and AWS KMS.flow-wallet-api-api-1
: This is an application in Golang that connects to the Flow network and performs actions such as creating wallets, transactions, scripts, and much more.Done! Our application runs through these two Docker containers, We can make calls using (the default) port 3000
.
All endpoints available in the application can be found in the documentation here.
The endpoint to check if the application is healthy is /v1/health/
ready.
We send a curl request to check this endpoint, and this is what we receive as a response:
$ curl -X GET -i http://localhost:3000/v1/health/ready
HTTP/1.1 200 OK
Vary: Accept-Encoding
Date: Fri, 05 May 2023 17:27:05 GMT
Content-Length: 0
With the 200
response, we are assured that our application is up and running.
Creating a new wallet via the API is very easy! We send a POST request to the /v1/accounts
endpoint.
$ curl -X POST http://localhost:3000/v1/accounts
{
"jobId":"2876f90d-d9ec-4007-935b-4aba3cb8e45e",
"type":"account_create",
"state":"INIT",
"error":"",
"errors":null,
"result":"",
"transactionId":"",
"createdAt":"2023-05-05T17:29:34.800299551Z",
"updatedAt":"2023-05-05T17:29:34.800299551Z"
}
Under the hood, the API creates an asymmetric key pair and executes a transaction on the Flow blockchain to create an account.
Since a transaction takes a few seconds and can fail, the API creates jobs.
These jobs are records/units that are stored in the database. Each time a transaction is called or queued, a job is created. The status of the transaction is stored in this job.
Notice how the transaction state
returned by our call above is INIT
. The API is monitoring the account creation transaction.
To get the updated state of this job, we can send a GET request to the /v1/jobs/{JOB_ID}
endpoint.
$ curl -X GET \
http://localhost:3000/v1/jobs/2876f90d-d9ec-4007-935b-4aba3cb8e45e
{
"jobId":"2876f90d-d9ec-4007-935b-4aba3cb8e45e",
"type":"account_create",
"state":"COMPLETE",
"error":"",
"errors":null,
"result":"0xb23d449bc23d9d04",
"transactionId":
"ab68527578c323b68caf9d1b1533ebf4a3486f22b0d6f7df4339c57e48d9c4ca",
"createdAt":"2023-05-05T17:29:34.800299Z",
"updatedAt":"2023-05-05T17:29:47.507803Z"
}
We see the result, with a newly created address. It is also possible to check all addresses created by the application using the /v1/accounts
endpoint.
$ curl -X GET http://localhost:3000/v1/accounts
[
{
"address":"0xb23d449bc23d9d04",
"keys":null,
"type":"custodial",
"createdAt":"2023-05-05T17:29:47.504906Z",
"updatedAt":"2023-05-05T17:29:47.504906Z"
},
{
"address":"0x7cc7be2796e8cf29",
"keys":null,
"type":"custodial",
"createdAt":"2023-05-05T17:25:51.847239Z",
"updatedAt":"2023-05-05T17:25:51.847239Z"
}
]
With our wallet API working, we can start implementing the application that will send requests to the wallet API to create new accounts. We'll cover this in part two of our walkthrough.
Also published here.