npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

abstractionkit

v0.3.7

Published

Account Abstraction 4337 SDK by Candidelabs

Readme

A Typescript Library to easily build standard Ethereum Smart Wallets, with first class support for Safe Accounts.

AbstractionKit is agnostic of:

  • Ethereum interface libraries: ethers, web3.js, viem/wagmi
  • Bundlers: Plug and play a Bundler URL from any provider, or self-host your own
  • Paymasters: Candide Paymaster is supported, but you can use any 3rd party paymaster to sponsor gas
  • Accounts: The Safe Account is first class supported, but you can use Bundlers and Paymasters with any account

Examples

Abstractionkit Example Projects

Features

  • Safe Accounts with passkey authentication, social recovery, multisig, and allowance management
  • EIP-7702 support via Calibur7702Account and Simple7702Account
  • Gas abstraction with sponsored UserOperations and ERC-20 gas payment via CandidePaymaster
  • Multichain signatures via SafeMultiChainSigAccountV1 (sign once, replay across chains)
  • Bundler client compatible with standard ERC-4337 methods
  • EntryPoint v0.6, v0.7, v0.8, and v0.9 support with a version-safe account/UserOp mapping

Docs

For full detailed documentation visit our docs page.

Installation

Requires Node.js 18 or later.

npm install abstractionkit

Upgrading to v0.3.0

v0.3.0 is a major release. The following API changes are likely to break existing paymaster code:

  • CandidePaymaster.createSponsorPaymasterUserOperation(...) now takes smartAccount as the first argument: (smartAccount, userOp, bundlerRpc, sponsorshipPolicyId?, context?, overrides?).
  • CandidePaymaster.createTokenPaymasterUserOperation(...) adds a dedicated context? argument before overrides?: (smartAccount, userOp, tokenAddress, bundlerRpc, context?, overrides?). Callers that previously passed overrides positionally at argument 5 must insert undefined (or an explicit context) so overrides shifts to argument 6.

See CHANGELOG.md for the full list of new features, renames, type export changes, and fixes.

Quickstart

Which account class to use?

| Class | EntryPoint | Account Type | When to use | |---|---|---|---| | SafeAccountV0_3_0 | EP v0.7 | Safe (counterfactual) | Recommended for most new projects | | SafeAccountV1_5_0_M_0_3_0 | EP v0.7 | Safe v1.5.0 (counterfactual) | Safe v1.5.0 with EIP-7951 / Daimo P256 verifier for WebAuthn | | SafeAccountV0_2_0 | EP v0.6 | Safe (counterfactual) | Legacy support for EntryPoint v0.6 | | SafeMultiChainSigAccountV1 | EP v0.9 | Safe multichain | Sign once, replay across chains. | | Calibur7702Account | EP v0.8 | EIP-7702 (Uniswap Calibur) | Upgrade an EOA in place. Supports EOA, P256, and WebAuthn keys | | Simple7702Account | EP v0.8 | EIP-7702 (minimal) | Minimal reference EIP-7702 account | | Simple7702AccountV09 | EP v0.9 | EIP-7702 (minimal, parallel paymaster) | EntryPoint v0.9 with parallel paymaster signing |

Endpoints

Candide hosts both bundler and paymaster under the same base URL. Get an API key from the dashboard, or use the public endpoint (rate-limited, no key required).

// Authenticated
const rpc = "https://api.candide.dev/api/v3/11155111/YOUR_API_KEY";

// Or public (no key required)
// const rpc = "https://api.candide.dev/public/v3/11155111";

Recipes

Copy paste patterns for common tasks. Examples use SafeAccountV0_3_0 (EntryPoint v0.7). For EntryPoint v0.6, replace with SafeAccountV0_2_0.

Send ETH from a new Safe account

import { SafeAccountV0_3_0 } from "abstractionkit";

const ownerPublicAddress = "0xOwner";
const ownerPrivateKey = "0xPrivateKey";
const nodeRpc = "https://rpc.example.com";
const bundlerRpc = "https://api.candide.dev/api/v3/11155111/YOUR_API_KEY";
const chainId = 11155111n; // Sepolia

// Initialize new account (deploys on first UserOp)
const smartAccount = SafeAccountV0_3_0.initializeNewAccount([ownerPublicAddress]);
console.log("Account address:", smartAccount.accountAddress);
// Fund this address with ETH before sending the first UserOp

// Create UserOp
const userOp = await smartAccount.createUserOperation(
  [{ to: "0xRecipient", value: 1000000000000000n, data: "0x" }], // 0.001 ETH
  nodeRpc,
  bundlerRpc,
);

// Sign
userOp.signature = smartAccount.signUserOperation(userOp, [ownerPrivateKey], chainId);

// Send and wait for receipt
const response = await smartAccount.sendUserOperation(userOp, bundlerRpc);
const receipt = await response.included();
console.log("Tx hash:", receipt?.receipt.transactionHash);

Send an ERC-20 token transfer

import { SafeAccountV0_3_0, createCallData, getFunctionSelector } from "abstractionkit";

