Sunday, April 28, 2024
No menu items!
HomeDatabase ManagementBuild Account Abstraction Wallets with Alchemy and AWS: Part 2

Build Account Abstraction Wallets with Alchemy and AWS: Part 2

In Part 1 of this series, we introduced the Account Abstraction (AA) concept and explained how it overcomes some of the user experience challenges of Externally Owned Account (EOA) wallets. We also covered how you can apply AA in the context of different use cases, such as marketing campaigns and enabling user onboarding at scale.

In this post, we introduce the technical core concepts of AA. We then walk you through a solution built on AWS using the Alchemy Account Kit SDK and other services provided by Alchemy. Finally, we deep dive into the components of Account Kit used for the solution, namely the AA-SDK, Bundler, and the Gas Manager API.

How Account Abstraction works

Ethereum Improvement Proposal (EIP) 4337 explains how Account Abstraction can be achieved on Ethereum without requiring any updates to the Ethereum protocol. The primary difference between AA wallets and EOAs is that every AA wallet is a smart contract. From Vitalik Buterin’s post, The road to account abstraction, this “[…] allows us to use smart contract logic to specify not just the effects of the transaction, but also the fee payment and validation logic. This allows many important security benefits, such as multi-signature and smart recovery wallets, being able to change keys without changing wallets, and quantum-safety.”

As pointed out earlier, AA on Ethereum does not require any changes to the Ethereum protocol. This has two benefits. First, no potentially destabilizing updates are required at the protocol level, removing any impact to other parts of the protocol. Second, AA development can proceed independently of protocol development. The following diagram depicts the general transaction flow for a transaction submitted from an AA wallet. This type of transaction is referred to as a UserOperation. Refer to Part 1 for additional information on the traditional EOA wallet transaction flow.

A UserOperation gets processed by the network through the following steps:

Users authorize transactions using their smart contract wallets by signing these with their private key. This creates a UserOperation.
The UserOperation is sent to a bundler, which is an off-chain entity that can process batches of UserOperations.
A bundler simulates these UserOperations, and evaluates if the signature submitted in the UserOperation corresponds to the address in the associated smart contract wallet.
After validating a batch of UserOperations, the bundler batches up one to several UserOperations into a single bundled transaction, which then gets submitted to the Ethereum blockchain and, ultimately, gets included in an Ethereum block.

For additional information and a technical deep dive on AA wallets, refer to Understanding Account Abstraction.

Solution overview

The following architecture diagram illustrates the solution we will be walking through.

A deployable AWS Cloud Development Kit (AWS CDK) repository of the AWS components is available in the public web3-workshop GitHub repository. The architecture covered in this post consists of Steps 1 and 4 of the first module of the Build Web3 workshop. For instructions on deploying the solution, refer to the README or follow the instructions in the Build Web3 workshop.

An Account Abstraction solution built on AWS consists of the following building blocks (as numbered in the preceding diagram):

Amazon API Gateway, a fully managed service to create, publish, maintain, monitor, and secure APIs at scale. API Gateway is used in this solution to expose all required functionality as a well-defined REST interface, validating JSON Web Token (JWT) tokens against the Amazon Cognito user pool, and translating authenticated calls to Lambda function invokes internally.
Amazon Cognito, a fully managed identity and access management service for web and mobile applications. With Amazon Cognito, developers can choose the authentication mechanism they want to use, for example, an email and password, or a social login. Upon successful login, users receive a JWT token containing various metadata about the user, including their AA wallet address. The AA wallet address is available even before the user ever interacts with it. This is because AA wallet addresses are deterministically calculated.
AWS Step Functions, a serverless workflow orchestration service. A Step Functions-based express workflow is the main serverless orchestration service used in this solution. It is triggered during the JWT generation after a user has successfully logged in via Amazon Cognito. The Step Functions workflow checks if the user already has a key associated via a lookup on the associated Amazon DynamoDB table. If no key exists yet, the workflow invokes the Key Management AWS Lambda function to generate a new key.
Key Management, UserOperation Signing, and Blockchain Lambda functions:

