Skip to content
Get startedGetting started →

toViemAccount

Adapts a secp256k1 signing session into a viem local account. Every signing method signs through session.signDigest, so signing shows no passkey prompt. The function lives in the @category-labs/mera/viem entry point, which requires viem (^2.28.0) as an optional peer dependency; the root entry point does not use viem.

import {
function toViemAccount(session: Secp256k1SigningSession, options?: ToViemAccountOptions): LocalAccount<"mera">

Adapts a secp256k1 signing session into a viem local account.

Each signing method hashes its input with viem's own hashers and signs the resulting 32-byte digest with session.signDigest, so signing never shows a passkey prompt. The account's address is the EIP-55 checksummed address of the session key, and publicKey is the 65-byte uncompressed public key as hex.

@paramsession - Live secp256k1 signing session that backs the account.

@paramoptions - Adapter inputs.

@returnsA viem local account with source: "mera" implementing signTransaction (honoring a custom serializer), signMessage (EIP-191), signTypedData (EIP-712), signAuthorization (EIP-7702), and raw-hash sign.

@throwsMeraError with code SESSION_ENDED, rejected from every signing method after session.end() has been called.

@throwsMeraError with code INPUT_INVALID, rejected from sign when hash is not exactly 32 bytes.

toViemAccount
} from "@category-labs/mera/viem";
import {
function createSecp256k1SigningSession({ privateKey, }: CreateSigningSessionOptions): Secp256k1SigningSession

Creates a signing session from a secp256k1 private key.

@paramoptions - Signing session inputs.

@returnsA live secp256k1 signing session.

@throwsMeraError with code INPUT_INVALID when privateKey is not a valid secp256k1 scalar.

createSecp256k1SigningSession
} from "@category-labs/mera";
import {
function toViemAccount(session: Secp256k1SigningSession, options?: ToViemAccountOptions): LocalAccount<"mera">

Adapts a secp256k1 signing session into a viem local account.

Each signing method hashes its input with viem's own hashers and signs the resulting 32-byte digest with session.signDigest, so signing never shows a passkey prompt. The account's address is the EIP-55 checksummed address of the session key, and publicKey is the 65-byte uncompressed public key as hex.

@paramsession - Live secp256k1 signing session that backs the account.

@paramoptions - Adapter inputs.

@returnsA viem local account with source: "mera" implementing signTransaction (honoring a custom serializer), signMessage (EIP-191), signTypedData (EIP-712), signAuthorization (EIP-7702), and raw-hash sign.

@throwsMeraError with code SESSION_ENDED, rejected from every signing method after session.end() has been called.

@throwsMeraError with code INPUT_INVALID, rejected from sign when hash is not exactly 32 bytes.

toViemAccount
} from "@category-labs/mera/viem";
import {
function createWalletClient<transport extends Transport, chain extends Chain | undefined = undefined, accountOrAddress extends Account | Address | undefined = undefined, rpcSchema extends RpcSchema | undefined = undefined, const tokens extends Tokens | undefined = undefined>(parameters: WalletClientConfig<transport, chain, accountOrAddress, rpcSchema, tokens>): WalletClient<transport, chain, ParseAccount<accountOrAddress>, rpcSchema, tokens>

Creates a Wallet Client with a given Transport configured for a Chain.

A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.

The Wallet Client supports signing over:

@paramconfig - WalletClientConfig

@returnsA Wallet Client. WalletClient

@example

// JSON-RPC Account import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), })

@example

// Local Account import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…') chain: mainnet, transport: http(), })

createWalletClient
,
function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>

@description Creates a HTTP transport that connects to a JSON-RPC API.

http
,
function parseEther(ether: string, unit?: "wei" | "gwei"): bigint

Converts a string representation of ether to numerical wei.

@example

import { parseEther } from 'viem'

parseEther('420') // 420000000000000000000n

parseEther
} from "viem";
import {
const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}
sepolia
} from "viem/chains";
const
const privateKey: Uint8Array<ArrayBuffer>
privateKey
=
var crypto: Crypto
crypto
.
Crypto.getRandomValues<Uint8Array<ArrayBuffer>>(array: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>

The Crypto.getRandomValues() method lets you get cryptographically strong random values.

MDN Reference

getRandomValues
(new
var Uint8Array: Uint8ArrayConstructor
new (length: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(32));
const
const session: Secp256k1SigningSession
session
=
function createSecp256k1SigningSession({ privateKey, }: CreateSigningSessionOptions): Secp256k1SigningSession

Creates a signing session from a secp256k1 private key.

@paramoptions - Signing session inputs.

@returnsA live secp256k1 signing session.

@throwsMeraError with code INPUT_INVALID when privateKey is not a valid secp256k1 scalar.

createSecp256k1SigningSession
({
privateKey: Uint8Array<ArrayBufferLike>

Curve private key. Must be exactly 32 bytes and, for secp256k1, a valid scalar.

privateKey
});
const
const client: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
};
... 43 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
client
=
createWalletClient<HttpTransport<undefined, false>, {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}, {
...;
}, undefined, undefined>(parameters: {
...;
}): {
...;
}

Creates a Wallet Client with a given Transport configured for a Chain.

A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.

The Wallet Client supports signing over:

@paramconfig - WalletClientConfig

@returnsA Wallet Client. WalletClient

@example

// JSON-RPC Account import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), })

@example

// Local Account import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…') chain: mainnet, transport: http(), })

createWalletClient
({
account?: `0x${string}` | {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
} | Account | undefined

The Account to use for the Client. This will be used for Actions that require an account as an argument.

account
:
function toViemAccount(session: Secp256k1SigningSession, options?: ToViemAccountOptions): LocalAccount<"mera">

Adapts a secp256k1 signing session into a viem local account.

Each signing method hashes its input with viem's own hashers and signs the resulting 32-byte digest with session.signDigest, so signing never shows a passkey prompt. The account's address is the EIP-55 checksummed address of the session key, and publicKey is the 65-byte uncompressed public key as hex.

@paramsession - Live secp256k1 signing session that backs the account.

@paramoptions - Adapter inputs.

@returnsA viem local account with source: "mera" implementing signTransaction (honoring a custom serializer), signMessage (EIP-191), signTypedData (EIP-712), signAuthorization (EIP-7702), and raw-hash sign.

@throwsMeraError with code SESSION_ENDED, rejected from every signing method after session.end() has been called.

@throwsMeraError with code INPUT_INVALID, rejected from sign when hash is not exactly 32 bytes.

toViemAccount
(
const session: Secp256k1SigningSession
session
),
chain?: Chain | {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
} | undefined

Chain for the client.

chain
:
const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}
sepolia
,
transport: HttpTransport<undefined, false>

The RPC transport

transport
:
http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>

@description Creates a HTTP transport that connects to a JSON-RPC API.

http
(),
});
const
const recipient: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
recipient
= "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
const
const hash: `0x${string}`
hash
= await
const client: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
};
... 43 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
client
.
sendTransaction: <{
readonly to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
readonly value: bigint;
}, undefined>(args: SendTransactionParameters<{
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}, {
...;
}, undefined, {
readonly to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
readonly value: bigint;
}>) => Promise<SendTransactionReturnType>

