Skip to main content

Calibur Account

Calibur7702Account upgrades an EOA to a smart account via EIP-7702 delegation to the Calibur singleton. It provides batched transactions, passkey authentication, multi-key management with per-key hooks, and full ERC-4337 support through EntryPoint v0.8.

When to Use Calibur Account​

  • Multi-key support: Register passkeys, session keys, or secondary signers alongside the EOA root key.
  • Per-key hooks: Attach custom validation logic to individual keys (e.g., spending limits, time locks).
  • Key expiration and admin separation: Set expiration timestamps and distinguish admin keys from non-admin keys.
  • WebAuthn/passkey authentication: Sign transactions with biometrics through the WebAuthn standard.
  • Production-grade contracts: Built by Uniswap, audited by OpenZeppelin and Cantina.

Supported ERCs: ERC-4337 (Account Abstraction), EIP-7702 (EOA delegation).

Supported key types:

TypeDescription
Secp256k1Standard Ethereum EOA key (keyType = 2). Used for EOA private keys and secondary signers.
WebAuthnP256P-256 key signed through the browser's WebAuthn API (keyType = 1). Used for passkeys, Face ID, fingerprint, and security keys. The signature includes WebAuthn metadata (authenticatorData, clientDataJSON) that the contract parses before verifying the underlying P-256 signature.
P256Raw P-256 / secp256r1 key (keyType = 0). Same elliptic curve as WebAuthnP256, but with a raw (r, s) signature format. Used when you have direct access to a P-256 key (e.g., from a secure enclave or HSM) without the WebAuthn wrapper.

Smart Contracts and Audits​

The contracts were developed by Uniswap and audited by OpenZeppelin and Cantina.

How to Use​

Prerequisites​

Before using Calibur7702Account, you must have:

  • Node.js: Version 18.0 or higher.
  • EIP-7702 Compatible Network: Ethereum mainnet, Sepolia, or other EIP-7702 enabled chains.
  • Private Key Access: Required for signing authorizations and user operations.

Installation​

npm install abstractionkit

The contracts are audited by OpenZeppelin and Cantina.

Usage​

import { Calibur7702Account } from "abstractionkit";

const eoaAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31"; // EOA public address
const smartAccount = new Calibur7702Account(eoaAddress);

Constructor defaults:

  • entrypointAddress: 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108 (EntryPoint v0.8)
  • delegateeAddress: 0x000000009B1D0aF20D8C6d0A44e162d11F9b8f00 (Uniswap v1.0.0)

Both can be overridden by passing an overrides object as the second constructor argument.

const smartAccount = new Calibur7702Account(eoaAddress, {
entrypointAddress: "0x...", // optional
delegateeAddress: "0x...", // optional
});

Essential Methods​

createUserOperation​

Creates a UserOperation for EIP-7702 accounts that can be sent to bundlers for execution. Handles nonce fetching, gas estimation, and optional EIP-7702 delegation authorization.

example.ts
import { Calibur7702Account } from "abstractionkit";

const eoaAddress = "0xBdbc5FBC9cA8C3F514D073eC3de840Ac84FC6D31";
const smartAccount = new Calibur7702Account(eoaAddress);

const transactions = [
{
to: "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336",
value: 0n,
data: "0x...",
},
];

const userOperation = await smartAccount.createUserOperation(
transactions,
"https://ethereum-sepolia-rpc.publicnode.com", // provider RPC
"https://api.candide.dev/public/v3/11155111", // bundler RPC
{
eip7702Auth: {
chainId: 11155111n, // required for the first UserOperation
},
}
);

Source code​

createUserOperation

signUserOperation​

Signs a UserOperation with a private key. Computes the UserOperation hash and wraps the ECDSA signature in Calibur's format. By default signs with the root key. To sign with a registered secondary key, pass its key hash via overrides.keyHash.

example.ts
// Sign with the root key (EOA private key)
userOperation.signature = smartAccount.signUserOperation(
userOperation,
"0x...private-key",
11155111n // chain ID
);

// Sign with a registered secondary key
userOperation.signature = smartAccount.signUserOperation(
userOperation,
"0x...secondary-private-key",
11155111n,
{ keyHash: "0x...registered-key-hash" }
);

Source code​

signUserOperation

signUserOperationWithSigner​

Signs a UserOperation using an ExternalSigner instead of a raw private key. Integrates viem, ethers, browser wallets, hardware wallets, HSMs, MPC, and WebAuthn through the same API. By default signs with the root key; pass { keyHash } to sign with a registered secondary key.

Since v0.3.8, Calibur accepts signers that implement either signHash or signTypedData. signTypedData signatures are wrapped into Calibur's (keyHash, sig, hookData) layout automatically.

