A Better Web3 Experience: Account Abstraction From Flow (Part 1)
Create a Walletless dApp Using the Flow Wallet API and Account Abstraction.
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. For users new to the blockchain, or those who are just uncomfortable with holding crypto, this poses a significant hurdle. 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 alFlow 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. In combination with hybrid custody, AA 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. AA 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.
Use Cases of Account Abstraction
There are many 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 dApps that create wallets for users, manage keys and transactions, and even abstract away the blockchain altogether. However, in the end, the user still has control and ownership over any assets in the wallet.
Other use cases include:
Key recovery with multisig transactions
An account may be set up to require multiple signatures in order to complete a transaction. This creates a wide variety of new use cases and enhances the usability of web3 apps.
Gasless experiences with sponsored transactions
The stress that comes with paying fees—before newbies can use a web3 app and execute a transaction—can be a barrier. Sponsored transactions give room for developers to subsidize these charges on behalf of users.
Seamless experiences with bundled transactions
Flow’s Cadence programming language, which introduces new features to smart contracts, also separates contracts from transactions. It supports bundling transactions from several contracts into a single transaction at the protocol level.
Social logins with walletless onboarding + Hybrid Custody
Flow enables its developers to deliver a familiar experience while progressively exposing new users to the benefits of web3. Users can realize the benefits of both custodial and non-custodial experiences, which is what Hybrid Custody entails.
All this together makes it much easier for users to get started with web3 dApps.
Building a Sample Walletless Login dApp
Let’s create 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:
Set up a dockerized Flow Wallet API application to use the Flow Testnet
Test the Flow Wallet API application
Then, in part two:
Create a new Next.js application
Set Up Prisma for backend user management
Build the Next.js application frontend functionality
Test our Next.js application
Are you ready? Let’s go!
Set up Flow Wallet API application
To make our life 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. Therefore, knowledge of Go will help you, though proficiency is not necessary to make this dApp work!
Clone the project folder with the following command:
$ git clone https://github.com/flow-hydraulics/flow-wallet-api.git
We will use Docker to facilitate development. 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.
Take the time to review the folder structure. Everything is organized like a traditional API where the routes are divided into separate folders. The majority of what we need will be in the accounts
folder, but of course explore other directories and endpoints. Perhaps the greatest overview of the structure is in the main.go
file starting at line 167 where all the API routes are laid out. The API has a complete code base with unit tests included! To make this article as short as possible, we’ll focus on executing the code.
MacOS M1 chip config
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.
A small note: if you are running MacOS Ventura then you might encounter “error: unrecognized command-line option '-m64.” If that is the case try adding the following line at the top of the same Dockerfile as demonstrated by this GitHub issue.
FROM --platform=linux/amd64 golang:1.17-alpine AS builder
Configure Environment Variables
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.
Flow network fields
Since we will run 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.
Flow admin account fields
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!
Create a Flow Testnet account
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 looks 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.
Proposal key field
Within an account, it's 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
Idempotency middleware field
We’ll also add a property to the .env
file in order to disable idempotency middleware. Since we are just testing the application (and using it to facilitate our post requests), we’ll leave it disabled by setting the following value to true
. However, it’s recommended to activate it in a production environment.
FLOW_WALLET_DISABLE_IDEMPOTENCY_MIDDLEWARE=true
Update the docker-compose.yml
file
Since we made some changes to our .env
(including using the Testnet network and disabling idempotency middleware), we can remove several 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
andFLOW_WALLET_CHAIN_ID
in theenvironment
section.Lines for
redis
andemulator
in thedepends_on
section
We spin up our containers with the following command:
$ docker compose up
Test the Flow Wallet API Application
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), be sure to 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 is running through these two Docker containers. Now we can make calls using (the default) port 3000
.
All endpoints available in the application can be found in the documentation here.
Check the Application's Health
Use the endpoint /v1/health/ready
to check if the application is healthy.
We send a curl request to check this endpoint, and we receive this 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
The 200
response means our application is up and running.
Create a Wallet
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.
Jobs
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.
Get Job Data and Account Addresses
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. I would highly recommend running the transactionId
through the Flow Diver Testnet explorer, as it gives a lot of information that can be used in further development work. It’s 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.