# Installing an ERC-6900 Plugin into Alchemy Smart Wallets In our [last post](https://hackmd.io/@locker/r1PzaK6Wkl), we explored how to create a plugin that enhances your smart account with round-up savings functionality. However, you might have noticed that installing such a plugin into a live smart account is more involved than it seems. ERC-6900 plugins don’t operate in isolation—they must be paired with a compatible ERC-4337 smart account, such as those offered by [Alchemy](https://accountkit.alchemy.com/) and [Circle](https://developers.circle.com/w3s/programmable-wallets). In this post, we’ll guide you through the process of installing your custom plugin into an Alchemy smart account. For those who want to jump straight to the code, you can find it [here](https://github.com/locker-labs/erc6900-plugin-starter). ## Account Abstraction with Alchemy Alchemy’s modular account contracts and companion SDK together enable you to integrate ERC-6900 plugins into ERC-4337 smart accounts without heavy lifting. Strictly speaking, you can interact with the modular account contracts on their own, but the SDK greatly simplifies tasks like installing, initializing, and managing your custom plugins. This tutorial focuses on **v1** of Alchemy’s modular account. A newer **v2** is already functional and undergoing audits, but not all features are fully integrated. ### Installing Official vs. Custom Plugins Alchemy’s SDK already includes out-of-the-box support for official plugins such as [MultiOwner](https://github.com/alchemyplatform/modular-account/blob/v1.0.x/src/plugins/owner/MultiOwnerPlugin.sol#L147) and [Session](https://github.com/alchemyplatform/modular-account/blob/1ceb7935b3d8642283f0dc8fd0a7a7f00132be9d/src/plugins/session/SessionKeyPlugin.sol). Installing these can be done with just a few commands. However, you can also build your own custom plugin. This tutorial demonstrates how to install a sample CounterPlugin inspired by Alchemy’s [example repo](https://github.com/alchemyplatform/modular-account-plugin). **Next Steps:** We’ll walk through the full process of writing, deploying, and installing the CounterPlugin into your Alchemy modular account. ## Project Layout The project is organized as follows: - `contracts`: On-chain code for the plugin. - `src`: Node.js scripts for interacting with the plugin, including installation, uninstallation, and increment functionality. **Important Tip:** When using Alchemy's modular-account kit, avoid direct imports from `account-abstraction`. Instead, import from `modular-account/lib/account-abstraction` to prevent dependency issues. Additionally, make sure to uninstall any existing plugins with the same ABI before installing new ones. ## Building the Counter Plugin ### CounterPlugin Solidity Code The CounterPlugin is a simple plugin that maintains a count per smart account. Here's the core contract code: ```solidity contract CounterPlugin is BasePlugin { mapping(address => uint256) public count; function increment() external { count[msg.sender]++; } } ``` To deploy the plugin run `forge script script/CounterPlugin.s.sol:CounterPluginScript --rpc-url RPC_URL --private-key PRIVATE_KEY --broadcast`. The pluginManifest() function defines the necessary functions, such as increment. It ensures that the plugin doesn't have dependencies on other plugins like the [MultiOwner plugin](https://github.com/alchemyplatform/modular-account/tree/v1.0.x/src/plugins/owner). ```solidity function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; manifest.dependencyInterfaceIds = new bytes4[](0); manifest.executionFunctions = new bytes4[](1); Use cases: manifest.executionFunctions[0] = this.increment.selector; } ``` ### Generate JS from ABI To install the plugin, we use [@account-kit/plugingen](https://github.com/alchemyplatform/aa-sdk/tree/v4.x.x/account-kit/plugingen) to generate code from the ABI. Here’s the configuration you’ll need for the Counter Plugin: ```ts import { CHAIN, PLUGIN_ADDRESS } from "@/constants"; import { CounterPluginAbi } from "@abi/CounterPluginAbi"; import type { PluginConfig } from "@account-kit/plugingen"; export const CounterPluginGenConfig: PluginConfig = { name: "CounterPlugin", abi: CounterPluginAbi, addresses: { [CHAIN.id]: PLUGIN_ADDRESS, }, installConfig: { initAbiParams: [], dependencies: [], }, }; ``` Then run `npx plugingen generate` to generate the plugin code based on the configuration. ## Interact with plugin ### Setup smart account client To interact with the plugin, you first need to instantiate a client for the smart account that it will be installed into. ```ts import { createModularAccountAlchemyClient } from "@account-kit/smart-contracts"; import { baseSepolia } from "@account-kit/infra"; // A standard Alchemy smart account const modularAccount = await createModularAccountAlchemyClient({ signer: LocalAccountSigner.privateKeyToAccountSigner(`0x${PRIV_KEY}`), chain: baseSepolia, transport: alchemy({ apiKey: process.env.ALCHEMY_API_KEY as string }), }) // The account is extended with actions your plugin enables const extendedAccount = modularAccount.extend(counterPluginActions); ``` ### Install plugin into smart account With an instance of the `extendedAccount`, we can install plugin if it hasn't already been installed. ```ts console.log("Installing the counter plugin..."); if (await isCounterPluginInstalled(extendedAccount)) { console.log("Counter plugin already installed."); } else { const res = await extendedAccount.installCounterPlugin({ args: [] }); console.log("Counter Plugin installed:", res.hash); } ``` ### Increment the counter After all that is complete, the payoff is that we should be able to increase the counter for our installed plugin. First, check that the count starts at 0. ```zsh cast call PLUGIN_ADDRESS "count(address)" SMART_ACCOUNT_ADDRESS --rpc-url RPC_URL --private-key PRIVATE_KEY > 0x0000000000000000000000000000000000000000000000000000000000000000 ``` ```ts const res = await extendedAccount.increment({ args: [], }); ``` After invoking increment, the count will now be 1. ```zsh cast call PLUGIN_ADDRESS "count(address)" SMART_ACCOUNT_ADDRESS --rpc-url RPC_URL --private-key PRIVATE_KEY > 0x0000000000000000000000000000000000000000000000000000000000000001 ``` ## What's next One challenge we encountered was getting the `CounterPlugin#userOpValidationFunction` to work correctly. A big shoutout to Howy at Alchemy for helping resolve this. The function needs to align with the `validateUserOpHash` function from the 4337 specification. ![Screenshot 2025-02-20 at 22.05.31](https://hackmd.io/_uploads/r1dcj_Sqyg.png) The solution is: ```solidity function userOpValidationFunction( uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash ) external override(BasePlugin) returns (uint256) { // Ignore the input parameters since we're always returning true (functionId, userOp, userOpHash); uint48 validAfter = 0; uint48 validUntil = type(uint48).max; uint160 authorizer = 0; return (uint256(validAfter) << 208) | (uint256(validUntil) << 160) | authorizer; } ``` Although this works, it introduces some security risks. This plugin validates all requests and anybody can invoke `increment` on the smart account that installed the plugin. This would empty the smart account of all it's ETH on gas costs. In the next post, where we’ll dive deeper into signature validation within the ERC-6900 framework and other practical topics.