Creates, signs, and sends a new transaction to the network.

@paramargs - SendTransactionParameters

@returnsThe Transaction hash. SendTransactionReturnType

@example

import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), }) const hash = await client.sendTransaction({ account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000000000000000000n, })

@example

// Account Hoisting import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…'), chain: mainnet, transport: http(), }) const hash = await client.sendTransaction({ to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000000000000000000n, })

sendTransaction
({
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
to
:
const recipient: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
recipient
,
value: bigint
value
:
function parseEther(ether: string, unit?: "wei" | "gwei"): bigint

Converts a string representation of ether to numerical wei.

@example

import { parseEther } from 'viem'

parseEther('420') // 420000000000000000000n

parseEther
("0.01"),
});
const session: Secp256k1SigningSession
session
.
function end(): void

Zeroes the session-owned private-key copy; later signing throws SESSION_ENDED.

end
();

The session is positional; options is a ToViemAccountOptions and may be omitted.

Live secp256k1 signing session that backs the account. createSecp256k1SigningSession produces one.

  • Type: NonceManager, from viem
  • Optional; when omitted the account carries no nonce manager

viem nonce manager forwarded to the account. viem clients use it to assign transaction nonces automatically.

import type {
type LocalAccount<source extends string = string, address extends Address = `0x${string}`> = {
address: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["address"];
nonceManager?: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["nonceManager"] | undefined;
sign?: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["sign"] | undefined;
signAuthorization?: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["signAuthorization"] | undefined;
... 5 more ...;
type: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["type"];
}
LocalAccount
} from "viem";
type
type ReturnType = {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
}
ReturnType
=
type LocalAccount<source extends string = string, address extends Address = `0x${string}`> = {
address: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["address"];
nonceManager?: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["nonceManager"] | undefined;
sign?: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["sign"] | undefined;
signAuthorization?: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["signAuthorization"] | undefined;
... 5 more ...;
type: (CustomSource & {
address: address;
publicKey: Hex;
source: source;
type: "local";
})["type"];
}
LocalAccount
<"mera">;

A viem LocalAccount with source: "mera", accepted anywhere viem takes an account.

The EIP-55 checksummed address of the session key, the same value getEvmAddress returns for session.publicKey.

The 65-byte uncompressed secp256k1 public key as hex: the 0x04 prefix, then 128 hex characters.

Serializes the transaction, signs its keccak-256 digest, and resolves to the signed serialized transaction. options.serializer replaces viem’s serializeTransaction for both steps. EIP-4844 transactions are hashed without their sidecars and serialized with them.

Resolves to the EIP-191 personal-message signature for message, 65 bytes as hex.

Resolves to the EIP-712 signature for the typed data, 65 bytes as hex.

Signs an EIP-7702 authorization and resolves to the signed authorization object: the contract address, chain ID, and nonce together with the signature fields.

Signs a 32-byte hash directly, with no additional hashing, and resolves to the 65-byte hex signature.

  • SESSION_ENDED: any signing method rejects with this after session.end().
  • INPUT_INVALID: sign rejects with this when hash is not exactly 32 bytes.

Signatures are low-S, which EVM chains require since EIP-2.