Skip to main content


Smart Accounts, much like EOAs, depend on signatures for validating transactions and messages. What sets smart accounts apart is their ability to utilize various signature schemes, such as multisig and passkeys.

Traditional Signers

Externally Owner Account (EOAs) utilize both a private and public key for security. Similarly, Smart Accounts can employ the same approach for securing funds. This can be achieved through various means such as a locally generated private key, utilizing a hosted MPC solution, or securing it with existing user wallets like MetaMask.

Email / Social Login

Developers have the option to integrate third-party "signer services" into their smart accounts. As a result, users must authenticate through these services to access their smart accounts. These services offer the advantage of traditional web2 onboarding experiences, enabling user authentication through familiar mediums such as email magic link, social platforms, or SMS.

AbstractionKit is flexible to accepts any third party MPC service. To signup an account owner and to sign a userOperation, you can follow the same flow as the guide on EOA wallets.

Ethers/Viem Signers

Ethereum Libraries support the concept of EOAs by wrapping a private key and supporting high-level methods to sign common types of interaction.

Setup Account Owner

import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit";
import { Wallet } from "ethers";

const ownerPrivateKey = process.env.PRIVATE_KEY as string;

const signer = new Wallet(ownerPrivateKey);
const smartAccount = SafeAccount.initializeNewAccount([signer.address]);

Sign UserOperation

Format the signature to a userOperation signature using formatEip712SignaturesToUseroperationSignature.

let userOperation = ... // Use createUserOperation() to help you construct the userOp below

const domain = {
chainId: process.env.CHAIN_ID,
verifyingContract: smartAccount.safe4337ModuleAddress,

const types = SafeAccount.EIP712_SAFE_OPERATION_TYPE;

// formate according to EIP712 Safe Operation Type
const { sender, ...userOp } = userOperation;
const safeUserOperation = {
safe: userOperation.sender,
validUntil: BigInt(0),
validAfter: BigInt(0),
entryPoint: smartAccount.entrypointAddress,

const signature = await signer.signTypedData(domain, types, safeUserOperation);
const formatedSig = SafeAccount.formatEip712SignaturesToUseroperationSignature([signer.address], [signature]);
userOperation.signature = formatedSig;

EOA Wallets

MetaMask, Rainbow, and all others, offer a JavaScript Ethereum Provider API, ensuring consistent interaction across any application.

Setup Account Owner

import { SafeAccountV0_2_0 as SafeAccount } from "abstractionkit";
import { BrowserProvider } from "ethers"

const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const signerAddress = await signer.getAddress();

const smartAccount = SafeAccount.initializeNewAccount([signerAddress]);

Sign UserOperation

Follow the same example for ethers/viem sign useroperation.

Direct Access to Private key

When there is direct access to the private key locally, you can pass the key directly in signUserOperation.

Signup Account Owner

import { SafeAccountV0_2_0 as SafeAccount } from 'abstractionkit'

const ownerPublicAddress = process.env.PUBLIC_KEY;

const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]);

Sign UserOperation

const chainId = BigInt(process.env.CHAIN_ID as string);
const ownerPrivateKey = process.env.PRIVATE_KEY as string;

const userOperation = ... // returned from createUserOperation()

userOperation.signature = smartAccount.signUserOperation(


Visit dedicated guide on Passkeys


Visit dedicated guide on MultiSig