example.ts
import { fromViem } from "abstractionkit";
import { privateKeyToAccount } from "viem/accounts";

const viemAccount = privateKeyToAccount("0x...private-key");

// Sign with the root key
userOperation.signature = await smartAccount.signUserOperationWithSigner(
userOperation,
fromViem(viemAccount),
11155111n, // chain ID
);

// Sign with a registered secondary key
userOperation.signature = await smartAccount.signUserOperationWithSigner(
userOperation,
fromViem(viemAccount),
11155111n,
{ keyHash: "0x...registered-key-hash" },
);

See External Signers for the full list of adapters (viem, ethers, custom) and the Calibur external-signer example for a full end-to-end flow.

Source code​

signUserOperationWithSigner

formatWebAuthnSignature​

Formats a WebAuthn (passkey) assertion into Calibur's signature format. Use this after getting an assertion from the browser's navigator.credentials.get() API. The challenge for the assertion should be the UserOperation hash.

example.ts
import { Calibur7702Account, WebAuthnSignatureData } from "abstractionkit";

// After getting a WebAuthn assertion from the browser
const webAuthnSignatureData: WebAuthnSignatureData = {
authenticatorData: "0x...",
clientDataJSON: '{"type":"webauthn.get","challenge":"...","origin":"https://example.com"}',
challengeIndex: 36n,
typeIndex: 8n,
r: 0x...n,
s: 0x...n,
};

const keyHash = Calibur7702Account.getKeyHash(webAuthnKey);

// Format the WebAuthn signature for the UserOperation
userOperation.signature = smartAccount.formatWebAuthnSignature(
keyHash,
webAuthnSignatureData,
);

Source code​

formatWebAuthnSignature

sendUserOperation​

Sends a signed UserOperation to the bundler for execution on-chain.

example.ts
const response = await smartAccount.sendUserOperation(
userOperation,
"https://api.candide.dev/public/v3/11155111" // bundler URL
);

console.log("UserOperation hash:", response.userOperationHash);

// Wait for the transaction to be included
const receipt = await response.included();
console.log("Transaction receipt:", receipt);

Source code​

sendUserOperation

Key Management Methods​

createSecp256k1Key​

Static method. Creates a secp256k1 key descriptor from an Ethereum address.

example.ts
import { Calibur7702Account } from "abstractionkit";

const secondaryAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18";
const key = Calibur7702Account.createSecp256k1Key(secondaryAddress);

console.log(key);
// { keyType: 2, publicKey: "0x000...742d35Cc6634C0532925a3b844Bc9e7595f2bD18" }

Source code​

createSecp256k1Key

createWebAuthnP256Key​

Static method. Creates a WebAuthn P-256 key descriptor from public key coordinates. Use this when registering a passkey on the account.

example.ts
import { Calibur7702Account } from "abstractionkit";

// Extract x, y coordinates from a WebAuthn credential
const x = 0x1234...n; // P-256 public key x coordinate
const y = 0x5678...n; // P-256 public key y coordinate

const webAuthnKey = Calibur7702Account.createWebAuthnP256Key(x, y);
const keyHash = Calibur7702Account.getKeyHash(webAuthnKey);
console.log("Key hash:", keyHash);

Source code​

createWebAuthnP256Key

createP256Key​

Static method. Creates a raw P-256 (secp256r1) key descriptor from public key coordinates. Use this for non-WebAuthn P-256 keys.

example.ts
import { Calibur7702Account } from "abstractionkit";

const x = 0x1234...n; // P-256 public key x coordinate
const y = 0x5678...n; // P-256 public key y coordinate

const p256Key = Calibur7702Account.createP256Key(x, y);
const keyHash = Calibur7702Account.getKeyHash(p256Key);
console.log("Key hash:", keyHash);

Source code​

createP256Key

getKeyHash​

Static method. Computes the key hash for a Calibur key. Uses double hashing: keccak256(abi.encode(uint8 keyType, bytes32 keccak256(publicKey))).

example.ts
import { Calibur7702Account } from "abstractionkit";

const key = Calibur7702Account.createSecp256k1Key("0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18");
const keyHash = Calibur7702Account.getKeyHash(key);

console.log("Key hash:", keyHash);
// bytes32 hex string, e.g. "0xabc123..."

Source code​

getKeyHash

createRegisterKeyMetaTransactions​

Static method. Creates meta-transactions to register a new key on the Calibur account. Returns two transactions ([register, update]) that must both be included in the same UserOperation. For safety, isAdmin is always forced to false.

example.ts
import { Calibur7702Account } from "abstractionkit";

// Create the key to register
const newKey = Calibur7702Account.createSecp256k1Key("0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18");

// Build register + update transactions (both required in the same UserOp)
const registerTxs = Calibur7702Account.createRegisterKeyMetaTransactions(
newKey,
{
expiration: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days
}
);