// Encode ERC-20 transfer(address,uint256)
const transferSelector = getFunctionSelector("transfer(address,uint256)");
const transferCallData = createCallData(
  transferSelector,
  ["address", "uint256"],
  ["0xRecipient", 1000000n], // amount in token's smallest unit
);

const userOp = await smartAccount.createUserOperation(
  [{ to: "0xTokenContractAddress", value: 0n, data: transferCallData }],
  nodeRpc,
  bundlerRpc,
);

Sponsor gas with CandidePaymaster

import { SafeAccountV0_3_0, CandidePaymaster } from "abstractionkit";

const paymaster = new CandidePaymaster("https://api.candide.dev/api/v3/11155111/YOUR_API_KEY");

// Create the UserOp first (without paymaster)
const userOp = await smartAccount.createUserOperation(
  [{ to: "0xRecipient", value: 1000000000000000n, data: "0x" }],
  nodeRpc,
  bundlerRpc,
);

// Sponsor it. Sets paymaster fields and re-estimates gas.
// Note: as of v0.3.0, smartAccount is the first argument.
const { userOperation: sponsoredOp, sponsorMetadata } = await paymaster.createSponsorPaymasterUserOperation(
  smartAccount,
  userOp,
  bundlerRpc,
  sponsorshipPolicyId,
  // context (optional — e.g. { signingPhase: "commit" } for EP v0.9 parallel signing)
  // overrides (optional — gas limits and multipliers)
);

// Sign and send as usual
sponsoredOp.signature = smartAccount.signUserOperation(sponsoredOp, [ownerPrivateKey], chainId);
const response = await smartAccount.sendUserOperation(sponsoredOp, bundlerRpc);

Pay gas with ERC-20 tokens

import { SafeAccountV0_3_0, CandidePaymaster } from "abstractionkit";

const paymaster = new CandidePaymaster("https://api.candide.dev/api/v3/11155111/YOUR_API_KEY");
const gasTokenAddress = "0xERC20TokenAddress"; // must be supported by paymaster

const userOp = await smartAccount.createUserOperation(
  [{ to: "0xRecipient", value: 0n, data: "0x" }],
  nodeRpc,
  bundlerRpc,
);

// Automatically prepends token approval + sets paymaster fields.
// For tokens like USDT that require resetting allowance to 0 first, pass
// { resetApproval: true } in the overrides.
// `tokenQuote` carries the exchange rate and max token cost used for the approval.
const { userOperation: tokenOp, tokenQuote } = await paymaster.createTokenPaymasterUserOperation(
  smartAccount,
  userOp,
  gasTokenAddress,
  bundlerRpc,
  // context (optional)
  // overrides (optional — gas limits, multipliers, resetApproval)
);

tokenOp.signature = smartAccount.signUserOperation(tokenOp, [ownerPrivateKey], chainId);
const response = await smartAccount.sendUserOperation(tokenOp, bundlerRpc);

Pass paymaster context (sponsorship policy, parallel signing)

CandidePaymasterContext is passed as its own argument, separate from gas overrides.

const { userOperation: sponsoredOp } = await paymaster.createSponsorPaymasterUserOperation(
  smartAccount,
  userOp,
  bundlerRpc,
  sponsorshipPolicyId,
  {
    // For EntryPoint v0.9 parallel signing flows:
    // signingPhase: "commit" | "finalize",
  },
  {
    // gas overrides:
    callGasLimitPercentageMultiplier: 110,
  },
);

Batch multiple transactions

import { SafeAccountV0_3_0, MetaTransaction } from "abstractionkit";

// Pass an array of MetaTransactions. Automatically encoded via MultiSend.
const transactions: MetaTransaction[] = [
  { to: "0xRecipientA", value: 1000000000000000n, data: "0x" },
  { to: "0xRecipientB", value: 2000000000000000n, data: "0x" },
  { to: "0xTokenContract", value: 0n, data: transferCallData },
];

const userOp = await smartAccount.createUserOperation(
  transactions, // automatically batched via MultiSend when length > 1
  nodeRpc,
  bundlerRpc,
);

Connect to an existing (deployed) account

import { SafeAccountV0_3_0 } from "abstractionkit";

// Use the constructor for an already-deployed account
const smartAccount = new SafeAccountV0_3_0("0xYourDeployedSafeAddress");

// vs. initializeNewAccount which sets factory data for first-time deployment
const newAccount = SafeAccountV0_3_0.initializeNewAccount(["0xOwnerAddress"]);
// newAccount.accountAddress is the counterfactual address
// First UserOp will deploy it automatically

When you have a stored Safe address and need to pick between the two paths at runtime, use isDeployed to avoid emitting redundant factory data (and the AA10 sender already constructed error) on subsequent UserOps:

import { SafeAccountV0_3_0 } from "abstractionkit";

const smartAccount = (await SafeAccountV0_3_0.isDeployed(safeAddress, nodeRpc))
  ? new SafeAccountV0_3_0(safeAddress)
  : SafeAccountV0_3_0.initializeNewAccount(owners);