The Key Management function is responsible for interacting with AWS KMS to obtain new private keys generated in a secure fashion by calling generate-data-key-pair-without-plaintext. The key is then stored in the associated DynamoDB table.
The UserOperation Signer function is responsible for creating signatures on the submitted UserOperations. To do so, the function has permission to decrypt certain private keys via AWS KMS. The private keys are then used to create a signature on the submitted hash of the UserOperation.
The Blockchain Handler function uses the Alchemy AA-SDK. It assembles the UserOperation, gets the hash signed via the UserOperation Signer Lambda, and interacts with Alchemy’s Account Abstraction APIs via the Alchemy AA-SDK.

AWS Key Management Service (AWS KMS), a fully managed key management service. With AWS KMS, it is straightforward to create and control cryptographic keys that are used by your application. In this solution, when a user logs in for the first time, a KMS data key pair is created for them using the secp256k1 curve. This key pair is encrypted and can only be decrypted via the associated key residing on AWS KMS.
Amazon DynamoDB, a fully managed, serverless, key-value NoSQL database designed to run high-performance applications at any scale. In this solution, DynamoDB is used to store users’ encrypted private keys of their data key pair. During user login, DynamoDB is checked to verify if a unique user ID (also known as a sub) already exists. If it does, the existing key material is returned. If the sub doesn’t exist, a new key is created in AWS KMS, and the returned encrypted private key is stored in DynamoDB along with the user ID.
An Amazon Managed Blockchain Ethereum node, which is a fully managed RPC node. AA wallets require access to the blockchain—for example, to get the deterministically calculated wallet address, calculated via the eth_call API, or to get the current network gas fees using eth_gasPrice. A Managed Blockchain Goerli Ethereum node is used in this solution for blockchain access, handling JSON Remote Procedure Call (RPC) requests to Ethereum. For additional information on how to deploy a Managed Blockchain Ethereum RPC node, refer to How to deploy an Ethereum node on AWS.
Alchemy Account Kit, a framework to embed smart accounts in your Web3 apps, unlocking powerful features like gas sponsorship, batched transactions, and more. The Alchemy AA-SDK makes it straightforward to integrate and deploy smart accounts, send user operations, and sponsor gas with just a few lines of code.

Let’s dive into the components of Account Kit used in our solution.

AA-SDK

The AA-SDK is a type-safe and performant TypeScript library built on top of viem to provide methods for sending user operations, sponsoring gas, and deploying smart accounts. It handles all the complexity of ERC-4337 to make Account Abstraction simple.

You can interact with Alchemy’s Bundler and Gas Manager through the AA-SDK. Let’s explore how to use the AA-SDK by walking through all of the steps required to configure the SDK and submit a UserOperation.

Create a provider

The first step in using the AA-SDK is setting up a provider. This provider acts as the gateway to interact with the blockchain, sending UserOperations (UOs) and managing smart accounts. The following JavaScript snippet illustrates how to instantiate a provider:

import { AlchemyProvider } from “@alchemy/aa-alchemy”;
import { goerli } from “viem/chains”;

// Initialize the provider with your Alchemy API Key and chain
const provider = new AlchemyProvider({
apiKey: “<YOUR_ALCHEMY_API_KEY>”, // Replace with your Alchemy API Key
chain: goerli // Replace with the chain you intend to send requests on
});

Replace the placeholder with your actual Alchemy API key, obtainable from the Alchemy dashboard.

Integrate a smart account implementation

The next step is to connect the provider with a smart account implementation. This defines the interface for your smart account (AA wallet) that you will be controlling through the provider. LightAccount is Alchemy’s gas-optimized smart account implementation, and is used in the following example.

Before running the example code, you need a private key, which you use to sign transactions for the AA wallet. If you need a private key for testing, you can create one via openssl:

