Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Skill — Copyable reference implementation. Use as-is or customize. See Skills Philosophy.
Viem Account Abstraction
Drop-in replacement for viem’s account module using Voltaire primitives. Implements the full LocalAccount interface for signing messages, transactions, and typed data.
Quick Start
import { privateKeyToAccount } from './examples/viem-account/index.js';
// Create account from private key
const account = privateKeyToAccount(
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
);
console.log(account.address); // '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
console.log(account.publicKey); // '0x04...' (65 bytes)
console.log(account.type); // 'local'
console.log(account.source); // 'privateKey'
// Sign a message (EIP-191)
const sig = await account.signMessage({ message: 'Hello, Ethereum!' });
// Sign typed data (EIP-712)
const typedSig = await account.signTypedData({
domain: { name: 'App', version: '1', chainId: 1n },
types: { Message: [{ name: 'content', type: 'string' }] },
primaryType: 'Message',
message: { content: 'Hello!' },
});
// Sign a transaction
const txSig = await account.signTransaction({
type: 2,
chainId: 1n,
to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
value: 1000000000000000000n,
});
API Reference
privateKeyToAccount
Creates a viem-compatible PrivateKeyAccount from a hex-encoded private key.
function privateKeyToAccount(
privateKey: `0x${string}`,
options?: {
nonceManager?: NonceManager;
}
): PrivateKeyAccount;
Parameters:
privateKey - 32-byte private key as hex string with 0x prefix
options.nonceManager - Optional nonce manager for transaction ordering
Returns: PrivateKeyAccount with all signing methods
Throws:
InvalidPrivateKeyError - If private key is invalid length or zero
Account Methods
sign()
Signs a raw 32-byte hash.
const signature = await account.sign({
hash: '0x0000000000000000000000000000000000000000000000000000000000000001'
});
// Returns: '0x...' (65 bytes: r + s + v)
signMessage()
Signs a message using EIP-191 personal sign format.
// String message
const sig1 = await account.signMessage({ message: 'Hello!' });
// Raw hex message
const sig2 = await account.signMessage({
message: { raw: '0x48656c6c6f' }
});
// Raw bytes message
const sig3 = await account.signMessage({
message: { raw: new Uint8Array([72, 101, 108, 108, 111]) }
});
signTypedData()
Signs EIP-712 typed structured data.
const signature = await account.signTypedData({
domain: {
name: 'MyApp',
version: '1',
chainId: 1n,
},
types: {
Transfer: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
},
primaryType: 'Transfer',
message: {
to: Address.from('0x...'), // Note: Use Address primitive
amount: 1000000000000000000n,
},
});
signTransaction(transaction, options)
Signs an Ethereum transaction.
const signedTx = await account.signTransaction({
type: 2,
chainId: 1n,
nonce: 0n,
maxFeePerGas: 20000000000n,
maxPriorityFeePerGas: 1000000000n,
gas: 21000n,
to: '0x...',
value: 1000000000000000000n,
});
signAuthorization()
Signs EIP-7702 authorization for account abstraction.
const auth = await account.signAuthorization({
chainId: 1n,
address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
nonce: 0n,
});
console.log(auth);
// {
// address: '0x...',
// chainId: 1n,
// nonce: 0n,
// r: '0x...',
// s: '0x...',
// v: 27n,
// yParity: 0,
// }
Standalone Signing Functions
For tree-shaking, use standalone functions:
import { signMessage, signTypedData, signTransaction } from './examples/viem-account/index.js';
// signMessage
const sig = await signMessage({
message: 'Hello!',
privateKey: privateKeyBytes, // Uint8Array
});
// signTypedData
const typedSig = await signTypedData({
domain: { name: 'App' },
types: { Message: [{ name: 'content', type: 'string' }] },
primaryType: 'Message',
message: { content: 'Hello!' },
privateKey: privateKeyBytes,
});
Factory Pattern
For dependency injection:
import { PrivateKeyToAccount, SignMessage, SignTypedData } from './examples/viem-account/index.js';
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { keccak256 } from './crypto/Keccak256/hash.js';
import { sign } from './crypto/Secp256k1/sign.js';
import { hashTypedData } from './crypto/EIP712/EIP712.js';
// Create factory with custom dependencies
const createAccount = PrivateKeyToAccount({
getPublicKey: (pk, compressed) => secp256k1.getPublicKey(pk, compressed),
keccak256,
sign,
hashTypedData,
});
const account = createAccount('0x...');
Type Definitions
interface PrivateKeyAccount {
address: string; // Checksummed address
publicKey: `0x${string}`; // Uncompressed (0x04 prefix)
source: 'privateKey';
type: 'local';
nonceManager?: NonceManager;
sign: ({ hash }) => Promise<`0x${string}`>;
signAuthorization: (auth) => Promise<SignedAuthorization>;
signMessage: ({ message }) => Promise<`0x${string}`>;
signTransaction: (tx, opts?) => Promise<`0x${string}`>;
signTypedData: (data) => Promise<`0x${string}`>;
}
type SignableMessage = string | { raw: `0x${string}` | Uint8Array };
interface TypedDataDefinition {
domain?: EIP712Domain;
types: Record<string, TypeProperty[]>;
primaryType: string;
message: Record<string, unknown>;
}
Viem Compatibility Notes
Full Compatibility
- Account structure matches viem’s
LocalAccount
- All signing methods return the same format
- Address is EIP-55 checksummed
- Public key is uncompressed (65 bytes)
- Signatures are 65 bytes (r + s + v)
Known Differences
- EIP-712 addresses: Voltaire’s EIP712 expects
Address primitives (Uint8Array), not strings. Convert addresses using Address.from('0x...') before signing typed data with address fields.
- Transaction serialization: Uses placeholder serializer. For production, integrate with Voltaire’s Transaction primitive.
Error Handling
import {
InvalidPrivateKeyError,
InvalidAddressError,
SigningError,
} from './examples/viem-account/errors.js';
try {
const account = privateKeyToAccount('0xinvalid');
} catch (error) {
if (error instanceof InvalidPrivateKeyError) {
console.error('Invalid key:', error.message);
console.error('Code:', error.code);
console.error('Docs:', error.docsPath);
}
}
Integration with WalletClient
import { createWalletClient, http } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from './examples/viem-account/index.js';
// Voltaire account works with viem's WalletClient
const account = privateKeyToAccount('0x...');
const client = createWalletClient({
account, // Use Voltaire account
chain: mainnet,
transport: http(),
});
// Send transaction
const hash = await client.sendTransaction({
to: '0x...',
value: parseEther('0.01'),
});
File Structure
examples/viem-account/
AccountTypes.ts # TypeScript type definitions
errors.ts # Custom error classes
index.ts # Module exports
privateKeyToAccount.js # Main factory function
signMessage.js # EIP-191 message signing
signTransaction.js # Transaction signing
signTypedData.js # EIP-712 typed data signing
Account.test.ts # Comprehensive tests
REQUIREMENTS.md # Extracted requirements from viem