// Include both transactions in the UserOperation
const userOperation = await smartAccount.createUserOperation(
registerTxs, // array of two SimpleMetaTransactions
nodeRpc,
bundlerRpc,
);

Source code​

createRegisterKeyMetaTransactions

createRevokeKeyMetaTransaction​

Static method. Creates a meta-transaction to revoke a key from the Calibur account. Only admin keys can execute revoke operations.

example.ts
import { Calibur7702Account } from "abstractionkit";

const keyHash = "0x...key-hash-to-revoke";
const revokeTx = Calibur7702Account.createRevokeKeyMetaTransaction(keyHash);

// Include in a UserOperation signed by an admin key
const userOperation = await smartAccount.createUserOperation(
[revokeTx],
nodeRpc,
bundlerRpc,
);

Source code​

createRevokeKeyMetaTransaction

createUpdateKeySettingsMetaTransaction​

Static method. Creates a meta-transaction to update settings for a registered key. Only admin keys can execute update operations. Throws if isAdmin is set to true in the settings.

example.ts
import { Calibur7702Account } from "abstractionkit";

const keyHash = "0x...registered-key-hash";

// Extend key expiration to 1 year from now
const updateTx = Calibur7702Account.createUpdateKeySettingsMetaTransaction(
keyHash,
{
expiration: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60,
}
);

const userOperation = await smartAccount.createUserOperation(
[updateTx],
nodeRpc,
bundlerRpc,
);

Source code​

createUpdateKeySettingsMetaTransaction

packKeySettings​

Static method. Packs key settings into a single uint256 value. Layout: (isAdmin << 200) | (expiration << 160) | hook.

example.ts
import { Calibur7702Account } from "abstractionkit";

const packed = Calibur7702Account.packKeySettings({
hook: "0x0000000000000000000000000000000000000000",
expiration: Math.floor(Date.now() / 1000) + 86400, // 1 day
isAdmin: false,
});

console.log("Packed settings:", packed);

Source code​

packKeySettings

unpackKeySettings​

Static method. Unpacks a uint256 settings value into a CaliburKeySettingsResult object with all fields populated.

example.ts
import { Calibur7702Account } from "abstractionkit";

const packed = 0x...n; // packed settings from the contract

const settings = Calibur7702Account.unpackKeySettings(packed);
console.log("Admin:", settings.isAdmin);
console.log("Expiration:", settings.expiration);
console.log("Hook:", settings.hook);

Source code​

unpackKeySettings

Read Methods​

isKeyRegistered​

Checks if a key is registered on this account by querying the on-chain isRegistered(bytes32) function.

example.ts
const keyHash = Calibur7702Account.getKeyHash(key);
const isRegistered = await smartAccount.isKeyRegistered(
"https://ethereum-sepolia-rpc.publicnode.com",
keyHash,
);

console.log("Key registered:", isRegistered);

Source code​

isKeyRegistered

getKeySettings​

Gets the settings for a registered key. Returns a CaliburKeySettingsResult with hook address, expiration timestamp, and admin flag.

example.ts
const keyHash = Calibur7702Account.getKeyHash(key);
const settings = await smartAccount.getKeySettings(
"https://ethereum-sepolia-rpc.publicnode.com",
keyHash,
);

console.log("Admin:", settings.isAdmin);
console.log("Expiration:", settings.expiration === 0 ? "never" : new Date(settings.expiration * 1000).toISOString());
console.log("Hook:", settings.hook);

Source code​

getKeySettings

getKey​

Gets the full key data for a registered key, including the key type and ABI-encoded public key bytes.

example.ts
const keyHash = "0x...registered-key-hash";
const key = await smartAccount.getKey(
"https://ethereum-sepolia-rpc.publicnode.com",
keyHash,
);

console.log("Key type:", key.keyType); // 0 = P256, 1 = WebAuthnP256, 2 = Secp256k1
console.log("Public key:", key.publicKey);

Source code​

getKey

getKeys​

Returns all keys registered on this account. Iterates keyCount() and keyAt(i) to enumerate all keys.

example.ts
const keys = await smartAccount.getKeys(
"https://ethereum-sepolia-rpc.publicnode.com",
);

for (const key of keys) {
const keyHash = Calibur7702Account.getKeyHash(key);
const settings = await smartAccount.getKeySettings(nodeRpc, keyHash);
console.log("Key:", keyHash.slice(0, 18) + "...");
console.log(" Type:", key.keyType);
console.log(" Admin:", settings.isAdmin);
}

Source code​

getKeys

isDelegatedToThisAccount​

Checks if the EOA is delegated to this account's Calibur singleton. Returns true only when delegated to the account's delegateeAddress, false if not delegated or delegated to a different singleton.

