A Better Web3 Experience: Account Abstraction From Flow (Part 1)

A Better Web3 Experience: Account Abstraction From Flow (Part 1)

Create a Walletless dApp Using the Flow Wallet API and Account Abstraction.

·

10 min read

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:

  1. Set up a dockerized Flow Wallet API application to use the Flow Testnet

  2. Test the Flow Wallet API application

Then, in part two:

  1. Create a new Next.js application

  2. Set Up Prisma for backend user management

  3. Build the Next.js application frontend functionality

  4. 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 and FLOW_WALLET_CHAIN_ID in the environment section.

  • Lines for redis and emulator in the depends_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.