# THIS KEY GENERATION EXAMPLE IS FOR DEMO PURPOSES ONLY.
#Do not re-use this private key in any production workloads.

# Generate the private key and store in a file privKey.pem
openssl ecparam -name secp256k1 -genkey -noout -out privKey.pem
# Extract the public key and store in a file pubKey.pem
openssl ec -in privKey.pem -pubout -out pubKey.pem

The following sample code shows how to connect LightAccount to a provider:

import {
LightSmartContractAccount,
getDefaultLightAccountFactoryAddress,
} from “@alchemy/aa-accounts”;
import { AlchemyProvider } from “@alchemy/aa-alchemy”;
import { LocalAccountSigner, type SmartAccountSigner } from “@alchemy/aa-core”;
import { goerli } from “viem/chains”;

const provider = new AlchemyProvider({
apiKey: “<ALCHEMY_API_KEY>”, // replace with your Alchemy API Key
chain: goerli, // replace with the chain you intend to send requests on
});

const chain = goerli;
const PRIVATE_KEY = “<0xYourEOAPrivateKey>”; // Your EOA’s private key
const eoaSigner: SmartAccountSigner =
LocalAccountSigner.privateKeyToAccountSigner(`0x${PRIVATE_KEY}`);

// Now that we have created our signer, we connect it to our provider and create a LightAccount
const connectedProvider = provider.connect(
(rpcClient) =>
new LightSmartContractAccount({
chain,
owner: eoaSigner,
factoryAddress: getDefaultLightAccountFactoryAddress(chain),
rpcClient,
})
);

Send UserOperations

At this point, you are ready to send UOs. UOs are akin to transactions sent from your smart account (AA wallet). The following code illustrates how to send a simple UO:

const userOperation = {
target: “0xTargetAddress”, // Replace with the target contract address
data: “0xCallData”, // The call data for the operation
value: 0n // Value in wei to send with the operation
};

// Send the UserOperation and get the operation hash
const { hash: userOpHash } = await connectedProvider.sendUserOperation(userOperation);
console.log(`UserOperation Hash: ${userOpHash}`);

The data field in the UO allows you to define your intended action. It can be empty for simple value transfers or detailed for contract calls. For further guidance on constructing call data, see Construct the call data.

Upon running the preceding code, UOs are sent to the Alchemy Bundler for processing and a UserOperationHash is logged, allowing you to track your UO’s status on platforms like Jiffyscan.

Batch UserOperations

Batching multiple UOs into a single UserOperation is an effective strategy to reduce gas costs and facilitate complex operations. For instance, in a Web3 game, players can run several on-chain actions with a single UO without having to approve UOs for each action, enhancing user experience. The following code illustrates how to batch UOs:

const { hash } = await connectedProvider.sendUserOperation([
{
target: “0x…”,
data: “0xcallDataTransacation1”,
},
{
target: “0x…”,
data: “0xcallDataTransacation2”,
},
]);

These UOs are run sequentially as they appear in the array. However, not every smart account implementation supports batching. Both LightAccount and SimpleAccount, as used in our examples, do support this feature.

To learn more about batching UOs, see Batching using sendUserOperation.

Gas Manager

A paymaster is an on-chain contract that allows an entity to sponsor the gas fees for another entity. It can be used by decentralized apps (dApps) to abstract away the concept of gas from their users. This significantly enhances the UX of dApps and can help onboard the next wave of Web3 users.

Gas Manager is Alchemy’s implementation of a paymaster. You can use Gas Manager to sponsor UOs through AA-SDK through the following steps:

To access Gas Manager, you’ll need an Alchemy account. Sign up and navigate to the Gas Manager page.

Create a new gas policy. A gas policy is a set of rules that define which UOs are eligible for gas sponsorship. You can control which operations are eligible for sponsorship by defining rules:

