Alchemy's open-source Account Abstraction SDK makes it easy for developers to build with ERC-4337 compliant smart accounts.
Under Development
The aa-sdk is still actively being developed so certain features might not be fully implemented yet or are subject to change. See the Github repository here: https://github.com/alchemyplatform/aa-sdk
What is the AA-SDK?
The Account Abstraction SDK can be used to simplify the interaction with account abstraction primitives (user operations and bundlers). It's a complete solution that implements an EIP-1193 provider, handling gas estimation, signing the tx, and fetching paymaster data (if you use one) with one method call. If you're using eth_sendTransaction
, the SDK converts that into a user operation for you and sends it along.
The AA SDK supports bringing your own Account Contracts or using any of the currently available accounts, and it is built on top of viem to enable a lighter-weight bundle. The SDK is published with ESM default exports though, and also supports CJS.
Getting Started
via yarn
:
yarn add @alchemy/aa-core
via npm
:
npm i -s @alchemy/aa-core
If you are using ethers
and want to use an ethers
compatible Provider
and Signer
you can also add the the @alchemy/aa-ethers
library.
via yarn
:
yarn add @alchemy/aa-ethers
via npm
:
npm i -s @alchemy/aa-ethers
Example Usage to Interact with Simple Accounts
via aa-core
aa-core
import {
SimpleSmartContractAccount,
SmartAccountProvider,
type SimpleSmartAccountOwner,
} from "@alchemy/aa-core";
import { mnemonicToAccount } from "viem/accounts";
import { polygonMumbai } from "viem/chains";
import { toHex } from "viem";
const SIMPLE_ACCOUNT_FACTORY_ADDRESS =
"0x9406Cc6185a346906296840746125a0E44976454";
// 1. define the EOA owner of the Smart Account
// This is just one exapmle of how to interact with EOAs, feel free to use any other interface
const ownerAccount = mnemonicToAccount(MNEMONIC);
// All that is important for defining an owner is that it provides a `signMessage` and `getAddress` function
const owner: SimpleSmartAccountOwner = {
// this should sign a message according to ERC-191
signMessage: async (msg) =>
ownerAccount.signMessage({
message: toHex(msg),
}),
getAddress: async () => ownerAccount.address,
};
// 2. initialize the provider and connect it to the account
const provider = new SmartAccountProvider(
// the demo key below is public and rate-limited, it's better to create a new one
// you can get started with a free account @ https://www.alchemy.com/
"https://polygon-mumbai.g.alchemy.com/v2/demo", // rpcUrl
"0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", // entryPointAddress
polygonMumbai // chain
).connect(
(rpcClient) =>
new SimpleSmartContractAccount({
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
chain: polygonMumbai,
factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS,
rpcClient,
// optionally if you already know the account's address
accountAddress: "0x000...000",
})
);
// 3. send a UserOperation
const { hash } = provider.sendUserOperation(
"0xTargetAddress",
"0xcallData",
0n // value: bigint or undefined
);
via aa-ethers
aa-ethers
import {
alchemyPaymasterAndDataMiddleware,
getChain,
SimpleSmartContractAccount,
} from "@alchemy/aa-core";
import { Alchemy, Network } from "alchemy-sdk";
import { Wallet } from "@ethersproject/wallet";
import {
EthersProviderAdapter,
convertWalletToAccountSigner,
} from "@alchemy/aa-ethers";
const SIMPLE_ACCOUNT_FACTORY_ADDRESS =
"0x9406Cc6185a346906296840746125a0E44976454";
// 1. connect to an RPC Provider and a Wallet
const alchemy = new Alchemy({
apiKey: API_KEY,
network: Network.MATIC_MUMBAI,
});
const alchemyProvider = await alchemy.config.getProvider();
const owner = Wallet.fromMnemonic(MNEMONIC);
// 2. Create the SimpleAccount signer
// signer is an ethers.js Signer
const signer = EthersProviderAdapter.fromEthersProvider(
alchemyProvider,
ENTRYPOINT_ADDRESS
).connectToAccount(
(rpcClient) =>
new SimpleSmartContractAccount({
entryPointAddress: ENTRYPOINT_ADDRESS,
chain: getChain(alchemyProvider.network.chainId),
owner: convertWalletToAccountSigner(owner),
factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS,
rpcClient,
})
);
// 3. send a user op
const { hash } = signer.sendUserOperation(
"0xTargetAddress",
"0xcallData",
0n // value: bigint or undefined
);
Components
Core Components
The primary interfaces are the SmartAccountProvider
and BaseSmartContractAccount
.
The SmartAccountProvider
is an ERC-1193 compliant Provider that wraps JSON RPC methods and some Wallet Methods (signing, sendTransaction, etc). It also provides two utility methods for sending UserOperations:
sendUserOperation
-- this takes intarget
,callData
, and an optionalvalue
which then constructs a UserOperation (UO), sends it, and returns thehash
of the UO. It handles estimating gas, fetching fee data, (optionally) requesting paymasterAndData, and lastly signing. This is done via a middleware stack that runs in a specific order. The middleware order isgetDummyPaymasterData
=>estimateGas
=>getFeeData
=>getPaymasterAndData
. The paymaster fields are set to0x
by default. They can be changed usingprovider.withPaymasterMiddleware
.sendTransaction
-- this takes in a traditional Transaction Request object which then gets converted into a UO. Currently, the only data being used from the Transaction Request object isto
,callData
andvalue
. Support for other fields is coming soon.
If you want to add support for your own SmartAccounts
then you will need to provide an implementation of BaseSmartContractAccount
. You can see an example of this in SimpleSmartContractAccount. You will need to implement 4 methods:
getDummySignature
-- this method should return a signature that will notrevert
during validation. It does not have to pass validation, just not cause the contract to revert. This is required for gas estimation so that the gas estimate are accurate.encodeExecute
-- this method should return the abi encoded function data for a call to your contract'sexecute
methodsignMessage
-- this should return an ERC-191 compliant message and is used to sign UO HashesgetAccountInitCode
-- this should return the init code that will be used to create an account if one does not exist. Usually this is the concatenation of the account's factory address and the abi encoded function data of the account factory'screateAccount
method.
Contributing
- clone the repo
- run
yarn
- make changes to packages
To run tests:
TODO: currently tests require a specific mnemonic to pass and they run against the alchemy bundler which is in private beta.