isDeployed only checks for bytecode at the address. It does not verify that the deployed code is a Safe or that its on-chain owners match a given set. If your owners may have changed, compare createAccountAddress(owners) against the stored address yourself before calling initializeNewAccount.

Calibur 7702: delegate an EOA and send a transfer

Calibur7702Account is Uniswap's EIP-7702 smart account. It upgrades a regular EOA in place so the same address becomes a programmable smart account on EntryPoint v0.8.

import {
  Calibur7702Account,
  createAndSignEip7702DelegationAuthorization,
} from "abstractionkit";

const eoaAddress = "0xYourEOA";
const privateKey = "0xYourPrivateKey";
const nodeRpc = "https://rpc.example.com";
const bundlerRpc = "https://api.candide.dev/api/v3/11155111/YOUR_API_KEY";
const chainId = 11155111n;

// The EOA address becomes the smart account address after delegation.
const account = new Calibur7702Account(eoaAddress);

// Create UserOp with EIP-7702 delegation (only required the first time).
const userOp = await account.createUserOperation(
  [{ to: "0xRecipient", value: 1000000000000000n, data: "0x" }],
  nodeRpc,
  bundlerRpc,
  { eip7702Auth: { chainId } },
);

// Sign the delegation authorization.
userOp.eip7702Auth = createAndSignEip7702DelegationAuthorization(
  BigInt(userOp.eip7702Auth.chainId),
  userOp.eip7702Auth.address,
  BigInt(userOp.eip7702Auth.nonce),
  privateKey,
);

// Sign and send.
userOp.signature = account.signUserOperation(userOp, privateKey, chainId);
const response = await account.sendUserOperation(userOp, bundlerRpc);
const receipt = await response.included();

After the first UserOp deploys the delegation, subsequent UserOps no longer need eip7702Auth. Use getDelegatedAddress(eoaAddress, nodeRpc) to check delegation status.

Calibur 7702: register a WebAuthn passkey

import { Calibur7702Account } from "abstractionkit";

// Build a P256 key from the WebAuthn public key coordinates.
const webAuthnKey = Calibur7702Account.createWebAuthnP256Key(pubKeyX, pubKeyY);
const keyHash = Calibur7702Account.getKeyHash(webAuthnKey);

// Register with a 1-year expiration.
const registerTxs = Calibur7702Account.createRegisterKeyMetaTransactions(
  webAuthnKey,
  { expiration: Math.floor(Date.now() / 1000) + 86400 * 365 },
);

const userOp = await account.createUserOperation(registerTxs, nodeRpc, bundlerRpc);
userOp.signature = account.signUserOperation(userOp, privateKey, chainId);
const response = await account.sendUserOperation(userOp, bundlerRpc);

Calibur 7702: sign a UserOp with a registered passkey

import { Calibur7702Account, createUserOperationHash } from "abstractionkit";

// Use a WebAuthn dummy signature for accurate gas estimation.
const dummySig = Calibur7702Account.createDummyWebAuthnSignature(keyHash);

const userOp = await account.createUserOperation(
  [{ to: "0xRecipient", value: 0n, data: "0x" }],
  nodeRpc,
  bundlerRpc,
  { dummySignature: dummySig },
);

// Compute the hash, sign with the passkey off-chain, then format the signature.
const userOpHash = createUserOperationHash(userOp, entryPointAddress, chainId);
userOp.signature = account.formatWebAuthnSignature(keyHash, {
  authenticatorData,
  clientDataJSON,
  challengeIndex,
  typeIndex,
  r,
  s, // P256 signature components
});

const response = await account.sendUserOperation(userOp, bundlerRpc);

Common error codes and solutions

| Error Code | Meaning | Fix | |---|---|---| | AA10 | Sender already constructed (initCode not needed) | Use new SafeAccountV0_3_0(address) instead of initializeNewAccount for deployed accounts | | AA21 | Didn't pay prefund | Fund the sender address with enough ETH to cover gas, or use a paymaster | | AA25 | Nonce mismatch | Don't override nonce, or fetch latest via fetchAccountNonce() | | AA40 | Paymaster deposit too low | Contact paymaster provider or use a different paymaster | | AA41 | Paymaster postOp reverted | Check paymaster-specific requirements (token balance, approval amount) |

Guides

| Title | Description | | -----------------------------------------------------------------------------------------| -------------------------------------------------------------------------------- | | Send your first user operation | Learn how to create a smart wallet and to send your first user operation | | Send a Gasless Transaction | Learn how to send gasless transactions using a paymaster | | Pay Gas in ERC-20 | Learn how to offer the ability for users to pay gas in ERC-20s using a Paymaster |

AI Agent Integration

If you use Claude Code, you can import this README into your project's CLAUDE.md for better AI assistance:

@node_modules/abstractionkit/README.md

npm package

npm

License

MIT

Acknowledgments

  • EIP-4337: Account Abstraction via Entry Point Contract specification
  • Safe Accounts, Modules, and SGP
  • Uniswap Calibur Account