Skip to main content

Authentication Methods in Smart Wallets

Smart Accounts, much like regular accounts (EOAs), depend on signatures for validating transactions and messages. What sets Smart Accounts apart is their ability to utilize various signature schemes, such as Passkeys and Multisig.

Passkeys

Candide supports on-chain Passkeys, allowing users to sign transactions directly from their devices. Using their default phone/computer auth system, such as PIN, biometrics, or FaceID, signatures are authenticated on-chain through smart contracts. All without reliance on centralized infrastructure.

See a demo on passkeys.candide.dev, and learn more on the Passkeys doc page.

Multisig

Candide supports on-chain multisig (multi-signature) accounts, which enhance security by requiring multiple approvals for transactions. This feature is ideal for implementing two-factor authentication (2FA) and is well-suited for wallets targeting companies or DAOs, where transactions often require multiple key approvals.

Visit the dedicated guide on Multisig.

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.

Social / Email

Developers can integrate third-party "Signer services" into their smart accounts, providing the benefits of traditional Web2 onboarding experiences. These services enable user authentication through familiar methods such as email magic links, social logins, or SMS.

  • Candide's AbstractionKit is highly adaptable, supporting any third-party Signer service. The process for assigning the smart account owner and signing a User Operation is consistent across all services. For details on how to integrate as third-parties as main signer, refer to the guide for EOA wallets.

  • However, it is important to note that using third-party services as the primary signer of the account makes the wallet you are offering custodial. Candide recommends using Passkeys as the main signer, while offering third-parties as recovery options via the on-chain Guardian Recovery. This approach ensures progressive self-custody, allowing users to modify recovery options as needed.

Below are some third-party recovery options that you can consider. Candide does not endorse any of these options; the guides are provided for educational purposes only.

ServiceKey Management MethodAuthentication MethodsPlug-n-play Front End?Guide
DynamicMPC (Turnkey under the hood)Email, Social, WalletsYes
FireblocksMPCCustomNo
Lit ProtocolMPCEmail, Social, PhoneNoAdd a Google Account as a Recovery Method using Lit
Magic.linkAWS KMSEmail, SocialYesAdd an Social Account as a Recovery Method using Magic
PrivyMPCEmail, Social, WalletsYes
TurnkeyAWS KMSCustomNoUse Turnkey with AbstractionKit
Web3AuthMPC, key shardingSocial, EmailYes

EOA Wallets

You can allow users to use MetaMask, or any other EOA wallet as the signer to the Smart Account. EOAs exposes a JavaScript Ethereum Provider API.

Setup Account Owner

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

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

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

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 = EIP712_SAFE_OPERATION_V7_TYPE;

// formate according to EIP712 Safe Operation Type
const { sender, ...userOp } = userOperation;
const safeUserOperation = {
...userOp,
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;

Private key Storage

Traditional wallets encrypts the private key in local storage. In this setup, the owner of the smart account is represented by it. Use signUserOperation.

Signup Account Owner

import { SafeAccountV0_3_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(
userOperation,
[privateKey],
chainId,
)