example.ts
const isDelegated = await smartAccount.isDelegatedToThisAccount(
"https://ethereum-sepolia-rpc.publicnode.com"
);

if (isDelegated) {
console.log("EOA is delegated to the Calibur singleton");
} else {
console.log("EOA is not delegated. Include eip7702Auth in createUserOperation.");
}

Source code​

isDelegatedToThisAccount

getNonce​

Gets the account nonce from the EntryPoint. Supports parallel nonce channels via the optional sequenceKey parameter.

example.ts
// Default nonce (sequence key = 0)
const nonce = await smartAccount.getNonce(
"https://ethereum-sepolia-rpc.publicnode.com"
);
console.log("Nonce:", nonce);

// Parallel nonce channel
const parallelNonce = await smartAccount.getNonce(
"https://ethereum-sepolia-rpc.publicnode.com",
1 // sequence key
);

Source code​

getNonce

Utility Methods​

createAccountCallData​

Static method. Encodes calldata for executeUserOp with BatchedCall format. All transactions (even single ones) go through the same BatchedCall path.

example.ts
import { Calibur7702Account } from "abstractionkit";

const transactions = [
{ to: "0x...", value: 0n, data: "0x..." },
{ to: "0x...", value: 1000000000000000000n, data: "0x" },
];

const callData = Calibur7702Account.createAccountCallData(transactions);

// With revertOnFailure disabled (individual calls can fail without reverting the batch)
const callDataNoRevert = Calibur7702Account.createAccountCallData(transactions, false);

Source code​

createAccountCallData

wrapSignature​

Static method. Wraps a raw ECDSA signature in Calibur's signature format: abi.encode(bytes32 keyHash, bytes signature, bytes hookData). Use this when signing externally (e.g., with viem, hardware wallet, MPC) to avoid manually ABI-encoding the wrapped signature.

example.ts
import { Calibur7702Account } from "abstractionkit";

// Root key hash (bytes32 zero) for the EOA's own key
const ROOT_KEY_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";

const rawEcdsaSignature = "0x..."; // 65-byte ECDSA signature

// Wrap for root key
const wrappedSignature = Calibur7702Account.wrapSignature(
ROOT_KEY_HASH,
rawEcdsaSignature,
);

// Wrap for a secondary key with hook data
const wrappedWithHook = Calibur7702Account.wrapSignature(
"0x...secondary-key-hash",
rawEcdsaSignature,
"0x...hook-data",
);

Source code​

wrapSignature

createDummyWebAuthnSignature​

Static method. Creates a dummy WebAuthn signature for gas estimation when signing with a passkey. The key hash must correspond to an actually registered key on the account, otherwise the contract's validateUserOp will revert.

example.ts
import { Calibur7702Account } from "abstractionkit";

const keyHash = Calibur7702Account.getKeyHash(webAuthnKey);

const dummySignature = Calibur7702Account.createDummyWebAuthnSignature(keyHash);

// Pass as override during UserOperation creation for accurate gas estimation
const userOperation = await smartAccount.createUserOperation(
transactions,
nodeRpc,
bundlerRpc,
{ dummySignature },
);

Source code​

createDummyWebAuthnSignature

getUserOperationHash​

Computes the UserOperation hash for this account's EntryPoint. Convenience wrapper that automatically uses this account's EntryPoint address.

example.ts
const userOpHash = smartAccount.getUserOperationHash(
userOperation,
11155111n // chain ID
);

console.log("UserOperation hash:", userOpHash);
// Use this hash as the challenge for WebAuthn assertions

Source code​

getUserOperationHash

prependTokenPaymasterApproveToCallData​

Prepends a token approve call to existing calldata for a token paymaster. Decodes the existing BatchedCall, prepends an ERC-20 approve transaction, and re-encodes.

example.ts
const callDataWithApproval = smartAccount.prependTokenPaymasterApproveToCallData(
userOperation.callData,
"0xa0b86a33e6b3e96bb24b8e4b28e80e0fb3a4f4b6", // USDC token address
"0x...", // paymaster address
1000000n // approve amount (1 USDC)
);

userOperation.callData = callDataWithApproval;

Source code​

prependTokenPaymasterApproveToCallData

createInvalidateNonceMetaTransaction​

Static method. Creates a meta-transaction to invalidate nonces up to a given value. All nonces below the specified value become invalid, preventing replay of previously signed but unsubmitted UserOperations.

example.ts
import { Calibur7702Account } from "abstractionkit";

// Invalidate all nonces below 100
const invalidateTx = Calibur7702Account.createInvalidateNonceMetaTransaction(100n);

const userOperation = await smartAccount.createUserOperation(
[invalidateTx],
nodeRpc,
bundlerRpc,
);

Source code​

createInvalidateNonceMetaTransaction