Spending rules – Limit the amount of money or the number of UOs that can be sponsored by a policy.
Allowlist – Restrict wallet addresses that are eligible for sponsorship. The policy will only sponsor gas for UOs that were sent by addresses on this list.
Blocklist – Ban certain addresses from receiving sponsorship under this policy.
Policy duration – Define the duration of your policy and the sponsorship expiry period. This is the period for which the Gas Manager signature (paymaster data) will remain valid after it’s generated.

Make sure that the policy is linked to the specific Alchemy app whose API key you are using for provider creation. A Gas Manager policy can only be associated with one Alchemy app, and it will only accept requests sent through the API key of the associated app.

To learn more about policy configuration, refer to How to Create a Gas Manager Policy.

Next, you must link your gas policy to your provider in AA-SDK. This makes sure any eligible UOs sent through your provider receive gas sponsorship.

Find your policy ID located at the top of the policy page on the Gas Manager dashboard.

Link it with the AA-SDK provider that’s already connected to a smart account implementation:

const GAS_MANAGER_POLICY_ID = “<YourGasManagerPolicyId>”;

// Link the provider with the Gas Manager. This ensures UOs
// sent with this provider get sponsorship from the Gas Manager.
connectedProvider.withAlchemyGasManager({
policyId: GAS_MANAGER_POLICY_ID,
});

Now you’re ready to send sponsored UOs, which means Gas Manager will cover the cost of any eligible UO sent using your provider:

// Since provider is already linked to gas policy
// any eligible UOs sent using this provider will receive gas sponsorship
const { hash } = await connectedProvider.sendUserOperation({
target: “0xTargetAddress”,
data: “0xCallData”,
value: 0n, // value in bigint or leave undefined
});

Bundler

Bundler is a network participant in the ERC-4337 standard that collects and submits UserOperations (UOs) to the blockchain, handling the associated gas fees, in exchange for payment during UO processing, either directly from the user’s AA wallet or from a paymaster.

Alchemy’s implementation of a Bundler is called Rundler. It is written in Rust and designed to achieve high performance and reliability. When you send a UO through AA-SDK, it is received by Rundler and submitted on-chain after passing certain checks.

The following are Rundler’s functionality and features:

ERC-4337 specification compliance – Rundler is designed to fully comply with the ERC-4337 specification, ensuring compatibility with the latest protocols and on-chain components.
Modular architecture – Its modular design allows various components to run either as an integrated binary or a distributed system. This flexibility is key in adapting to different deployment needs.
Separate mempool – Rundler maintains its own mempool, a collection of pending user operations awaiting inclusion in a bundle. This ensures better control and prioritization of UOs.
Mempool management – Rundler’s mempool validates and simulates UOs as per the ERC-4337 specification. It maintains these operations in memory until they are mined on-chain, handling chain reorgs effectively through a cache of mined user operations.
Chain support and extendibility – Rundler is built to support various EVM-compatible chains, with an architecture that can be extended and adapted to specific network requirements.
Event listening – Rundler listens for various blockchain events, including new block confirmations and changes in gas prices. This responsiveness ensures that it remains synchronized with the latest state of the blockchain and adjusts its operations accordingly.

To learn more about the inner workings of Rundler and inspect its source code, check out the Rundler GitHub repository.

Conclusion

In this post, we introduced how Account Abstraction works, and we walked through the AA wallet reference architecture that is used in the AWS Build Web3 workshop. We closed the post with a high-level introduction to the Alchemy Account Kit framework.

You can deploy the provided AA wallet CDK repository into your AWS account, configure your Alchemy Account Kit framework, and build your next Web3 app using AA smart wallets!

About the Authors

Emile Baizel is a Senior Blockchain Architect at AWS. He has been working with blockchain technology since 2018, when he participated in his first Ethereum hackathon. He didn’t win, but he got hooked. He specializes in blockchain node infrastructure, digital custody and wallets, and smart contract security.

David-Paul Dornseifer is a Blockchain Development Architect at AWS. He focuses on helping customers design, develop, and scale end-to-end blockchain solutions. His primary focus is on digital asset custody and key management solutions.

Read MoreAWS Database Blog

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments