If you’re a developer working on a web3 app, you know that onboarding mainstream users into web3 is difficult.
Even with the promise of truly owning your data, making near-free worldwide payments, and using a censorship-free system, the current process of creating and using a digital wallet is just too hard.
Fortunately, there’s a better way. Account abstraction promises to streamline the process, making user onboarding seamless and simple. Flow—a decentralized, public, layer-one blockchain designed for building web3 experiences for mainstream users—has used account abstraction to combine a hybrid custody model with walletless onboarding to create an onboarding that is easy, simple, and as straightforward as traditional apps.
Let's dive into the details to see how Hybrid Custody and Walletless Onboarding work together: the architecture, the code, and what we can expect moving forward.
Digital wallets bring all the benefits of web3, including the assurance that no one can control your assets. There are no middlemen such as banks taking a percentage of your money, or third parties dictating what you can do. However, because most wallets are “self custody” (meaning only you have access), you—and only you—are in charge of your keys. There is no customer support if you make a mistake.
This means you must store your keys safely. Don’t store them in clear text on your computer where they can be hacked. And don’t write them down where someone can read them. And don’t lose them, because there is no reset password in web3, so you’ll lose everything. You can see the issues.
In addition to the challenges of self custody, you also need to understand tokens and payments. How do you pay for your transactions? What tokens do you need? How do you move from fiat to tokens? How do you transfer the tokens once you have them? Oh, you made a typo when you sent your tokens? Sorry, they’re lost forever. Again, you can see how difficult this all is.
With all these obstacles, it’s easy to see why many people don’t make it past step one of signing up for your app.
Developers tried to solve this issue with custodial wallets. These wallets manage the keys for you and have many benefits.
Signing up is easy. You can use an email and password, as you would for a traditional service, and the wallet provider takes care of the rest. If you lose your login credentials, your keys are still accessible on the provider's servers – you can simply request a password reset. Since the wallet software runs on their servers, providers can even pay your transaction fees. You don’t have to understand tokens, funding, and transfers.
But as the saying in web3 goes: “Not your keys, not your coins!” While custodial wallets solved many onboarding issues, the wallet providers, in the end, owned the keys. We’re right back to the problems we wanted to get away from: a centralized entity that can prevent you from using your tokens, can close your account at a whim, or can shut down completely, taking your funds with them.
Wouldn’t it be nice if you could have the best of both worlds?
Account abstraction solves one of the fundamental issues of blockchain networks: the distinction between externally owned accounts and contract accounts.
An externally owned account (EOA) is an account controlled by the user with a private key. These are the traditional web3 wallets that we talked about above. An EOA has a public address and initiates transactions (such as sending tokens) with the blockchain.
A contract account (which is also just a smart contract) is a program deployed on-chain and executed by the network nodes. Contract accounts don’t initiate transactions. Instead they respond to a transaction with an action of their own. They have no private key—they are controlled by their code. This type of account could be said to have app-custody.
Account abstraction combines app-custody and self-custody into a new user experience (UX), giving users the benefits of both: control of their wallets, while abstracting away the wallet (and blockchain) altogether.
On Flow, account abstraction is the basis for Walletless Onboarding using Hybrid Custody.
Walletless Onboarding allows developers to create an onboarding experience for users that is nearly seamless.
The walletless user journey (from flow.com)
The hybrid custody model uses account abstraction to allow account delegation—where a child account delegates control to a parent account through a Capability, essentially allowing one account to control another.
Now, the self-custody account can be a parent account, giving permissions to the app account, which is now a child account. With this setup, the accounts work together, and your app can work like this:
With this hybrid custody model, users get to experience your app right away, with minimal barriers and no risk. And later on, if they choose, they can take control of the self-custody account themselves, enjoying all the real ownership and self-sovereignty it brings.
Account Creation
How does all this work?
On Ethereum and many other blockchain networks, an EOA is created from a public key. The key is hashed, and the first 160 bits of this hash become the address of the account controlled by that key. This makes account creation independent from the network but irrevocably ties an account to that key.
Flow uses a particular on-chain function to create account addresses. You can then add any number of keys to a Flow account, adding and removing access based on the custodian of each key. This means a transaction is required to create a new address—so you can’t create accounts offline—but it also makes Flow account ownership much more flexible.
You can see a full account creation example on GitHub, but let’s check out the crucial parts here:
userPrivateKey := examples.RandomPrivateKey()
userPublicKey := flow.NewAccountKey().
FromPrivateKey(userPrivateKey).
SetHashAlgo(crypto.SHA3_256).
SetWeight(flow.AccountKeyWeightThreshold)
serviceAccountAddress, serviceAccountKey, serviceSigner :=
examples.ServiceAccount(flowClient)
createAccountTx, _ := templates.CreateAccount(
[]*flow.AccountKey{userPublicKey},
nil,
serviceAccountAddress
)
createAccountTx.SetProposalKey(
serviceAccountAddress,
serviceAccountKey.Index,
serviceAccountKey.SequenceNumber
)
createAccountTx.SetReferenceBlockID(
examples.GetReferenceBlockId(flowClient)
)
createAccountTx.SetPayer(serviceAccountAddress)
_ = createAccountTx.SignEnvelope(
serviceAccountAddress,
serviceAccountKey.Index,
serviceSigner
)
_ = flowClient.SendTransaction(ctx, *createAccountTx)
First, the user creates a key pair. Pretty basic stuff.
Because on Flow, you create accounts by sending an account creation transaction, we need an existing account to send this transaction. This housekeeping is the job of the
ServiceAccount
. The
createAccountTx
receives the user’s public key to control the new account later, but the ServiceAccount
, which signs and pays for the transaction, handles the rest. In the case of your app, you would own this service account so you could sign account creation transactions on behalf of your user, also funding the account’s creation.Hybrid Custody
Let’s get back to the hybrid custody model. Your app can create app accounts for your users and link them to a public key that your app controls. The app can then handle your user's transactions and assets and even sponsor their transaction fees, just like any custodial account would do.
Later, when a user decides to dive deeper into the web3 ecosystem and get their own wallet, you can create and save a link to the app-custodied account’s AuthAccount Capability, and delegate control to the user’s wallet-managed account.
You can see an example of this in the walletless arcade example where a multisig transaction is executed by the app to link a user's account to a walletless onboarding, dapp-custodied account to the signing user’s wallet-managed account.
It’s worth driving the point home that delegating access on the app-created account is done via a linked Capability on the account which is then provided to the user. This mechanism for account delegation is completely new to blockchain accounts, and is only possible because of the account abstraction that’s been native to Flow for years now. This language API account linking feature is a huge advantage to accounts on Flow, in addition to many other features – m-of-n multisig, re-keying accounts, etc. – which are only now beginning to be realized on other chains with their nascent, non-native account abstraction implementations.
Account abstraction is a powerful feature. On Flow, it enables a hybrid custody model that allows users to enjoy the benefits of a custodial account when they sign up, while enabling them to take the account into self-custody at any time. It’s a new – and vastly improved – user experience that hopefully brings millions more users into web3.
Also published here.